spark SQL(11)sql语句执行流程源码

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语句的处理过程 如上图所示:

  1. SqlParser将SQL语句解析成一个逻辑执行计划(未解析)
  2. Analyzer利用HiveMeta中表/列等信息,对逻辑执行计划进行解析(如表/列是否存在等)
  3. SparkOptimizer利用Rule Based(基于经验规则RBO)/Cost Based(基于代价CBO)的优化方法,对逻辑执行计划进行优化(如谓词下推/JoinReorder)
  4. SparkPlanner将逻辑执行计划转换成物理执行计划(如Filter -> FilterExec),
    同时从某些逻辑算子的多种物理算子实现中根据RBO/CBO选择其中一个合适的物理算子(如Join的多个实现BroadcastJoin/SortMergeJoin/HashJoin中选择一个实现)
  5. PrepareForExecution是执行物理执行计划之前做的一些事情,比如ReuseExchange/WholeStageCodegen的处理等等
  6. 最终在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)
  }

猜你喜欢

转载自blog.csdn.net/rover2002/article/details/106235227