首先我们回顾一下使用SparkSql的一般步骤:
1. 从数据源或者RDD读取数据,构造出一个DataFrame
2. 使用DataFrame的registerTempTable方法根据刚才读取的数据创建一个临时表
3. 调用sqlContext的sql方法执行sql语句

那么在这里我们就从sql语句的调用开始:

def sql(sqlText: String): DataFrame = {
   DataFrame(this, parseSql(sqlText))
}

parseSql的返回类型是一个LogicalPlan(代表一个逻辑计划),这里就是使用sqlContext和调用parseSql方法返回的Logical Plan封装为一个DataFrame

protected[sql] val sqlParser = new SparkSQLParser(getSQLDialect().parse(_))
protected[sql] val ddlParser = new DDLParser(sqlParser.parse(_))
protected[sql] def parseSql(sql: String): LogicalPlan = ddlParser.parse(sql, false)

在parseSql中我们看到会调用ddlParser方法的parse方法:

def parse(input: String, exceptionOnError: Boolean): LogicalPlan = {
  try {
    parse(input)
  } catch {
    case ddlException: DDLException => throw ddlException
    case _ if !exceptionOnError => parseQuery(input)
    case x: Throwable => throw x
  }
}

在此我们先需要知道下面的类之间的关系

spark sql 解析第一个json spark sql源码解析_spark sql 解析第一个json


那么这里调用的parse方法其实是会调用AbstractSparkSQLParser的parse方法:

def parse(input: String): LogicalPlan = synchronized {
   // Initialize the Keywords.
   initLexical
   //用lexical.Scanner针对sql语句来进行语法检查和分析,满足语法检查结果的话就是用sql解析器针对sql进行解析
   //包括词法解析,将sql语句解析成一个个的token,最后生成一个UnResolved LogicPlan
   //该LogicPlan仅仅针对SQL语句本身生成,不涉及任何关联的数据源等信息
   phrase(start)(new lexical.Scanner(input)) match {
     case Success(plan, _) => plan
     case failureOrError => sys.error(failureOrError.toString)
   }
}

在这里我们可以看到根据scala的柯里化,我们知道会调用start方法,这个start方法就是在我们的DDLParser中

protected lazy val ddl: Parser[LogicalPlan] = createTable | describeTable | refreshTable
protected def start: Parser[LogicalPlan] = ddl

protected lazy val describeTable: Parser[LogicalPlan] =
(DESCRIBE ~> opt(EXTENDED)) ~ tableIdentifier ^^ {
  case e ~ tableIdent =>
    DescribeCommand(UnresolvedRelation(tableIdent.toSeq, None), e.isDefined)
}
createTable...
...

调用start方法其实就是调用createTable,describeTable,refreshTable等方法,至此我们可以知道其实DDLParser的主要目的是处理DDL语句,主要就是建表的一些语句,那么对于那些不是ddl的sql语句该如何处理呢,显然DDLParser是无法处理的。然后它会抛出异常case _ if !exceptionOnError => parseQuery(input)那么此时会调用parseQuer方法进行处理,这个parseQuery就是在创建DDLParser时传递过来的参数,根据我们创建DDLParser的代码可以看出这个parseQuery就是SparkSQLParser.parse,根据上面的类的继承关系,可以知道这个方法也是调用的其父类的方法即AbstractSparkSQLParser类的parse方法,在这个方法中最后还是调用SparkSQLParser的start方法

override protected lazy val start: Parser[LogicalPlan] =
    cache | uncache | set | show | desc | others

当然这个类里面也会包含这些方法的实现,具体对诸如cache方法的实现我们就不深入了,重点我们看一下others的实现

private lazy val others: Parser[LogicalPlan] =
    wholeInput ^^ {
      case input => fallback(input)
    }

对于一些没有办法处理的sql关键字,我们又会调用fallback方法,那么这个fallback方法是从哪里来的呢?

private[sql] class SparkSQLParser(fallback: String => LogicalPlan) extends AbstractSparkSQLParser {}

很明显这是其构造函数的参数,也就是在创建类对象的时候传过来的,我们创建这个类的对象的代码如下

protected[sql] val sqlParser = new SparkSQLParser(getSQLDialect().parse(_))

那么很明显这个fallback方法就是getSQLDialect().parse(_)方法返回的那个方法

传入的参数是getSQLDialect().parse方法

protected[sql] def dialectClassName = if (conf.dialect == "sql") {
   classOf[DefaultParserDialect].getCanonicalName
} else {
   conf.dialect
}

protected[sql] def getSQLDialect(): ParserDialect = {
    try {
      val clazz = Utils.classForName(dialectClassName)
      clazz.newInstance().asInstanceOf[ParserDialect]
    } catch {
      ...
    }
  }

在这里我们可以看到getSQLDialect()方法返回的是一个DefaultParserDialect类,那么它传进去的就是DefaultParserDialect.parse方法

private[spark] class DefaultParserDialect extends ParserDialect {
  @transient
  protected val sqlParser = SqlParser

  override def parse(sqlText: String): LogicalPlan = {
    sqlParser.parse(sqlText)
  }
}

因此fallback其实就是sqlParse.parse,还是跟之前一样,这边还是会调用AbstarctSparkSQLParser的parse方法,最后还是SqlParser的start方法:

protected lazy val start: Parser[LogicalPlan] =
    start1 | insert | cte

