標籤:man sys spark ble 預設 abstract 而不是 record comm
上周Spark1.2剛發布,周末在家沒事,把這個特性給瞭解一下,順便分析下源碼,看一看這個特性是如何設計及實現的。
/** Spark SQL源碼分析系列文章*/
(Ps: External DataSource使用篇地址:Spark SQL之External DataSource外部資料源(一)樣本 http://blog.csdn.net/oopsoom/article/details/42061077)
一、Sources包核心
Spark SQL在Spark1.2中提供了External DataSource API,開發人員可以根據介面來實現自己的外部資料源,如avro, csv, json, parquet等等。
在Spark SQL原始碼的org/spark/sql/sources目錄下,我們會看到關於External DataSource的相關代碼。這裡特別介紹幾個:
1、DDLParser
專門負責解析外部資料源SQL的SqlParser,解析create temporary table xxx using options (key ‘value‘, key ‘value‘) 建立載入外部資料源表的語句。
[java] view plain copy
- protected lazy val createTable: Parser[LogicalPlan] =
- CREATE ~ TEMPORARY ~ TABLE ~> ident ~ (USING ~> className) ~ (OPTIONS ~> options) ^^ {
- case tableName ~ provider ~ opts =>
- CreateTableUsing(tableName, provider, opts)
- }
2、CreateTableUsing
一個RunnableCommand,通過反射從外部資料源lib中執行個體化Relation,然後註冊到為temp table。
[java] view plain copy
- private[sql] case class CreateTableUsing(
- tableName: String,
- provider: String, // org.apache.spark.sql.json
- options: Map[String, String]) extends RunnableCommand {
-
- def run(sqlContext: SQLContext) = {
- val loader = Utils.getContextOrSparkClassLoader
- val clazz: Class[_] = try loader.loadClass(provider) catch { //do reflection
- case cnf: java.lang.ClassNotFoundException =>
- try loader.loadClass(provider + ".DefaultSource") catch {
- case cnf: java.lang.ClassNotFoundException =>
- sys.error(s"Failed to load class for data source: $provider")
- }
- }
- val dataSource = clazz.newInstance().asInstanceOf[org.apache.spark.sql.sources.RelationProvider] //json包DefaultDataSource
- val relation = dataSource.createRelation(sqlContext, new CaseInsensitiveMap(options))//建立JsonRelation
-
- sqlContext.baseRelationToSchemaRDD(relation).registerTempTable(tableName)//註冊
- Seq.empty
- }
- }
2、DataSourcesStrategy
在 Strategy 一文中,我已講過Streategy的作用,用來Plan產生物理計劃的。這裡提供了一種專門為瞭解析外部資料源的策略。
最後會根據不同的BaseRelation生產不同的PhysicalRDD。不同的BaseRelation的scan策略下文會介紹。
[java] view plain copy
- private[sql] object DataSourceStrategy extends Strategy {
- def apply(plan: LogicalPlan): Seq[SparkPlan] = plan match {
- case PhysicalOperation(projectList, filters, l @ LogicalRelation(t: CatalystScan)) =>
- pruneFilterProjectRaw(
- l,
- projectList,
- filters,
- (a, f) => t.buildScan(a, f)) :: Nil
- ......
- case l @ LogicalRelation(t: TableScan) =>
- execution.PhysicalRDD(l.output, t.buildScan()) :: Nil
-
- case _ => Nil
- }
3、interfaces.scala
該檔案定義了一系列可擴充的外部資料源介面,對於想要接入的外部資料源,我們只需實現該介面即可。裡面比較重要的trait RelationProvider 和 BaseRelation,下文會詳細介紹。
4、filters.scala
該Filter定義了如何在載入外部資料源的時候,就進行過濾。注意哦,是載入外部資料源到Table裡的時候,而不是Spark裡進行filter。這個有點像hbase的coprocessor,查詢過濾在Server上就做了,不在Client端做過濾。
5、LogicalRelation
封裝了baseRelation,繼承了catalyst的LeafNode,實現MultiInstanceRelation。
二、External DataSource註冊流程用spark sql下sql/json來做樣本, 畫了一張流程圖,如下: 註冊外部資料源的表的流程:1、提供一個外部資料源檔案,比如json檔案。2、提供一個實現了外部資料源所需要的interfaces的類庫,比如sql下得json包,在1.2版本後改為了External Datasource實現。3、引入SQLContext,使用DDL建立表,如create temporary table xxx using options (key ‘value‘, key ‘value‘) 4、External Datasource的DDLParser將對該SQL進行Parse5、Parse後封裝成為一個CreateTableUsing類的對象。該類是一個RunnableCommand,其run方法會直接執行建立表語句。6、該類會通過反射來建立一個org.apache.spark.sql.sources.RelationProvider,該trait定義要createRelation,如json,則建立JSONRelation,若avro,則建立AvroRelation。7、得到external releation後,直接調用SQLContext的baseRelationToSchemaRDD轉換為SchemaRDD8、最後registerTempTable(tableName) 來註冊為Table,可以用SQL來查詢了。 三、External DataSource解析流程先看圖,圖如下: Spark SQL解析SQL流程如下:1、Analyzer通過Rule解析,將UnresolvedRelation解析為JsonRelation。2、通過Parse,Analyzer,Optimizer最後得到JSONRelation(file:///path/to/shengli.json,1.0) 3、通過sources下得DataSourceStrategy將LogicalPlan映射到物理計劃PhysicalRDD。4、PhysicalRDD裡包含了如何查詢外部資料的規則,可以調用execute()方法來執行Spark查詢。 四、External Datasource Interfaces在第一節我已經介紹過,主要的interfaces,主要看一下BaseRelation和RelationProvider。如果我們要實現一個外部資料源,比如avro資料來源,支援spark sql操作avro file。那麼久必須定義AvroRelation來繼承BaseRelation。同時也要實現一個RelationProvider。 BaseRelation:是外部資料源的抽象,裡面存放了schema的映射,和如何scan資料的規則。[java] view plain copy
- abstract class BaseRelation {
- def sqlContext: SQLContext
- def schema: StructType
[java] view plain copy
- abstract class PrunedFilteredScan extends BaseRelation {
- def buildScan(requiredColumns: Array[String], filters: Array[Filter]): RDD[Row]
- }
1、schema我們如果自訂Relation,必須重寫schema,就是我們必須描述對於外部資料源的Schema。2、buildScan我們定義如何查詢外部資料源,提供了4種Scan的策略,對應4種BaseRelation。 我們支援4種BaseRelation,分為TableScan, PrunedScan,PrunedFilterScan,CatalystScan。 1、TableScan: 預設的Scan策略。 2、PrunedScan: 這裡可以傳入指定的列,requiredColumns,列裁剪,不需要的列不會從外部資料源載入。 3、PrunedFilterScan: 在列裁剪的基礎上,並且加入Filter機制,在載入資料也的時候就進行過濾,而不是在用戶端請求返回時做Filter。 4、CatalystScan: Catalyst的支援傳入expressions來進行Scan。支援列裁剪和Filter。 RelationProvider:我們要實現這個,接受Parse後傳入的參數,來產生對應的External Relation,就是一個反射生產外部資料源Relation的介面。[java] view plain copy
- trait RelationProvider {
- /**
- * Returns a new base relation with the given parameters.
- * Note: the parameters‘ keywords are case insensitive and this insensitivity is enforced
- * by the Map that is passed to the function.
- */
- def createRelation(sqlContext: SQLContext, parameters: Map[String, String]): BaseRelation
- }
五、External Datasource定義樣本在Spark1.2之後,json和parquet也改為通過實現External API來進行外部資料源查詢的。下面以json的外部資料源定義為樣本,說明是如何?的: 1、JsonRelation 定義處理對於json檔案的,schema和Scan策略,均基於JsonRDD,細節可以自行閱讀JsonRDD。 [java] view plain copy
- private[sql] case class JSONRelation(fileName: String, samplingRatio: Double)(
- @transient val sqlContext: SQLContext)
- extends TableScan {
-
- private def baseRDD = sqlContext.sparkContext.textFile(fileName) //讀取json file
-
- override val schema =
- JsonRDD.inferSchema( // jsonRDD的inferSchema方法,能自動識別json的schema,和類型type。
- baseRDD,
- samplingRatio,
- sqlContext.columnNameOfCorruptRecord)
-
- override def buildScan() =
- JsonRDD.jsonStringToRow(baseRDD, schema, sqlContext.columnNameOfCorruptRecord) //這裡還是JsonRDD,調用jsonStringToRow查詢返回Row
- }
2、DefaultSourceparameters中可以擷取到options中傳入的path等自訂參數。
這裡接受傳入的參數,來狗仔JsonRelation。[java] view plain copy
- private[sql] class DefaultSource extends RelationProvider {
- /** Returns a new base relation with the given parameters. */
- override def createRelation(
- sqlContext: SQLContext,
- parameters: Map[String, String]): BaseRelation = {
- val fileName = parameters.getOrElse("path", sys.error("Option ‘path‘ not specified"))
- val samplingRatio = parameters.get("samplingRatio").map(_.toDouble).getOrElse(1.0)
-
- JSONRelation(fileName, samplingRatio)(sqlContext)
- }
- }
六、總結 External DataSource源碼分析下來,可以總結為3部分。 1、外部資料源的註冊流程 2、外部資料源Table查詢的計劃解析流程 3、如何自訂一個外部資料源,重寫BaseRelation定義外部資料源的schema和scan的規則。定義RelationProvider,如何產生外部資料源Relation。 External Datasource此部分API還有可能在後續的build中改動,目前只是涉及到了查詢,關於其它的操作還未涉及。——EOF——
原創文章,轉載請註明:
轉載自:OopsOutOfMemory盛利的Blog, OopsOutOfMemory
本文連結地址:http://blog.csdn.net/oopsoom/article/details/42064075
註:本文基於署名-非商業性使用-禁止演繹 2.5 中國大陸(CC BY-NC-ND 2.5 CN)協議,歡迎轉載、轉寄和評論,但是請保留本文作者署名和文章連結。如若需要用於商業目的或者與授權方面的協商,請聯絡我。
轉自:http://blog.csdn.net/oopsoom/article/details/42064075
第十一篇:Spark SQL 源碼分析之 External DataSource外部資料源