SparkSQL基於DataSourceV2自定義數據源

SparkSQL基於DataSourceV2自定義數據源

版本說明:Spark 2.3

前言:之前在SparkSQL數據源操作文章中整理了一些SparkSQL內置數據源的使用,總的來說SparkSQL支持的數據源還是挺豐富的,但業務上可能不拘束於這幾種數據源,比如將HBase作爲SparkSQL的數據源,REST數據源等。這裏主要講一下在Spark2.3版本之後推出的DataSourceV2,基於DataSourceV2實現自定義數據源

1 DataSourceV1 VS DataSourceV2

自Spark1.3版本之後,引入了數據源API,我們可以實現自定義數據源。2.3版本之後又引入的新版API,關於V1與V2的區別以及使用可以參考https://blog.csdn.net/zjerryj/article/details/84922369與https://developer.ibm.com/code/2018/04/16/introducing-apache-spark-data-sources-api-v2/這兩篇文章。這裏簡單的總結一下V1的缺點,以及V2的新特性。

1.1 DataSourceV1缺點

  • 依賴上層API
  • 難以添加新的優化算子
  • 難以傳遞分區信息
  • 缺少事務性的寫操作
  • 缺少列存儲和流式計算支持

1.2 DataSourceV2優點

  • DataSourceV2 API使用Java編寫
  • 不依賴於上層API(DataFrame/RDD)
  • 易於擴展,可以添加新的優化,同時保持向後兼容
  • 提供物理信息,如大小、分區等
  • 支持Streamin Source/Sink
  • 靈活、強大和事務性的寫入API

1.3 Spark2.3中V2的功能

  • 支持列掃描和行掃描
  • 列裁剪和過濾條件下推
  • 可以提供基本統計和數據分區
  • 事務寫入API
  • 支持微批和連續的Streaming Source/Sink

2 基於DataSourceV2實現輸入源

SparkSQL的DataSourceV2的實現與StructuredStreaming自定義數據源如出一轍,思想是一樣的,但是具體實現有所不同,主要步驟如下:

第一步:繼承DataSourceV2和ReadSupport創建XXXDataSource類,重寫ReadSupport的creatReader方法,用來返回自定義的DataSourceReader類,如返回自定義XXXDataSourceReader實例

第二步:繼承DataSourceReader創建XXXDataSourceReader類,重寫DataSourceReader的readSchema方法用來返回數據源的schema,重寫DataSourceReader的createDataReaderFactories用來返回多個自定義DataReaderFactory實例

第三步:繼承DataReaderFactory創建DataReader工廠類,如XXXDataReaderFactory,重寫DataReaderFactory的createDataReader方法,返回自定義DataRader實例

第四步:繼承DataReader類創建自定義的DataReader,如XXXDataReader,重寫DataReader的next()方法,用來告訴Spark是否有下條數據,用來觸發get()方法,重寫DataReader的get()方法獲取數據,重寫DataReader的close()方法用來關閉資源

2.1 繼承DataSourceV2和ReadSupport創建XXXDataSource類

這裏以創建CustomDataSourceV2類爲例

2.1.1 創建CustomDataSourceV2類

/**
  * 創建DataSource提供類
  * 1.繼承DataSourceV2向Spark註冊數據源
  * 2.繼承ReadSupport支持讀數據
  */
class CustomDataSourceV2 extends DataSourceV2
  with ReadSupport {
      // todo
}

2.1.2 重寫ReadSupport的createReader方法

該方法用來返回一個用戶自定義的DataSourceReader實例

  /**
    * 創建Reader
    *
    * @param options 用戶定義的options
    * @return 自定義的DataSourceReader
    */
  override def createReader(options: DataSourceOptions): DataSourceReader = new CustomDataSourceV2Reader(options)

2.2 繼承DataSourceReader創建XXXDataSourceReader類

該類用來自定義DataSourceReader,需要繼承DataSourceReader,並重寫readSchema和createDataReaderFactories方法。

2.2.1 創建CustomDataSourceV2Reader類

/**
  * 自定義的DataSourceReader
  * 繼承DataSourceReader
  * 重寫readSchema方法用來生成schema
  * 重寫createDataReaderFactories,用來根據條件,創建多個工廠實例
  *
  * @param options options
  */
class CustomDataSourceV2Reader(options: DataSourceOptions) extends DataSourceReader {
    // Override some functions
}

2.2.2 重寫DataSourceReader的readSchema方法

該方法用來返回數據源的schema

