SparkSQL源码分析-01-SparkSQL执行流程总览

481 阅读2分钟

工作中一直用到sparkSQL,但是对其底层实现不是很清楚,这里通过学习相关源码信息来加深对Spark的相关理解。这里以spark-3.2.1的源码来学习

第一篇,我们先从整体SparkSQL的执行流程介绍来有个整体的概念

1.执行流程概述

sql在转换为RDD执行中会经过如下几个阶段

image.png

词法分析:将输入的SQL语句拆解为单词序列,并识别出关键字、标识、常量等信息;

语法分析:检查词法分析解析出来的单词序列是否满足SQL语法规则;

逻辑计划:语法分析完的结果会生成原始的逻辑计划信息,在这个阶段会进行分析和进行优化处理,具体会分为如下几个阶段

image.png

物理计划:物理计划会将上一步的逻辑计划进一步转换,生成可以执行的计划信息

image.png

2.代码分析

这里我们通过example例子来入手

val spark = SparkSession.builder().appName("Spark SQL basic example").config("spark.some.config.option", "some-value").getOrCreate()
val df = spark.read.json("examples/src/main/resources/people.json")
df.createOrReplaceTempView("people")
val sqlDF = spark.sql("SELECT * FROM people")

2.1 各个阶段转换的核心类

SQL各个阶段转换处理使用的类信息都在SessionState(SparkSession中的sessionState)中,我们看看SessionState的定义

private[sql] class SessionState(
    ...
    val sqlParser: ParserInterface,
    analyzerBuilder: () => Analyzer,
    optimizerBuilder: () => Optimizer,
    val planner: SparkPlanner,
    ...
   )

SparkSqlParser:ParserInterface的子类,处理SQL的词法和语法分析过程;

Analyzer:通过提供的Catalog信息,给逻辑计划的各节点绑定各种信息,处理后为分析后的逻辑计划(analyzed logicalPlan);

Optimizer:按照定义的规则对一些低效的逻辑计划进行转换;

2.2 代码流程

def sql(sqlText: String): DataFrame = withActive {
  val tracker = new QueryPlanningTracker
  val plan = tracker.measurePhase(QueryPlanningTracker.PARSING) {
sessionState.sqlParser.parsePlan(sqlText)
  }
  Dataset.ofRows(self, plan, tracker)
}
  1. 从SparkSession的sql方法入口,这里调用sqlParser.parsePlan来生成了逻辑计划

  2. 从Dataset.ofRows里面,在新建QueryExecution实例,逻辑计划和物理计划的处理都在该类里面处理的。各个阶段的结果分别对应到QueryExecution类中的

    • analyzed
    • optimizedPlan
    • sparkPlan
    • executedPlan

Tips:在处理的各个阶段有通过QueryPlanningTracker类来跟踪具体过程,所以可以根据该类来快速定位具体处理过程的代码

object QueryPlanningTracker {

  // Define a list of common phases here.
  valPARSING= "parsing"
  valANALYSIS= "analysis"
  valOPTIMIZATION= "optimization"
  valPLANNING= "planning"

2.3执行计划查看

前面spark.sql()返回的是一个DataFrame对象,而DataFrame是个一种特殊的DataSet(DataSet[Row]),DataSet中的explain方法可以查看相应的执行计划信息,另外通过DataSet的queryExecution属性可以直接访问各个具体的计划对象。

scala> sqlDF.explain("extended");
== Parsed Logical Plan ==
'Project [*]
+- 'UnresolvedRelation [people], [], false

== Analyzed Logical Plan ==
age: bigint, name: string
Project [age#7L, name#8]
+- SubqueryAlias people
   +- View (`people`, [age#7L,name#8])
      +- Relation [age#7L,name#8] json

== Optimized Logical Plan ==
Relation [age#7L,name#8] json

== Physical Plan ==
FileScan json [age#7L,name#8] Batched: false, DataFilters: [], Format: JSON, Location: InMemoryFileIndex(1 paths)[file:/Users/liuj/opt/spark-3.2.2-bin-hadoop3.2/examples/src/main/resou..., PartitionFilters: [], PushedFilters: [], ReadSchema: struct<age:bigint,name:string>

直接使用SQL的话通过如下语句也可以查看执行计划

EXPLAIN EXTENDED select * from people;