前言
由前面博客我们知道了SparkSql整个解析流程如下:
- sqlText 经过 SqlParser 解析成 Unresolved LogicalPlan;
- analyzer 模块结合catalog进行绑定,生成 resolved LogicalPlan;
- optimizer 模块对 resolved LogicalPlan 进行优化,生成 optimized LogicalPlan;
- SparkPlan 将 LogicalPlan 转换成PhysicalPlan;
- prepareForExecution()将 PhysicalPlan 转换成可执行物理计划;
- 使用 execute()执行可执行物理计划;
详解analyzer模块
Analyzer模块将Unresolved LogicalPlan结合元数据catalog进行绑定,最终转化为Resolved LogicalPlan。跟着代码看流程:
// 代码1
spark.sql("select * from table").show(false)
---
// 代码2
def sql(sqlText: String): DataFrame = {
Dataset.ofRows(self, sessionState.sqlParser.parsePlan(sqlText))
}
---
// 代码3
def ofRows(sparkSession: SparkSession, logicalPlan: LogicalPlan): DataFrame = {
val qe = sparkSession.sessionState.executePlan(logicalPlan)
qe.assertAnalyzed()
new Dataset[Row](sparkSession, qe, RowEncoder(qe.analyzed.schema))
}
代码2中的后半段sessionState.sqlParser.parsePlan(sqlText)
在上篇博客已经解析,即将sqlText通过第三方解析器antlr解析成语法树。
接着进入代码3,通过Unresolved LogicalPlan创建QueryExecution对象, 这是一个非常关键的类,analyzer 、optimizer 、SparkPlan、executedPlan等都是在该类中触发的。继续跟着代码3走:
// 代码4
def assertAnalyzed(): Unit = {
// Analyzer is invoked outside the try block to avoid calling it again from within the
// catch block below.
analyzed
...
// 代码5
lazy val analyzed: LogicalPlan = {
SparkSession.setActiveSession(sparkSession)
sparkSession.sessionState.analyzer.execute(logical)
}
最终调用analyzer的execute方法,该方法在Analyzer的父类RuleExecutor中,另外还继承了CheckAnalysis 类,用于对 plan 做一些解析,如果解析失败则抛出用户层面的错误:
class Analyzer(
catalog: SessionCatalog,
conf: SQLConf,
maxIterations: Int)
extends RuleExecutor[LogicalPlan] with CheckAnalysis {
可以看到构造器中有SessionCatalog类型的catalog,此类管理着临时表、view、函数及外部依赖元数据(如hive metastore),是analyzer进行绑定的桥梁。
继承了RuleExecutor的类(Analyzer、Optimizer)需要实现def batches: Seq[Batch]
方法,在execute方法中再对此batches进行遍历执行,batches 由多个Batch构成,每个Batch由多个Rule构成,看看Batch的定义protected case class Batch(name: String, strategy: Strategy, rules: Rule[TreeType]*)
,Strategy是每个Batch的执行策略即该batch被最大执行次数maxIterations ,Once和FixedPoint即执行一次和多次(默认是100次),停止执行batch的条件有两个,一是在执行maxIterations 次之前规则前后plan没有变化,二是执行次数达到maxIterations 。batch里面的所有规则都继承了Rule,在execute方法里就是遍历这些batchs,将所有的规则应用到LogicalPlan上。
接下来我们看看execute中具体是怎么做的:
def execute(plan: TreeType): TreeType = {
var curPlan = plan
//遍历batches
batches.foreach { batch =>
val batchStartPlan = curPlan
var iteration = 1 //每个batch单独计数
var lastPlan = curPlan //保存遍历batch之前的plan,以便和遍历后的plan进行比较,若无变化则停止执行当前batch
var continue = true
// Run until fix point (or the max number of iterations as specified in the strategy.
while (continue) {
curPlan = batch.rules.foldLeft(curPlan) { // 遍历一个batch所有的Rule,并应用到LogicalPlan上
case (plan, rule) =>
val