/**
  * 生成schema
  *
  * @return schema
  */
override def readSchema(): StructType = ???

2.2.3 重寫DataSourceReader的createDataReaderFactories方法

實現該方法,可以根據不同的條件,創建多個createDataReader工廠實例,用來併發獲取數據?(暫且這麼理解的,或者是按照分區獲取數據?)

  /**
    * 創建DataReader工廠實例
    *
    * @return 多個工廠類實例
    */
  override def createDataReaderFactories(): util.List[DataReaderFactory[Row]] = {
    import collection.JavaConverters._
    Seq(
      new CustomDataSourceV2ReaderFactory().asInstanceOf[DataReaderFactory[Row]]
    ).asJava
  }

2.3 繼承DataReaderFactory創建DataReader工廠類

該類是DataReader的工廠來,用來返回DataReader實例

2.3.1 創建CustomDataSourceV2Factory類

/**
  * 自定義DataReaderFactory類
  */
class CustomDataSourceV2ReaderFactory extends DataReaderFactory[Row] {
   // Override some functions
}

2.3.2 重寫DataReaderFactory的createDataReader方法

該方法用來實例化自定義的DataReader

  /**
    * 重寫createDataReader方法,用來實例化自定義的DataReader
    *
    * @return 自定義的DataReader
    */
  override def createDataReader(): DataReader[Row] = new CustomDataReader

2.4 繼承DataReader類創建自定義的DataReader

該類爲重點實現部分,用來自定義獲取數據的方式

2.4.1 創建CustomDataReader類

/**
  * 自定義DataReader類
  */
class CustomDataReader extends DataReader[Row] {
    // Override some functions
}

2.4.2 重寫CustomDataReader的next()方法

該方法返回一個布爾值,來告訴Spark是否含有下條數據,以便觸發get()方法獲取數據

  /**
    * 是否有下一條數據
    *
    * @return boolean
    */
  override def next(): Boolean = ???

2.4.3 重寫CustomDataReader的get()方法

該方法用來獲取數據,返回類型是在繼承DataReader時指定的泛型

  /**
    * 獲取數據
    * 當next爲true時會調用get方法獲取數據
    *
    * @return Row
    */
  override def get(): Row = ???

2.4.4 重寫CustomDataReader的close()方法

該方法用來關閉相應的資源

  /**
    * 關閉資源
    */
  override def close(): Unit = ???

2.5 以REST爲例,實現自定義的數據源

這裏主要是從REST接口裏獲取JSON格式的數據,然後生成DataFrame數據源

2.5.1 創建RestDataSource類

class RestDataSource extends DataSourceV2 with ReadSupport with WriteSupport {

  override def createReader(options: DataSourceOptions): DataSourceReader =
    new RestDataSourceReader(
      options.get("url").get(),
      options.get("params").get(),
      options.get("xPath").get(),
      options.get("schema").get()
    )
}

2.5.2 創建RestDataSourceReader類

/**
  * 創建RestDataSourceReader
  *
  * @param url          REST服務的的api
  * @param params       請求需要的參數
  * @param xPath        JSON數據的xPath
  * @param schemaString 用戶傳入的schema字符串
  */
class RestDataSourceReader(url: String, params: String, xPath: String, schemaString: String)
  extends DataSourceReader {
  // 使用StructType.fromDDL方法將schema字符串轉成StructType類型
  var requiredSchema: StructType = StructType.fromDDL(schemaString)

  /**
    * 生成schema
    *
    * @return schema
    */
  override def readSchema(): StructType = requiredSchema

  /**
    * 創建工廠類
    *
    * @return List[實例]
    */
  override def createDataReaderFactories(): util.List[DataReaderFactory[Row]] = {
    import collection.JavaConverters._
    Seq(
      new RestDataReaderFactory(url, params, xPath).asInstanceOf[DataReaderFactory[Row]]
    ).asJava
  }
}

2.5.3 創建RestDataReaderFactory

/**
  * RestDataReaderFactory工廠類
  *
  * @param url    REST服務的的api
  * @param params 請求需要的參數
  * @param xPath  JSON數據的xPath
  */
class RestDataReaderFactory(url: String, params: String, xPath: String) extends DataReaderFactory[Row] {
  override def createDataReader(): DataReader[Row] = new RestDataReader(url, params, xPath)
}

2.5.4 創建RestDataReader

/**
  * RestDataReader類
  *
  * @param url    REST服務的的api
  * @param params 請求需要的參數
  * @param xPath  JSON數據的xPath
  */
