spark通常这样开始执行一条SQL语句:
val spark_sess = SparkSession
.builder()
.appName("Spark SQL basic example")
.config("spark.sql.shuffle.partitions", "600")
.getOrCreate()
df = spark.read.json("examples/src/main/resources/people.json")
df.createOrReplaceTempView("people")
val sqlDF = spark_sess.sql("select * from people")
sqlDF.show()
spark2.4的SQL引擎名为 Catalyst (中文意思是催化剂)。
我们看看一条SQL语句在sql()中的执行过程。如图:
SparkSQL中对一条SQL语句的处理过程 如上图所示:
- SqlParser将SQL语句解析成一个逻辑执行计划(未解析)
- Analyzer利用HiveMeta中表/列等信息,对逻辑执行计划进行解析(如表/列是否存在等)
- SparkOptimizer利用Rule Based(基于经验规则RBO)/Cost Based(基于代价CBO)的优化方法,对逻辑执行计划进行优化(如谓词下推/JoinReorder)
- SparkPlanner将逻辑执行计划转换成物理执行计划(如Filter -> FilterExec),
同时从某些逻辑算子的多种物理算子实现中根据RBO/CBO选择其中一个合适的物理算子(如Join的多个实现BroadcastJoin/SortMergeJoin/HashJoin中选择一个实现) - PrepareForExecution是执行物理执行计划之前做的一些事情,比如ReuseExchange/WholeStageCodegen的处理等等
- 最终在SparkCore中执行该物理执行计划。
其实SQLContext.sql()也是调用的SparkSession.sql():
def sql(sqlText: String): DataFrame = sparkSession.sql(sqlText)
sql()函数:
执行了一个parsePlan,返回一个DataFrame。
def sql(sqlText: String): DataFrame = {
Dataset.ofRows(self, sessionState.sqlParser.parsePlan(sqlText))
}
sessionState是一个lazy的SessionState类:
lazy val sessionState: SessionState = {
parentSessionState
.map(_.clone(this))
.getOrElse {
val state = SparkSession.instantiateSessionState(
SparkSession.sessionStateClassName(sparkContext.conf),
self)
initialSessionOptions.foreach { case (k, v) => state.conf.setConfString(k, v) }
state
}
}
SessionState类定义在
org.apache.spark.sql.internal下的SessionState.scala
它是A class that holds all session-specific state in a given [[SparkSession]].
private[sql] class SessionState(
sharedState: SharedState,
val conf: SQLConf,
val experimentalMethods: ExperimentalMethods,
val functionRegistry: FunctionRegistry,
val udfRegistration: UDFRegistration,
catalogBuilder: () => SessionCatalog,
val sqlParser: ParserInterface,
analyzerBuilder: () => Analyzer,
optimizerBuilder: () => Optimizer,
val planner: SparkPlanner,
val streamingQueryManager: StreamingQueryManager,
val listenerManager: ExecutionListenerManager,
resourceLoaderBuilder: () => SessionResourceLoader,
createQueryExecution: LogicalPlan => QueryExecution,
createClone: (SparkSession, SessionState) => SessionState) {
SparkSession.sessionStateClassName:
用builder模式构建SessionState,
如果是in-memory方式,就返回org.apache.spark.sql.internal.SessionStateBuilder
如果是 hive方式,就返回org.apache.spark.sql.hive.HiveSessionStateBuilder
private def sessionStateClassName(conf: SparkConf): String = {
// spark.sql.catalogImplementation, 分为 hive 和 in-memory模式,默认为 in-memory 模式
conf.get(CATALOG_IMPLEMENTATION) match {
case "hive" => HIVE_SESSION_STATE_BUILDER_CLASS_NAME
case "in-memory" => classOf[SessionStateBuilder].getCanonicalName
}
}
sqlParser.parsePlan()
sqlParser定义在org.apache.spark.sql.execution下的SparkSqlParser.scala:
class SparkSqlParser(conf: SQLConf) extends AbstractSqlParser {
AbstractSqlParser定义在catalyst项目中,org.apache.spark.sql.catalyst.parser下的ParseDriver.scala:
abstract class AbstractSqlParser extends ParserInterface
AbstractSqlParser的parsePlan()根据传入的sqlText,返回一个逻辑计划LogicalPlan。
逻辑计划会生成AST抽象语法树:
// SparkSqlParser.scala
/** 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)
}
}
LogicalPlan = parse(sqlText),这里逻辑计划是一个函数parse(),
参数是(command: String),返回值是(toResult: SqlBaseParser => T),定义
在SparkSqlParser中:
protected override def parse[T](command: String)(toResult: SqlBaseParser => T): T = {
// substitutor是一个命令替换器,用于把SQL中的命令参数都替换掉
super.parse(substitutor.substitute(command))(toResult)
}
调用了AbstractSqlParser的parse():
在这个方法中调用ANLTR4的API将SQL转换为AST抽象语法树,然后调用 toResult(parser) 方法,这个 toResult 方法就是parsePlan 方法的回调方法。
https://www.cnblogs.com/johnny666888/p/12345142.html
parser.singleStatement()就是执行了SqlBaseParser的回调,生成AST,把结果传给AstBuilder.visitSingleStatement(),
封装成unresolved LogicalPlan。
protected def parse[T](command: String)(toResult: SqlBaseParser => T): T = {
logDebug(s"Parsing command: $command")
// lexer 就是词法分析了
val lexer = new SqlBaseLexer(new UpperCaseCharStream(CharStreams.fromString(command)))
lexer.removeErrorListeners()
lexer.addErrorListener(ParseErrorListener)
lexer.legacy_setops_precedence_enbled = SQLConf.get.setOpsPrecedenceEnforced
val tokenStream = new CommonTokenStream(lexer)
// SqlBaseParser是从\org\apache\spark\sql\catalyst\parser\SqlBase.g4生成的,SqlBase.g4是一个“语法文件”
// 定义了ruleNames:"singleStatement", "singleDataType", "createTableHeader", "insertInto", "partitionSpecLocation",...
// ruleNames是一系列函数的函数名。
// 定义了VocabularyImpl,"SELECT", "FROM", "ADD", "AS", "ALL", "ANY", "DISTINCT", "WHERE", "GROUP", "BY",...
// 用于句法分析。
val parser = new SqlBaseParser(tokenStream)
parser.addParseListener(PostProcessor)
parser.removeErrorListeners()
parser.addErrorListener(ParseErrorListener)
parser.legacy_setops_precedence_enbled = SQLConf.get.setOpsPrecedenceEnforced
try {
try {
// first, try parsing with potentially faster SLL mode
parser.getInterpreter.setPredictionMode(PredictionMode.SLL)
// 返回的toResult看起来是用SqlBaseParser克隆的
toResult(parser)
}
catch {
case e: ParseCancellationException =>
// if we fail, parse with LL mode
tokenStream.seek(0) // rewind input stream
parser.reset()
// Try Again.
parser.getInterpreter.setPredictionMode(PredictionMode.LL)
toResult(parser)
}
}
catch {
case e: ParseException if e.command.isDefined =>
throw e
case e: ParseException =>
throw e.withCommand(command)
case e: AnalysisException =>
val position = Origin(e.line, e.startPosition)
throw new ParseException(Option(command), e.message, position, position)
}
}
}
尽管现在看来,使用ANTLR解析SQL生成AST是一个black box,但对于Spark SQL来说,其后续流程的输入已经得到。
总体执行流程如下图所示:从提供的输入API(SQL,Dataset, dataframe)开始,依次经过unresolved逻辑计划,解析的逻辑计划,优化的逻辑计划,物理计划,然后根据cost based优化,选取一条物理计划进行执行。从unresolved logical plan开始, sql的查询是通过抽象语法树(AST)来表示的,所以以后各个操作都是对AST进行的等价转换操作。
Dataset.ofRows():
Dataset.ofRows()创建一个DataFrame时,回调LogicalPlan做词法句法分析和检查,正常后根据LogicalPlan.schema
创建一个Dataset[Row]对象,返回这个DataFrame。
可见,用户拿到封装了Unresolved LogicalPlan的DataFrame时,并没有可操作的数据,
当执行到了show()、select().show()这样的操作,或者对DataFrame转换出的RDD执行到Action操作时,
才会真正执行SQL语句后续的优化、物理计划的选择,最后执行物理计划,生成真正的DataFrame。
// class Dataset.scala
def ofRows(sparkSession: SparkSession, logicalPlan: LogicalPlan): DataFrame = {
// 返回QueryExecution
val qe = sparkSession.sessionState.executePlan(logicalPlan)
// assertAnalyzed 执行lazy的 analyzed 函数:
// lazy val analyzed: LogicalPlan = {
// SparkSession.setActiveSession(sparkSession)
// sparkSession.sessionState.analyzer.executeAndCheck(logical)
// }
qe.assertAnalyzed()
new Dataset[Row](sparkSession, qe, RowEncoder(qe.analyzed.schema))
}
// class SessionState中executePlan的定义:
def executePlan(plan: LogicalPlan): QueryExecution = createQueryExecution(plan)
// class BaseSessionStateBuilder中:
protected def createQueryExecution: LogicalPlan => QueryExecution = { plan =>
new QueryExecution(session, plan)
}