一、概述

物理计划阶段是 Spark SQL 整个查询处理流程的最后一步。不同于逻辑计划(LogicalPlan)的平台无关性,物理计划(PhysicalPlan) 是与底层平台紧密相关的。在此阶段,Spark SQL 会对生成的逻辑算子树进行进一步处理,得到物理算子树,并将 LogicalPlan 节点及其所包含的各种信息映射成 Spark Core 计算模型的元素,如 RDD、 Transformation 和 Action 等,以支持其提交执行。

在 SparkSQL 中,物理计划用 SparkPlan 表示,Spark SQL,最终将 SQL 语句经过逻辑算子树转换成物理算子树。在物理算子树中,叶子类型的 SparkPlan 节点负责 “从无到有” 地创建 RDD,每个非叶子类型的 SparkPlan 节点等价于在 RDD 上进行一次 Transformation,即通过调用 $execute$ 函数转换成新的 RDD,最终执行 $collect$ 操作触发计算,返回结果给用户。

二、架构设计

Plan 模块主要经过 3 个阶段:

  1. 由 SparkPlanner 将各种物理计划策略(Strategy))作用于对应的 LogicalPlan 节点上,生成 SparkPlan 列表

    一个 LogicalPlan 可能产生多种 SparkPlan

  2. 选取最佳的 SparkPlan

    在 Spark 3.3 版本中的实现较为简单,在候选列表中直接用 $next$ 方法获取第一个。

  3. 提交前进行准备工作,进行一些分区排序方面的处理,确保 SparkPlan 各节点能够正确执行,这一步通过 $prepareForExecution$ 方法调用若干规则(Rule)进行转换。

2.1. 生成 SparkPlanner

在 Spark SQL 中,当逻辑计划处理完毕后,会构造 SparkPlanner 并执行 $plan$ 方法对 LogicalPlan 进行处理,得到对应的物理计划。

一个逻辑计划可能会对应多个物理计划,因此,SparkPlanner 得到的是一个物理计划的列表 (Iterator[SparkPlan])。

类似逻辑计划阶段的 Anaylzer 和 Optimizer, SparkPlanner 本身只是一个逻辑的驱动,各种 stragety 的 $apply$ 方法把逻辑执行计划算子映射成物理执行计划算子。

在 SparkPlanner 的调用逻辑和各种策略中,PlanLater 随处可见。根据其实现,PlanLater 本身也是 SparkPlan 的一种,区别在于
$doExecute$ 方法没有实现,表示不支持执行,所起到的作用仅仅是占位,等待后续步骤处理。

2.2. prunePlans()

Spark SQL 对生成的物理计划列表进行过滤筛选($prunePlans()$ )

在当前版本中并没有实现,生成多个物理计划后,仅仅是直接选取列表中的第一个作为最终结果

2.3. prepareForExecution()

物理计划的生成意味着用户的 SQL 语句已经成功转换为 SparkPlan 物理算子树。然而,在通常情况下,到了这一步仍然不能直接提交给 Spark 系统执行。类似人工直接编写代码,应用提交前有必要从 Spark 系统本身的角度来考虑代码的正确性和高效性。因此,得到 SparkPlan 之后,还需要完成若干的准备工作,对树型结构的物理计划进行全局的整合处理或优化。

在 QueryExection 中,最后阶段由 $prepareforExecution$ 方法对传入的 SparkPlan 进行处理而生成 executedPlan,处理过程仍然基于若干规则。