Spark SQL核心类解析

一、执行计划实体相关

     Tree是Catalyst执行计划表示的数据结构。LogicalPlans,Expressions和Pysical Operators都可以使用Tree来表示。Tree具备一些Scala Collection的操作能力和树遍历能力。

    Tree提供三种特质:

  1. UnaryNode:一元节点,即只有一个子节点
  2. BinaryNode:二元节点,即有左右子节点的二叉节点
  3. LeafNode:叶子节点,没有子节点的节点
    针对不同的节点,Tree提供了不同的操作方法。对Tree的遍历,主要是通过迭代将Rule应用到该节点以及子节点。
     Tree有两个子类继承体系,即QueryPlan和Expression
  1. QueryPlan下面的两个子类分别是LogicalPlan(逻辑执行计划)和SparkPlan(物理执行计划)。QueryPlan内部带有output:Seq[Attribute]、transformExpressionDown和transformExpressionUp等方法,它的主要子体系是LogicalPlan,即逻辑执行计划表示,它在Catalyst优化器里有详细实现。LogicalPlan内部带一个reference:Set[Attribute],主要方法为resolve(name:String): Option[NamedExpression],用于分析生成对应的NamedExpression。对于SparkPlan,即物理执行计划表示,需要用户在系统中自己实现。LogicalPlan本身也有很多具体子类,也分为UnaryNode,BinaryNode和LeafNode三类。
  2. Expression是表达式体系,是指不需要执行引擎计算,而可以直接计算或处理的节点,包括Cast操作、Porjection操作、四则运算和逻辑操作符运算等等。

1.1 LogicalPlan实现/继承关系

1.1.1 LeafNode


LeafNode是一个没有子节点的logicalplan
LeafNode下有一个实现类LocalRelation和一个Command 

1.1.1.1  LocalRelation

1.1.1.2  Command

     Command 是由系统执行的一个非查询命令,Command只有一个实现trait  RunnableCommand
1.1.1.2.1  RunnableCommand

     RunnableCommand是一个会造成一些作用影响的逻辑命令,RunnableCommand有57个实现类,分别实现了不同作用的运行操作。

    command下区分作用目的主要分为以下几个方向 


Command类型 说明
列分析 分析给定表的给定列来生成统计数据,用于查询优化
表分析 分析给定表生成统计数据,用于查询优化
缓存相关 表级别的缓存、清楚操作
命令相关 执行、explain
Datasource相关 使用查询结果来创建一个Datasource供之后的查询使用
Database相关 show database  、   set database
DDL alter  create  drop 等DDL操作
函数function相关 create desc  drop  show  function   类似注册UDF函数
资源resource相关 AddJar AddFile ListJar  ListFile
setCommand 设置属性、重置属性
table相关 alter create show truncate table
view相关 alter create 视图

1.1.2  UnaryNode

     UnaryNode是一元节点,是只有一个子节点的logicalPlan,  包括聚合、采样、过滤、grouping sets等操作(union直接继承LogicalPlan),这里就不再一一枚举

1.1.3  BinaryNode

     BinaryNode是一个区分左右节点的LogicalPlan,实现类较少


实现类 子类 描述
CoGroup   cogroup:对两个RDD中的KV元素,每个RDD中相同key中的元素分别聚合成一个集合。与reduceByKey不同的是针对两个RDD中相同的key的元素进行合并。
SetOperation Except 返回一个dataframe,返回在当前集合存在的在其他集合不存在的
  Intersect  
Join   Join操作
OrderedJoin   一个和join节点类似的操作,排序join
TestBinaryRelation   ???

1.2 SparkPlan实现/继承关系


    SparkPlan是物理操作的基础类,SparkPlan有众多具体实现子类,代表着物理实现时的具体操作。
    SparkPlan类在spark.sql.core下的org.apache.spark.sql.execution包中,同LogicalPlan一样继承自QueryPlan
    SparkPlan根据操作的子节点类型主要分为三种trait:BinaryExecNode、LeafExecNode和UnaryExecNode

1.2.1  LeafExecNode

     LeafExecNode是SparkPlan中的叶子节点,下有13个实现子类。主要功能是加载数据


实现类 子类 描述
DataSourceScanExec  FileSourceScanExec 从HadoopFileRelations中scan数据
RowDataSourceScanExec 从一个relation中scan数据
ExternalRDDScanExec   从RDD中加载数据
HiveTableScanExec   Hive表scan操作,支持列和partition裁剪
InMemoryTableScanExec   从内存中scan数据
LocalTableScanExec   从本地collection中scan数据
MyPlan   伪SparkPlan,主要用于更新driver统计数据
PlanLater    
RDDScanExec   scan内部RDD的row
RangeExec   生成一个64bit 数据的范围操作
ReusedExchangeExec    
StreamingRelationExec   伪操作


