Spark 3 AQE (Adaptive Query Execution)

序
在搭建平台的过程中,我们使用 CDH 6.3.2 进行搭建,但 CDH 中阉割掉了 spark-sql 功能,所以我们外挂了 Spark 3,补充 spark-sql 功能,版本为 3.3。在使用的过程中,查看 sql 执行图,发现了一个以前没有发现的 AQE 功能。所以今天我们就来聊一聊关于 AQE 这个 Spark 新特性(Hadoop3 也采用了这一个思路)。
AQE Adaptive Query Execution 原理 代码 讲解
AQE 面对的场景
在我们大型分布式计算框架中,遇到问题最多的就是对是否 shuffle、切块切片、谓词没有下推、数据量倾斜、小文件等等问题。而产生这些问题的根本在于解析 sql 时就已经决定了整个任务的执行规划,但没有考虑到真实的数据场景。因为开发人员也不可能每次都去实地考察每一次运行的 sql 环境,并且做出对应的参数调整。
比如以下一些任务场景:
数据分区不均衡
某类数据白天数据量很大,晚上数据量很小,白天每个文件可能都很大,但晚上的文件很小。如果我们配置了调度运行这个任务,那么可能我们的 spark.sql.shuffle.partition 是固定的,这样就决定了我们 reduce 任务的数量。那么造成白天 reduce 数量少,造成 OOM;而晚上的 reduce 数量又多,造成小文件的产生。
执行计划总是不最优的
一般我们的 CBO 是通过 Hive Metastore 的记录来进行优化策略调整,但这种策略也是静态策略,一旦开始执行就无法进行更改。
比如:一张大表 A join 一张大表 B,但其实表 B 经过过滤后,是可以 broadcast 进行关联的,但还是只能停留使用 sort merge join,造成大量的 shuffle,降低了查询性能。
热点 Key 问题
总有一些 top 流量是远远超过其他的,所以如果我们恰好使用这些热点 Key 来进行一些排序、分区,那么 shuffle 就会使得某一个节点造成数据倾斜。
一般对于这种,我们可能会考虑手动过滤,或者 加盐 处理,但并不是所有的 Key 我们都可以手动处理的。
AQE 是什么
上述的问题,其实都是关于真实运行数据时遇到的问题,而处理这些问题,大多数并不能在提交任务之前进行优化,而是基于运行过程中,产生的数据进行优化。所以, Spark 社区在 DAGScheduler 中,新增了一个 API 在支持提交单个 map 阶段,以及在运行时修改 shuffle 分区数等等,而这些就是 AQE。
在 spark 运行时,每当一个 shuffle map 阶段进行完毕,AQE 就会统计这个阶段的信息,并且基于规则进行动态调整并修正还未执行的任务逻辑计算与物理计划(在条件运行的情况下),使得 spark 程序在接下来的运行过程中得到优化。
所以,和 CBO 不同, AQE 是通过 shuffle map 中间阶段的输出文件信息进行调整。
在运行 spark 程序时,每个 Map Task 都会产出很多以 data 为后缀的数据文件,和以 index 结尾的索引文件。每个 data 的文件大小、空文件数量与占比、每个 reduce task 对应分区大小。A QE 就获取这些来进行相关优化。
与 RBO 也不同,RBO 是根据 sql 表象来进行一些经验主义的优化,如谓词下推,列剪枝等等。
后面会讲到 AQE 也会有谓词下推的优化,但一个是根据 SQL 表象(通过 sql 即可获取到的信息),一个是根据实际数据情况需要(需要部分运算后才能得到的信息)。
AQE 配置
在配置中,开启参数 spark.sql.adaptive.enabled=true,在 spark3.3 实际使用中,已经是默认开启(但 spark3.0 是 false)。
原始 Spark sql 解析以及任务提交流程
下图是描述 spark sql 的内部结构,一般来说物理计划是解析 sql 得到。
包含目录解析,基于规则的常量折叠、谓词和计算下推,转换为多个 spark 算子(一般会有多个,然后选择成本最低的)等基础优化。

比如这个 SQL 的解析:
select a1, avg(b1)
from A
join B on A.pk = B.pk
where b1 <= 10000
group by a1
;
获取将 A 表和 B 表的 pk 进行关联,并且 B 表有一个筛选,A 表字段聚合。那么他的 SQL 逻辑计划如:
观察该图应该从叶节点到根节点(倒过来看),只有当下层执行完毕才会执行上层任务。

那么我们还需要将逻辑计划转变为物理计划,比如 join 操作就可能是多种中的一种:

当物理计划一旦确定,那么在运行时就不能够更改,这里选择 SortMergeJoin 就是可能因为 A 表和 B 表都是很大的表(但可能其实过滤条件将大表已经化小了,如前面提到的场景 2)。物理计划确定后,就会形成 DAG 图,使用 RDD 进行计算,并且有对应的依赖关系(宽窄依赖)。最后会提交给 DAGScheduler 进行分解,然后传递 TaskSet 在 TaskScheduler 负责的物理资源上执行。
AQE 基于任务统计信息进行任务修订
在没有 AQE 时,spark 会在确定了物理执行计划之后,根据每个算子定义生成 RDD 以及对应的 DAG,然后 spark DAGScheduler 通过 shuffle 来划分 RDD Graph,并以此创建每个 stage,以及挨个执行 stage。
在开启 AQE 时,会将逻辑计划拆分为 QueryStage 独立的子图,更早的拆分 Stage。通过单独提交 mapStage(与 Stage 不同),收集它们的 MapOutputStatistics 对象。
在 AQE 的 plan 中,定义了两种类型的 QueryStage:
-
Shuffle query stages
将输出物化为 Shuffle 文件。
-
Broadcast query stages
将输出物化到 Driver 内存中的数组。
所以如果要支持这个新特性,必须依靠上面的 DAGSchedule

介绍Spark3中Adaptive Query Execution(AQE)的新特性,包括动态合并分区、Join策略调整及数据倾斜优化等内容。
最低0.47元/天 解锁文章
3897

被折叠的 条评论
为什么被折叠?



