通过Dataset API,我们可以直接在数据上执行关系型操作,这一功能主要是借助了Spark SQL的一些核心功能。本文主要分析Dataset API和Spark SQL模块之间的关联关系
一、Dataset初始化
Dataset类有两个构造参数,SparkSession和LogicalPlan
Dataset支持从外部数据源或是JVM内存提取数据来进行构造,这里我们以一个外部csv文件为例来理解Dataset的初始化过程。
1.1 读取csv文件初始化
1.1.1 SparkSession.read返回DagtaFrameReader
1.1.2 DataFrame提供多种数据源的读取方式,包括.csv
1.1.3 DataFrameReader.csv调用format方法,通过字符串参数指定format为csv,为DataFrameReader设置属性
1.1.4 DataFrameReader.load开始加载数据
1.1.5 通过文件路径、用户指定schema和文件格式csv构造DataSource
HadoopFsRelation
1.1.6 调用LogicalRelation来构造一个LogicalPlan
1.1.7 调用Dataset.ofRows构造一个新的Dataset
1.1.8 LogicalPlan转换为QueryExecution
SessionState.executePlan调用BaseSessionStateBuilder将LogicalPlan转换为QueryExecution
1.1.9 QueryExecution构建并解析执行计划
QueryExecution.assertAnalyzed方法中,先使用Analyzer将执行计划从UnresolvedLogicalPlan转换为ResolvedLogicalPlan,再进行验证
此时QueryExecution如下,
== Parsed Logical Plan ==
Relation[value#0] text
== Analyzed Logical Plan ==
value: string
Relation[value#0] text
== Optimized Logical Plan ==
Relation[value#0] text
== Physical Plan ==
*FileScan text [value#0] Batched: false, Format: Text, Location: InMemoryFileIndex[
file:/Users/lulijun/git/spark_study/sparkserver/src/main/resources/sparktest.csv], PartitionFilters: [], PushedFilters: [], ReadSchema: struct<value:string>
二、Dataset transformation
2.1 filter
2.1.1 sqlparser解析
注意Spark2.0后SqlParser已经转移到SparkSession中了,首先,针对filter的操作进行解析
2.1.2 构建filter操作的逻辑执行计划
利用Antlr进行词法语法解析(此处不再赘述)构造一个Column对象。调用withTypedPlan方法生成一个新的Dataset。
wityTypedPlan方法的参数是一个Filter类实例,Filter是一个LogicalPlan中的一元操作节点UnaryNode,Filter接收一个已有的LogicalPlan以及一个Expression,LogicalPlan作为Expression的一个子节点。生成一个新的 UnaryNode节点。即返回值是包含了FileScan和Filter的一个完整的LogicalPlan
2.1.4 构建新的Dataset
调用Dataset的构造函数,返回一个包含了增加Filter Expression的LogicalPlan的Dataset,同样,构造过程中会再调用Analyzer对UnresolvedLogicalPlan进行analyze以及optimize
2.2 groupby
2.2.1 api语句解析
如果合法,返回一个NamedExpression(需要重新看)
2.2.3 构建 RelationGroupedDataset
2.3 aggr
非RelationGroupedDataset不提供aggr方法
RelationGroupedDataset.aggr方法与Dataset.filter方法类似,先通过解析api参数构造Expression,再结合已有LogicPlan和新增加的Expression构造一个叠加LogicalPlan的Dataset。
2.3.1 api语句解析
2.3.2 构建Expression
方法strToExpr构造Expression
2.3.3 构建Dataset
toDF接收Expression参数,构造新的Dataset。调用Aggregate(groupingExpr, aliasedAgg, df.logicalPlan)将Expression和已有的logicalPlan封装为新的LogicalPlan
三、Dataset action
3.1 count
- 调用Dataset中的count api,注意,中括号中构造了了一个函数,plan为输入参数,plan.executeCollect().head.getLong(0)为输出,作为一个函数体提交给withAction函数
2. withAction函数中,将当前Dataset的QueryExecution中的物理执行计划executedPlan作为前面函数体的参数,提交给SQLExecution.withNewExecutionId方法,这个方法中实际执行的还是action(qe.executedPlan),其他操作只是为了获取新的执行id等等
3. 从上面可以得知,这个函数最终执行的入参是这个Dataset本身的物理执行计划SparkPlan,执行的过程是SparkPlan.executeCollect().head.getLong(0)
4. SparkPlan的getByteArrayRdd方法首先会对这个物理执行计划SparkPlan执行,再进行一系列的序列化
递归操作获取SparkPlan的子节点RDD,进行一系列该物理执行计划的特性处理,也就是在RDD上附加函数,最终返回一个新的RDD。在这个过程中也构造出了RDD的血缘关系lineage
四、总结
Dataset API的构建和运行与Spark SQL模块紧密结合,始终围绕着逻辑执行计划和物理执行计划运行。
Dataset的延迟执行特性在于,transformation操作中,通过构建Expression来不断封装逻辑执行计划,同时使用Analyzer和Optimizer解析和优化执行计划;action操作中,通过在已有的逻辑执行计划基础上构建而来的物理执行计划上迭加单个物理执行计划,并显式的触发Job提交来进行计算。