1.2.2  UnaryExecNode

    UnaryExecNode下有40个实现子类,主要是一些聚合过滤等一元操作

1.2.3  BinaryExecNode

    BinaryExecNode主要是针对RDD的一些复杂操作,实现类较少


实现类 描述
BroadcastHashJoinExec  
BroadcastNestedLoopJoinExec  
CartesianProductExec  
CoGroupExec  
ShuffledHashJoinExec  
SortMergeJoinExec  

二、执行计划流转相关

2.1 Rule


    Rule是一个抽象泛型类,泛型上界是TreeNode
Rule[TreeType <: TreeNode[_]]是一个抽象类,子类需要复写apply(plan: TreeType)方法来指定处理逻辑。 于Rule的具体实现是通过RuleExecutor完成的,凡是需要处理执行计划树进行实施规则匹配和节点处理的,都需要继承RuleExecutor[TreeType]抽象类。
    在RuleExecutor的实现子类(如Analyzer和Optimizer)中会定义Batch,Once和FixedPoint。其中每个Batch代表着一套规则,这样可以简便地、模块化地对Tree进行Transform操作。Once和FixedPoint是配备策略。RuleExecutor内部有一个Seq[Batch]属性,定义的是该RuleExecutor的处理逻辑,具体的处理逻辑由具体的Rule子类实现。RuleExecutor中的apply方法会按照Batch顺序和Batch内的Rules顺序,对传入的节点进行迭代操作。
    在Analyzer过程中处理由解析器(SqlParser)生成的未绑定逻辑计划Tree时,就定义了多种Rules应用到该Unresolved逻辑计划Tree上。
    Analyzer过程中使用了自身定义的多个Batch,如MultiInstanceRelations,Resolution,CheckAnalysis和AnalysisOperators:每个Batch又由不同的Rules构成,每个Rule又有自己相对应的处理函数。注意,不同Rule的使用次数不同(Once FixedPoint)。 

2.2  RuleExecutor


    对 于Rule的具体实现是通过RuleExecutor完成的,凡是需要处理执行计划树进行实施规则匹配和节点处理的,都需要继承RuleExecutor[TreeType]抽象类。
    RuleExecutor和Rule一样是一个抽象泛型类,泛型上界同样是TreeNode 


    RuleExecutor中的Strategy是一个内部抽象类,它的子类有两个,Once和FiexPoint,它们的区别在于最大迭代次数maxIterations是1还是一个具体数值。
    batches是一个Batch的的seq,其中每一个Batch针对一种或多种Rule定义了一种处理策略。
    RuleExecutor的核心方法是execute,所有RuleExecutor的execute方法都是一致的,它的子类只会重新定义batches和rules,batch是顺序执行的,一个batch中的rule也是顺序执行的。
    execute的逻辑比较简单,主要是调用Rule实现类中的apply方法,并根据最大迭代次数以及apply前后plan的一致性来进行迭代操作。


    RuleExecutor的实现类有很多,但大部分都是测试类中创建的内部类,我们可以主要关注它的Analyzer和Optimizer实现类.
    Analyzer和Optimizer定义了不同的Batch,Batch中定义了不同的Rule,使得它们在执行计划流转过程中承担了不同的角色。

2.3  Analyzer


    Analyzer是一个逻辑计划分析器,位于catalyst下的analysis包中。
    Analyzer通过SessionCatalog和FunctionRegistry(相当于是一个UDF注册中心)将 UnresolvedAttributeUnresolvedRelation转换为一个完全类型对象
下方的Object基本都是Rule对象,这里可以暂时忽略。

2.3.1  Analyzer


    Analyzer继承于RuleExecutor, Analyzer中的rule较多,这里就不一一分析了。
    Analyzer的一个主要特性是,它会依赖于SessionCatalog来进行一些rule操作


    SessionCatalog主要在下列几个Rule中的方法中使用到了,通过方法名我们就能大概了解SessionCatalog在analyze过程中的作用
  1. ResolveRelations.lookupTableFromCatalog
  2. ResolveRelations.isRunningDirectlyOnFiles
  3. LookupFunctions.apply
  4. ResolveFunctions.apply


2.3.2 SimpleAnalyzer

  SimpleAnalyzer继承了Analyzer,并且构造了一个伪SessionCatalog和一个空的EmptyFunctionRegistry。用于在relations已经被解析完毕,analyzer只需要去解析属性引用时的测试(测试???)