class RestDataReader(url: String, params: String, xPath: String) extends DataReader[Row] {
  // 使用Iterator模擬數據
  val data: Iterator[Seq[AnyRef]] = getIterator

  override def next(): Boolean = {
    data.hasNext
  }

  override def get(): Row = {
    val seq = data.next().map {
      // 浮點類型會自動轉爲BigDecimal,導致Spark無法轉換
      case decimal: BigDecimal =>
        decimal.doubleValue()
      case x => x
    }
    Row(seq: _*)
  }

  override def close(): Unit = {
    println("close source")
  }

  def getIterator: Iterator[Seq[AnyRef]] = {
    import scala.collection.JavaConverters._
    val res: List[AnyRef] = RestDataSource.requestData(url, params, xPath)
    res.map(r => {
      r.asInstanceOf[JSONObject].asScala.values.toList
    }).toIterator
  }
}

2.5.5 測試RestDataSource

object RestDataSourceTest {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .master("local[2]")
      .appName(this.getClass.getSimpleName)
      .getOrCreate()

    val df = spark.read
      .format("com.hollysys.spark.sql.datasource.rest.RestDataSource")
      .option("url", "http://model-opcua-hollysysdigital-test.hiacloud.net.cn/aggquery/query/queryPointHistoryData")
      .option("params", "{\n    \"startTime\": \"1543887720000\",\n    \"endTime\": \"1543891320000\",\n    \"maxSizePerNode\": 1000,\n    \"nodes\": [\n        {\n            \"uri\": \"/SymLink-10000012030100000-device/5c174da007a54e0001035ddd\"\n        }\n    ]\n}")
      .option("xPath", "$.result.historyData")
      //`response` ARRAY<STRUCT<`historyData`:ARRAY<STRUCT<`s`:INT,`t`:LONG,`v`:FLOAT>>>>
      .option("schema", "`s` INT,`t` LONG,`v` DOUBLE")
      .load()
    df.printSchema()
    df.show(false)
 
  }
}

3 基於DataSourceV2實現輸出源

基於DataSourceV2實現自定義的輸出源,需要以下幾個步驟:

第一步:繼承DataSourceV2和WriteSupport創建XXXDataSource,重寫createWriter方法用來返回自定義的DataSourceWriter

第二步:繼承DataSourceWriter創建XXXDataSourceWriter類,重寫createWriterFactory返回自定義的DataWriterFactory,重寫commit方法,用來提交整個事務。重寫abort方法,用來做事務回滾

第三步:繼承DataWriterFactory創建XXXDataWriterFactory類,重寫createWriter方法返回自定義的DataWriter

第四步:繼承DataWriter創建XXXDataWriter類,重寫write方法,用來將數據寫出,重寫commit方法用來提交事務,重寫abort方法用來做事務回滾

3.1 繼承DataSourceV和WriterSupport創建XXXDataSource類

3.1.1 創建CustomDataSourceV2類

/**
  * 創建DataSource提供類
  * 1.繼承DataSourceV2向Spark註冊數據源
  * 2.繼承WriteSupport支持讀數據
  */
class CustomDataSourceV2 extends DataSourceV2
  with WriteSupport {
      // todo
}

3.1.2 重寫createWriter方法

  /**
    * 創建Writer
    *
    * @param jobId   jobId
    * @param schema  schema
    * @param mode    保存模式
    * @param options 用於定義的option
    * @return Optional[自定義的DataSourceWriter]
    */
  override def createWriter(jobId: String,
                            schema: StructType,
                            mode: SaveMode,
                            options: DataSourceOptions): Optional[DataSourceWriter] = Optional.of(new CustomDataSourceV2Writer)

3.2 繼承DataSourceWriter創建XXXDataSourceWriter類

3.2.1 創建CustomDataSourceV2Writer

需要繼承DataSourceWriter

/**
  * 自定義DataSourceWriter
  * 繼承DataSourceWriter
  */
class CustomDataSourceV2Writer extends DataSourceWriter {
    // Override some functions
}

3.3 繼承DataWriterFactory創建XXXDataWriterFactory類

3.3.1 創建CustomDataWriterFactory

class CustomDataWriterFactory extends DataWriterFactory[Row] {
    // Override some functions
}

3.3.2 重寫createDataWriter方法

該方法返回一個自定義的DataWriter

  /**
    * 創建DataWriter
    *
    * @param partitionId   分區ID
    * @param attemptNumber 重試次數
    * @return DataWriter
    *         每個分區創建一個RestDataWriter實例
    */
  override def createDataWriter(partitionId: Int, attemptNumber: Int): DataWriter[Row] = ???

