工作中一直用到sparkSQL,但是对其底层实现不是很清楚,这里通过学习相关源码信息来加深对Spark的相关理解。这里以spark-3.2.1的源码来学习
第一篇,我们先从整体SparkSQL的执行流程介绍来有个整体的概念
1.执行流程概述
sql在转换为RDD执行中会经过如下几个阶段
词法分析:将输入的SQL语句拆解为单词序列,并识别出关键字、标识、常量等信息;
语法分析:检查词法分析解析出来的单词序列是否满足SQL语法规则;
逻辑计划:语法分析完的结果会生成原始的逻辑计划信息,在这个阶段会进行分析和进行优化处理,具体会分为如下几个阶段
物理计划:物理计划会将上一步的逻辑计划进一步转换,生成可以执行的计划信息
2.代码分析
这里我们通过example例子来入手
val spark = SparkSession.builder().appName("Spark SQL basic example").config("spark.some.config.option", "some-value").getOrCreate()
val df = spark.read.json("examples/src/main/resources/people.json")
df.createOrReplaceTempView("people")
val sqlDF = spark.sql("SELECT * FROM people")
2.1 各个阶段转换的核心类
SQL各个阶段转换处理使用的类信息都在SessionState(SparkSession中的sessionState)中,我们看看SessionState的定义
private[sql] class SessionState(
...
val sqlParser: ParserInterface,
analyzerBuilder: () => Analyzer,
optimizerBuilder: () => Optimizer,
val planner: SparkPlanner,
...
)
SparkSqlParser:ParserInterface的子类,处理SQL的词法和语法分析过程;
Analyzer:通过提供的Catalog信息,给逻辑计划的各节点绑定各种信息,处理后为分析后的逻辑计划(analyzed logicalPlan);
Optimizer:按照定义的规则对一些低效的逻辑计划进行转换;
2.2 代码流程
def sql(sqlText: String): DataFrame = withActive {
val tracker = new QueryPlanningTracker
val plan = tracker.measurePhase(QueryPlanningTracker.PARSING) {
sessionState.sqlParser.parsePlan(sqlText)
}
Dataset.ofRows(self, plan, tracker)
}
-
从SparkSession的sql方法入口,这里调用sqlParser.parsePlan来生成了逻辑计划
-
从Dataset.ofRows里面,在新建QueryExecution实例,逻辑计划和物理计划的处理都在该类里面处理的。各个阶段的结果分别对应到QueryExecution类中的
- analyzed
- optimizedPlan
- sparkPlan
- executedPlan
Tips:在处理的各个阶段有通过QueryPlanningTracker类来跟踪具体过程,所以可以根据该类来快速定位具体处理过程的代码
object QueryPlanningTracker {
// Define a list of common phases here.
valPARSING= "parsing"
valANALYSIS= "analysis"
valOPTIMIZATION= "optimization"
valPLANNING= "planning"
2.3执行计划查看
前面spark.sql()返回的是一个DataFrame对象,而DataFrame是个一种特殊的DataSet(DataSet[Row]),DataSet中的explain方法可以查看相应的执行计划信息,另外通过DataSet的queryExecution属性可以直接访问各个具体的计划对象。
scala> sqlDF.explain("extended");
== Parsed Logical Plan ==
'Project [*]
+- 'UnresolvedRelation [people], [], false
== Analyzed Logical Plan ==
age: bigint, name: string
Project [age#7L, name#8]
+- SubqueryAlias people
+- View (`people`, [age#7L,name#8])
+- Relation [age#7L,name#8] json
== Optimized Logical Plan ==
Relation [age#7L,name#8] json
== Physical Plan ==
FileScan json [age#7L,name#8] Batched: false, DataFilters: [], Format: JSON, Location: InMemoryFileIndex(1 paths)[file:/Users/liuj/opt/spark-3.2.2-bin-hadoop3.2/examples/src/main/resou..., PartitionFilters: [], PushedFilters: [], ReadSchema: struct<age:bigint,name:string>
直接使用SQL的话通过如下语句也可以查看执行计划
EXPLAIN EXTENDED select * from people;