简述
自从Spark统一了RDD和DataFrame(DataSet)后,批处理上对DataFrame的使用频率上也大大超过了原始RDD,同样的SparkSql的使用也越来越频繁,因此对其中的执行过程进行简单了解是必不可少的,本文就对SparkSql源码进行简单的流程走读,涉及复杂内容的地方做到知其作用目的即可,不予深究。
从一条sql开始
在新版本中,SparkSession
早已经作为统一入口,下面就直接进入SparkSession.sql
方法中:
class SparkSession private...
def sql(sqlText: String): DataFrame = {
// tracker作为一个统一的封装,主要用于记录执行时长,会贯穿整SQL的解析过程
val tracker = new QueryPlanningTracker
// measurePhase 方法的内容放在下面,其中 sessionState的构造过程就不再展开,是一个lazy对象,从配置文件读取类名,通过反射来完成实例化
val plan = tracker.measurePhase(QueryPlanningTracker.PARSING) {
sessionState.sqlParser.parsePlan(sqlText)
}
Dataset.ofRows(self, plan, tracker)
}
// Measure the start and end time of a phase. 这里是柯里化的一个应用,在后续代码中,我们会看到调用逻辑
def measurePhase[T](phase: String)(f: => T): T = {
val startTime = System.currentTimeMillis()
val ret = f //这里调用第二个传入参数,是一个函数
val endTime = System.currentTimeMillis
// phase 作为第一个传参,在后续调用会对同一类处理过程传同一值,用于准确计算每个阶段的耗时
// val PARSING = "parsing" val ANALYSIS = "analysis" val OPTIMIZATION = "optimization"
val PLANNING = "planning"
if (phasesMap.containsKey(phase)) {
val oldSummary = phasesMap.get(phase)
phasesMap.put(phase, new PhaseSummary(oldSummary.startTimeMs, endTime))
} else {
phasesMap.put(phase, new PhaseSummary(startTime, endTime))
}
ret
}
}
根据上面的measurePhase方法内容,直接去查看柯里化的第二部分函数,sessionState.sqlParser.parsePlan(sqlText)
内容,这里我们直接看接口实现类CatalystSqlParser
class CatalystSqlParser(conf: SQLConf) extends AbstractSqlParser {
val astBuilder = new AstBuilder(conf)
}
可以看到内部实例化了AstBuilder
,进一步看parsePlan
,又是一个柯里化,第二部分是匿名函数
/** Creates LogicalPlan for a given SQL string. */
override def parsePlan(sqlText: String): LogicalPlan = parse(sqlText) {
parser =>
astBuilder.visitSingleStatement(parser.singleStatement()) match {
case plan: LogicalPlan => plan
case _ =>
val position = Origin(None, None)
throw new ParseException(Option(sqlText), "Unsupported SQL statement", position, position)
}
}
先来看parse
方法体,简单来看柯里化第二个参数仅在末尾调用,仅此可以简单理解为将上述匿名函数替换至此。(本文后面部分不再对柯里化函数进行分拆解释)
protected def parse[T](command: String)(toResult: SqlBaseParser => T): T = {
logDebug(s"Parsing command: $command")
// 实例化 词法解释器,具体不再展开
val lexer = new SqlBaseLexer(new UpperCaseCharStream(CharStreams.fromString(command)))
...
// 构造Token流
val tokenStream = new CommonTokenStream(lexer)
// 实例化 语法解释器,具体不再展开
val parser = new SqlBaseParser(tokenStream)
try {
try {
// first, try parsing with potentially faster SLL mode
parser.getInterpreter.setPredictionMode(PredictionMode.SLL)
// 调用第二个传参,是一个函数,将实例化的 parser作为入参
toResult(parser)
}
catch {
}
}
catch {
}
}
回到parsePlan
的第二部分,其中parser.singleStatement()
返回内容为语法文件中定义的最顶级节点,也即SqlBase.g4
中的各主节点,visitSingleStatement
用于遍历语法树,将各节点替换成LogicalPlan
返回。
{
parser =>
astBuilder.visitSingleStatement(parser.singleStatement()) match {
case plan: LogicalPlan =>