3.4 繼承DataWriter創建XXXDataWriter類

3.4.1 創建CustomDataWriter類

class CustomDataWriter extends DataWriter[Row] {
	// Overrride some functions
}

3.4.2 重寫write方法

該方法用來寫出單條數據,每條數據都會觸發該方法

/**
  * write
  *
  * @param record 單條記錄
  *               每條記錄都會觸發該方法
  */
override def write(record: Row): Unit = ???

3.4.3 重寫commit方法

該方法一般用於事務提交,每個分區觸發一次

/**
    * commit
    *
    * @return commit message
    *         每個分區觸發一次
    */
  override def commit(): WriterCommitMessage = ???

3.4.4 重寫abort方法

該方法用於事務回滾,當write方法發生異常之後觸發該方法


  /**
    * 回滾:當write發生異常時觸發該方法
    */
  override def abort(): Unit = ???

4 完整代碼

4.1 自定義DataSource示例代碼:

package com.hollysys.spark.sql.datasource

import java.util
import java.util.Optional

import org.apache.spark.sql.{Row, SaveMode}
import org.apache.spark.sql.sources.v2.reader.{DataReader, DataReaderFactory, DataSourceReader}
import org.apache.spark.sql.sources.v2.writer.{DataSourceWriter, DataWriter, DataWriterFactory, WriterCommitMessage}
import org.apache.spark.sql.sources.v2.{DataSourceOptions, DataSourceV2, ReadSupport, WriteSupport}
import org.apache.spark.sql.types.StructType

/**
  * @author : shirukai
  * @date : 2019-01-30 10:37
  *       Spark SQL 基於DataSourceV2接口實現自定義數據源
  */

/**
  * 創建DataSource提供類
  * 1.繼承DataSourceV2向Spark註冊數據源
  * 2.繼承ReadSupport支持讀數據
  * 3.繼承WriteSupport支持讀數據
  */
class CustomDataSourceV2 extends DataSourceV2
  with ReadSupport
  with WriteSupport {

  /**
    * 創建Reader
    *
    * @param options 用戶定義的options
    * @return 自定義的DataSourceReader
    */
  override def createReader(options: DataSourceOptions): DataSourceReader = new CustomDataSourceV2Reader(options)

  /**
    * 創建Writer
    *
    * @param jobId   jobId
    * @param schema  schema
    * @param mode    保存模式
    * @param options 用於定義的option
    * @return Optional[自定義的DataSourceWriter]
    */
  override def createWriter(jobId: String,
                            schema: StructType,
                            mode: SaveMode,
                            options: DataSourceOptions): Optional[DataSourceWriter] = Optional.of(new CustomDataSourceV2Writer)
}


/**
  * 自定義的DataSourceReader
  * 繼承DataSourceReader
  * 重寫readSchema方法用來生成schema
  * 重寫createDataReaderFactories,用來根據條件,創建多個工廠實例
  *
  * @param options options
  */
class CustomDataSourceV2Reader(options: DataSourceOptions) extends DataSourceReader {
  /**
    * 生成schema
    *
    * @return schema
    */
  override def readSchema(): StructType = ???

  /**
    * 創建DataReader工廠實例
    *
    * @return 多個工廠類實例
    */
  override def createDataReaderFactories(): util.List[DataReaderFactory[Row]] = {
    import collection.JavaConverters._
    Seq(
      new CustomDataSourceV2ReaderFactory().asInstanceOf[DataReaderFactory[Row]]
    ).asJava
  }
}


/**
  * 自定義DataReaderFactory類
  */
class CustomDataSourceV2ReaderFactory extends DataReaderFactory[Row] {
  /**
    * 重寫createDataReader方法,用來實例化自定義的DataReader
    *
    * @return 自定義的DataReader
    */
  override def createDataReader(): DataReader[Row] = new CustomDataReader
}


/**
  * 自定義DataReader類
  */
class CustomDataReader extends DataReader[Row] {
  /**
    * 是否有下一條數據
    *
    * @return boolean
    */
  override def next(): Boolean = ???

  /**
    * 獲取數據
    * 當next爲true時會調用get方法獲取數據
    *
    * @return Row
    */
  override def get(): Row = ???

  /**
    * 關閉資源
    */
  override def close(): Unit = ???
}

/**
  * 自定義DataSourceWriter
  * 繼承DataSourceWriter
  */