protected lazy val start1: Parser[LogicalPlan] =
    (select | ("(" ~> select <~ ")")) *
    ( UNION ~ ALL        ^^^ { (q1: LogicalPlan, q2: LogicalPlan) => Union(q1, q2) }
    | INTERSECT          ^^^ { (q1: LogicalPlan, q2: LogicalPlan) => Intersect(q1, q2) }
    | EXCEPT             ^^^ { (q1: LogicalPlan, q2: LogicalPlan) => Except(q1, q2)}
    | UNION ~ DISTINCT.? ^^^ { (q1: LogicalPlan, q2: LogicalPlan) => Distinct(Union(q1, q2)) }
    )

这里要处理的就是诸如select,insert的关键字
当上面的步骤都执行完了以后,我们就对传进去的sql语句做好了初步的解析,即将调用parser这个组件将sql解析成了Unresolved Logical Plan,这个类继承了QueryPlan,而QueryPlan又继承自TreeNode,所以其实到这里就是根据语法生成了一棵树,该树一直在内存里维护,不会以某种格式保存到磁盘中,且无论是Analyzer分析过的逻计划还是Optimizer优化过的逻辑计划,树的修改是以替换现有节点的方式进行的。

其实dataFrame也是具有懒加载的特性的,也就意味着只有当遇到show,collect等需要正真的返回结果的地方才会真正地去执行sparkSql后面的流程

//实际上在后面真正对DataFrame操作的时候需要真正的去执行sql语句的时候
 // 就会触发sqlContext的executesql方法的执行
 //该方法实际上会返回一个QueryExecution,这个QueryExecution就会触发后续的整个流程
 protected[sql] def executeSql(sql: String): this.QueryExecution = executePlan(parseSql(sql))
 protected[sql] def executePlan(plan: LogicalPlan) = new this.QueryExecution(plan)

新建一个QueryExecution的对象的代码如下

@DeveloperApi
  protected[sql] class QueryExecution(val logical: LogicalPlan) {
    def assertAnalyzed(): Unit = analyzer.checkAnalysis(analyzed)

    //使用一个UnResolved LogicPlan去构造一个QueryExecution的实例对象
    //那么sql语句的设计执行就会立即一步一步的触发
    //调用analyzer来生成一个resolved LogicPlan
    lazy val analyzed: LogicalPlan = analyzer.execute(logical)

    //如果当前的这个执行计划缓存中有,那么就从缓存中读取
    lazy val withCachedData: LogicalPlan = {
      assertAnalyzed()
      cacheManager.useCachedData(analyzed)
    }

    //针对resolvedPlan调用optimizer的execute进行优化,得到优化后的optimized LogicalPlan
    //获得优化后的逻辑执行计划
    lazy val optimizedPlan: LogicalPlan = optimizer.execute(withCachedData)

    // TODO: Don't just pick the first one...
    //使用sparkPlanner根据刚刚创建的一个optimized LogicalPlan创建一个sparkplan
    lazy val sparkPlan: SparkPlan = {
      SparkPlan.currentContext.set(self)
      planner.plan(optimizedPlan).next()
    }

    /*在sparksql中,逻辑执行计划就是LogicalPlan,物理执行计划就是SparkPlan*/
    // executedPlan should not be used to initialize any SparkPlan. It should be
    // only used for execution.
    //生成一个可以执行的sparkplan,此时就是physicplan,此时就是物理执行计划
    //此时的话就已经绑定好了数据源,知各个表如何join
    //如果进行join,默认spark内部是会对小表进行广播的
    lazy val executedPlan: SparkPlan = prepareForExecution.execute(sparkPlan)

    /** Internal version of the RDD. Avoids copies and has no schema */
      //调用sparkPlan (封装了Physical plan)的execute方法,execute方法实际上就会执行物理执行计划
    lazy val toRdd: RDD[InternalRow] = executedPlan.execute()

    protected def stringOrError[A](f: => A): String =
      try f.toString catch { case e: Throwable => e.toString }

    def simpleString: String =
      s"""== Physical Plan ==
         |${stringOrError(executedPlan)}
      """.stripMargin.trim

    override def toString: String = {
      def output =
        analyzed.output.map(o => s"${o.name}: ${o.dataType.simpleString}").mkString(", ")

      s"""== Parsed Logical Plan ==
         |${stringOrError(logical)}
         |== Analyzed Logical Plan ==
         |${stringOrError(output)}
         |${stringOrError(analyzed)}
         |== Optimized Logical Plan ==
         |${stringOrError(optimizedPlan)}
         |== Physical Plan ==
         |${stringOrError(executedPlan)}
         |Code Generation: ${stringOrError(executedPlan.codegenEnabled)}
      """.stripMargin.trim
    }
  }

在创建这个对象的时候:
1. 调用analyzer组件去对上一步得到的Unresolved Logical Plan进行解析得到resolved Logical Plan,在Analyzer中会定义一系列的Rules用来解析这个Logical Plan
2. 调用optimizer组件对上一步得到的Resolved Logical Plan进行优化,这一步非常重要,了解其中的一些规则我们就可以按照它的优化策略写出合适的sql语句,减少sql优化的过程,提高性能
3. 根据上一步中创建的Optimized Logical Plan创建一个SparkPlan即物理计划,这边就是可以正真执行的了
4. 调用SparkPlan的execute方法去执行物理计划