一、执行计划实体相关
Tree是Catalyst执行计划表示的数据结构。LogicalPlans,Expressions和Pysical Operators都可以使用Tree来表示。Tree具备一些Scala Collection的操作能力和树遍历能力。
Tree提供三种特质:
- UnaryNode:一元节点,即只有一个子节点
- BinaryNode:二元节点,即有左右子节点的二叉节点
- LeafNode:叶子节点,没有子节点的节点
针对不同的节点,Tree提供了不同的操作方法。对Tree的遍历,主要是通过迭代将Rule应用到该节点以及子节点。
Tree有两个子类继承体系,即QueryPlan和Expression
- 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三类。
- 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注册中心)将
UnresolvedAttribute和
UnresolvedRelation转换为一个完全类型对象
下方的Object基本都是Rule对象,这里可以暂时忽略。
2.3.1 Analyzer
Analyzer继承于RuleExecutor, Analyzer中的rule较多,这里就不一一分析了。
Analyzer的一个主要特性是,它会依赖于SessionCatalog来进行一些rule操作
SessionCatalog主要在下列几个Rule中的方法中使用到了,通过方法名我们就能大概了解SessionCatalog在analyze过程中的作用
- ResolveRelations.lookupTableFromCatalog
- ResolveRelations.isRunningDirectlyOnFiles
- LookupFunctions.apply
- 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个成员
- logical : LogicalPlan Unresolved Logical Plan
- analyzed : LogicalPlan
analyzed是QueryExecution通过SparkSession获取到Analyzer对Unresolved LogicalPlan进行元数据绑定生成的Resolved LogicalPlan
lazy val
analyzed: LogicalPlan = {
SparkSession.setActiveSession(sparkSession)
sparkSession.
sessionState.analyzer.execute(logical)
}
- withCachedData : LogicalPlan
withCachedData是基于已进行元数据绑定的Resolved LogicalPlan,从SparkSession中的CacheManager
(???)中进行一个关于缓存的规则转换后的Resolved LogicalPlan
lazy val
withCachedData: LogicalPlan = {
assertAnalyzed()
assertSupported()
sparkSession.
sharedState.
cacheManager.useCachedData(
analyzed)
}
- optimizedPlan : LogicalPlan
optimizedPlan是基于withCachedData,通过获取SparkSession中的Optimizer进行逻辑计划的剪枝等优化操作后生产的执行计划
lazy val
optimizedPlan: LogicalPlan = sparkSession.
sessionState.optimizer.execute(
withCachedData)
- 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()
}
- 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。
- 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