class CustomDataSourceV2Writer extends DataSourceWriter {
  /**
    * 創建WriterFactory
    *
    * @return 自定義的DataWriterFactory
    */
  override def createWriterFactory(): DataWriterFactory[Row] = ???

  /**
    * commit
    *
    * @param messages 所有分區提交的commit信息
    *                 觸發一次
    */
  override def commit(messages: Array[WriterCommitMessage]): Unit = ???

  /** *
    * abort
    *
    * @param messages 當write異常時調用
    */
  override def abort(messages: Array[WriterCommitMessage]): Unit = ???
}

/**
  * DataWriterFactory工廠類
  */
class CustomDataWriterFactory extends DataWriterFactory[Row] {
  /**
    * 創建DataWriter
    *
    * @param partitionId   分區ID
    * @param attemptNumber 重試次數
    * @return DataWriter
    *         每個分區創建一個RestDataWriter實例
    */
  override def createDataWriter(partitionId: Int, attemptNumber: Int): DataWriter[Row] = ???
}
/**
  * DataWriter
  */
class CustomDataWriter extends DataWriter[Row] {
  /**
    * write
    *
    * @param record 單條記錄
    *               每條記錄都會觸發該方法
    */
  override def write(record: Row): Unit = ???
  /**
    * commit
    *
    * @return commit message
    *         每個分區觸發一次
    */
  override def commit(): WriterCommitMessage = ???


  /**
    * 回滾:當write發生異常時觸發該方法
    */
  override def abort(): Unit = ???
}

4.2 自定義RestDataSource代碼

package com.hollysys.spark.sql.datasource.rest

import java.math.BigDecimal
import java.util
import java.util.Optional

import com.alibaba.fastjson.{JSONArray, JSONObject, JSONPath}
import org.apache.http.client.fluent.Request
import org.apache.http.entity.ContentType
import org.apache.spark.sql.{Row, SaveMode, SparkSession}
import org.apache.spark.sql.sources.v2.reader.{DataReader, DataReaderFactory, DataSourceReader, SupportsPushDownRequiredColumns}
import org.apache.spark.sql.sources.v2.writer.{DataSourceWriter, DataWriter, DataWriterFactory, WriterCommitMessage}
import org.apache.spark.sql.sources.v2.{DataSourceOptions, DataSourceV2, ReadSupport, WriteSupport}
import org.apache.spark.sql.types.StructType

/**
  * @author : shirukai
  * @date : 2019-01-09 16:53
  *       基於Rest的Spark SQL DataSource
  */
class RestDataSource extends DataSourceV2 with ReadSupport with WriteSupport {

  override def createReader(options: DataSourceOptions): DataSourceReader =
    new RestDataSourceReader(
      options.get("url").get(),
      options.get("params").get(),
      options.get("xPath").get(),
      options.get("schema").get()
    )

  override def createWriter(jobId: String,
                            schema: StructType,
                            mode: SaveMode,
                            options: DataSourceOptions): Optional[DataSourceWriter] = Optional.of(new RestDataSourceWriter)
}

/**
  * 創建RestDataSourceReader
  *
  * @param url          REST服務的的api
  * @param params       請求需要的參數
  * @param xPath        JSON數據的xPath
  * @param schemaString 用戶傳入的schema字符串
  */
class RestDataSourceReader(url: String, params: String, xPath: String, schemaString: String)
  extends DataSourceReader {
  // 使用StructType.fromDDL方法將schema字符串轉成StructType類型
  var requiredSchema: StructType = StructType.fromDDL(schemaString)

  /**
    * 生成schema
    *
    * @return schema
    */
  override def readSchema(): StructType = requiredSchema

  /**
    * 創建工廠類
    *
    * @return List[實例]
    */
  override def createDataReaderFactories(): util.List[DataReaderFactory[Row]] = {
    import collection.JavaConverters._
    Seq(
      new RestDataReaderFactory(url, params, xPath).asInstanceOf[DataReaderFactory[Row]]
    ).asJava
  }


}

/**
  * RestDataReaderFactory工廠類
  *
  * @param url    REST服務的的api
  * @param params 請求需要的參數
  * @param xPath  JSON數據的xPath
  */
class RestDataReaderFactory(url: String, params: String, xPath: String) extends DataReaderFactory[Row] {
  override def createDataReader(): DataReader[Row] = new RestDataReader(url, params, xPath)
}

/**
  * RestDataReader類
  *
  * @param url    REST服務的的api
  * @param params 請求需要的參數
  * @param xPath  JSON數據的xPath
  */
