Spark-Sql源码简单走读

简述

自从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 =>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值