2.4 Optimizer

    Optimizer是一个抽象类,定义了一些标准的rule,所有的子类都需要继承它的rule。同时Optimizer也维护了SessionCatalog的引用。整体逻辑和Analyzer类似,其中的Rule实现类不同。

2.5 SparkPlanner

     SparkPlanner主要功能是执行从逻辑执行计划到物理执行计划的转换。它在第三章的QueryExecution中被调用。


    SparkPlanner类中执行主要操作的方法是plan,它接收一个被指定逻辑执行计划ReturnAnswer封装的optimizedPlan
    SparkPlanner中的strategy是固定的几类,每个strategy的apply方法接收LogicalPlan作为参数,输出一系列的SparkPlan

三、执行计划执行相关

3.1  QueryExecution


/**
* The primary workflow for executing relational queries using Spark.  Designed to allow easy
* access to the intermediate phases of query execution for developers.
*
* While this is not a public class, we should avoid changing the function names for the sake of
* changing them, because a lot of developers use the feature for debugging.
*/
    QueryExecution是Spark用来执行关系型查询的主要工作流。它是被设计用来为开发人员提供更方便的对查询执行中间阶段的访问。
    虽然它不是一个public class,我们还是需要避免为了更改它们而更改函数名,因为需要开发人员都在使用这个特性来进行debug。
    我们可以具体看一下QueryExecution中提供的方法和成员,如下图。QueryExecution中最重要的是成员变量,它的成员变量几乎都是lazy variable,它的方法大部分是为了提供给这些lazy variable去调用的。
另外,QueryExecution在初始化时还接收了一个成员变量logical : LogicalPlan
    QueryExecution类中共有有7个成员
  1. logical : LogicalPlan  Unresolved Logical Plan
  2. analyzed : LogicalPlan  
        analyzed是QueryExecution通过SparkSession获取到Analyzer对Unresolved LogicalPlan进行元数据绑定生成的Resolved LogicalPlan
lazy val  analyzed: LogicalPlan = {
  SparkSession.setActiveSession(sparkSession)
  sparkSession. sessionState.analyzer.execute(logical)
}
  1. withCachedData : LogicalPlan
withCachedData是基于已进行元数据绑定的Resolved LogicalPlan,从SparkSession中的CacheManager (???)中进行一个关于缓存的规则转换后的Resolved LogicalPlan
        
lazy val  withCachedData: LogicalPlan = {
  assertAnalyzed()
  assertSupported()
  sparkSession. sharedState. cacheManager.useCachedData( analyzed)
}
  1. optimizedPlan : LogicalPlan 
optimizedPlan是基于withCachedData,通过获取SparkSession中的Optimizer进行逻辑计划的剪枝等优化操作后生产的执行计划
lazy val  optimizedPlan: LogicalPlan = sparkSession. sessionState.optimizer.execute( withCachedData)
    
  1. sparkPlan : SparkPlan
SparkPlan是将已经进行过优化的逻辑执行计划进行转换而得到的物理执行计划SparkPlan
逻辑执行计划可以转换为多个物理执行计划,Spark目前只取了第一个物理执行计划,将来会实现自动选择最优的执行计划 (???和之前看的资料不符,是否只是在这一处地方进行的处理???)
lazy val  sparkPlan: SparkPlan = {
  SparkSession.setActiveSession(sparkSession)
   // TODO: We use next(), i.e. take the first plan returned by the planner, here for now,
  //       but we will implement to choose the best plan.
  planner.plan(ReturnAnswer( optimizedPlan)).next()
}
  1. executedPlan : SparkPlan   execution是在SparkPlan生成以后进行额外的处理生成新的SparkPlan,execution不该用来去初始化任何SparkPlan
// executedPlan should not be used to initialize any SparkPlan. It should be
// only used for execution.
在SparkPlan中插入Shuffle的操作 (???) ,如果前后2个SparkPlan的outputPartitioning不一样的话,则中间需要插入Shuffle的动作,比分说聚合函数,先局部聚合,然后全局聚合,局部聚合和全局聚合的分区规则是不一样的,中间需要进行一次Shuffle。
  1. toRDD : RDD   toRDD是这个QueryExecution最后生成的RDD
/**
* Prepares a planned [[ SparkPlan]]  for execution by inserting shuffle operations and internal
* row format conversions as needed.
*/
protected def prepareForExecution(plan: SparkPlan): SparkPlan = {
  preparations.foldLeft(plan) {  case (sp, rule) => rule.apply(sp) }
}
注意,QueryExecution中的所有成员变量都是lazy的,除了初始化的Unresolved Plan :logical 


你可能感兴趣的:(spark学习)