class RestDataReader(url: String, params: String, xPath: String) extends DataReader[Row] {
  // 使用Iterator模擬數據
  val data: Iterator[Seq[AnyRef]] = getIterator

  override def next(): Boolean = {
    data.hasNext
  }

  override def get(): Row = {
    val seq = data.next().map {
      // 浮點類型會自動轉爲BigDecimal,導致Spark無法轉換
      case decimal: BigDecimal =>
        decimal.doubleValue()
      case x => x
    }
    Row(seq: _*)
  }

  override def close(): Unit = {
    println("close source")
  }

  def getIterator: Iterator[Seq[AnyRef]] = {
    import scala.collection.JavaConverters._
    val res: List[AnyRef] = RestDataSource.requestData(url, params, xPath)
    res.map(r => {
      r.asInstanceOf[JSONObject].asScala.values.toList
    }).toIterator
  }
}

/** *
  * RestDataSourceWriter
  */
class RestDataSourceWriter extends DataSourceWriter {
  /**
    * 創建RestDataWriter工廠類
    *
    * @return RestDataWriterFactory
    */
  override def createWriterFactory(): DataWriterFactory[Row] = new RestDataWriterFactory

  /**
    * commit
    *
    * @param messages 所有分區提交的commit信息
    *                 觸發一次
    */
  override def commit(messages: Array[WriterCommitMessage]): Unit = ???

  /** *
    * abort
    *
    * @param messages 當write異常時調用
    */
  override def abort(messages: Array[WriterCommitMessage]): Unit = ???

}

/**
  * DataWriterFactory工廠類
  */
class RestDataWriterFactory extends DataWriterFactory[Row] {
  /**
    * 創建DataWriter
    *
    * @param partitionId   分區ID
    * @param attemptNumber 重試次數
    * @return DataWriter
    *         每個分區創建一個RestDataWriter實例
    */
  override def createDataWriter(partitionId: Int, attemptNumber: Int): DataWriter[Row] = new RestDataWriter(partitionId, attemptNumber)
}

/**
  * RestDataWriter
  *
  * @param partitionId   分區ID
  * @param attemptNumber 重試次數
  */
class RestDataWriter(partitionId: Int, attemptNumber: Int) extends DataWriter[Row] {
  /**
    * write
    *
    * @param record 單條記錄
    *               每條記錄都會觸發該方法
    */
  override def write(record: Row): Unit = {

    println(record)
  }

  /**
    * commit
    *
    * @return commit message
    *         每個分區觸發一次
    */
  override def commit(): WriterCommitMessage = {
    RestWriterCommitMessage(partitionId, attemptNumber)
  }

  /**
    * 回滾:當write發生異常時觸發該方法
    */
  override def abort(): Unit = {
    println("abort 方法被出發了")
  }
}

case class RestWriterCommitMessage(partitionId: Int, attemptNumber: Int) extends WriterCommitMessage

object RestDataSource {
  def requestData(url: String, params: String, xPath: String): List[AnyRef] = {
    import scala.collection.JavaConverters._
    val response = Request.Post(url).bodyString(params, ContentType.APPLICATION_JSON).execute()
    JSONPath.read(response.returnContent().asString(), xPath)
      .asInstanceOf[JSONArray].asScala.toList
  }
}

object RestDataSourceTest {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .master("local[2]")
      .appName(this.getClass.getSimpleName)
      .getOrCreate()

    val df = spark.read
      .format("com.hollysys.spark.sql.datasource.rest.RestDataSource")
      .option("url", "http://model-opcua-hollysysdigital-test.hiacloud.net.cn/aggquery/query/queryPointHistoryData")
      .option("params", "{\n    \"startTime\": \"1543887720000\",\n    \"endTime\": \"1543891320000\",\n    \"maxSizePerNode\": 1000,\n    \"nodes\": [\n        {\n            \"uri\": \"/SymLink-10000012030100000-device/5c174da007a54e0001035ddd\"\n        }\n    ]\n}")
      .option("xPath", "$.result.historyData")
      //`response` ARRAY<STRUCT<`historyData`:ARRAY<STRUCT<`s`:INT,`t`:LONG,`v`:FLOAT>>>>
      .option("schema", "`s` INT,`t` LONG,`v` DOUBLE")
      .load()


    df.printSchema()
    df.show(false)
//    df.repartition(5).write.format("com.hollysys.spark.sql.datasource.rest.RestDataSource")
//      .save()
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章