文章目录
Spark Shuffle
在 Shuffle 过程中,提供数据的称为 Map端 (shuffle write),接收数据的称为 Reduce端 (shuffle read)
在 Spark 的相邻两个 stage 中,总是前一个阶段产生一批 Map 提供数据,下一阶段产生一批 Reduce 接收数据
Spark 提供 2种 Shuffle 管理器:HashShuffleManager & SortShuffleManager
1. HashShuffleManager
每个 Task 按照相同的 hash 规则对自己处理的数据进行分组,存储在 Buffer 缓冲区内,然后落盘;
经由网络将 hash 值相同的数据发送到对应的接收端的分区中
上述为经典的HashShuffle流程,可以看到过程中产生了大量的block块和shuffle,会影响Spark的性能,可以对其进行简单的优化:
在一个Executor内,不同的 Task 共享Buffer缓冲区,在内存中进行数据归并,这样就减少了缓冲区乃至落盘的文件数量,并行度相同的情况下减少了网络IO次数,提高了性能
2. SortShuffleManager
尽管优化后的 HashShuffleManager 减少了文件数量,但是当任务过多时文件数量依然很庞大,怎样进一步减少文件数量呢?这时就诞生了 SortShuffleManager
SortShuffleManager 有两种运行机制:普通运行机制 和 bypass运行机制
- 普通运行机制下,
数据会在内存中暂缓。当数据量达到阈值后,会对数据进行分批排序(为了生成索引,便于后续拉取数据),然后写入内存缓冲区中。缓冲区中数据达到一定数量后落盘(将多个文件合并成一个后落盘);
接收端会主动拉取数据。落盘后的每个文件会发给多个Reduce端,每个Reduce通过数据索引文件得到所需数据在文件中的始末位置,从而拉取所需数据
- bypass运行机制下,
相比于普通机制不进行排序步骤,进一步减少性能开销。
bypass运行机制的触发需要一定的条件:
①map task数量少于设置的参数spark.shuffle.sort.bypassMergeThreshold=200
②当前shuffle不是聚合类型的shuffle(如groupByKey)
总结
(bypass)SortShuffle = HashShuffle + 文件合并
(普通)SortShuffle = HashShuffle + 排序 + 文件合并
文件合并可以减少文件数量,从而减少磁盘IO,SortShuffle 性能要优于 HashShuffle,
遇到 Shuffle 必落盘,尽管 Spark 是内存迭代框架,但是内存迭代主要在窄依赖中,宽依赖中的磁盘交互是无法避免的,因此要尽量减少Shuffle
Spark 3.0 新特性
自适应查询 AQE(Adaptive Query Execution)
优于缺乏或不准确的数据统计信息(元数据)和对成本的错误估算(执行计划调度)导致生成的初始执行计划不理想(在运行时才能发现)
Spark 3.x版本通过在“运行时”对查询执行计划进行优化,允许Planner在运行时执行可选计划,这些可选计划将会基于运行时数据统计进行动态优化,从而提高性能
AQE 开启方式:通过设置相应参数为true(spark配置文件中设置 或 客户端设置临时参数)
set spark.sql.adaptive.enabled = true;
AQE 主要提供三个自适应优化:
- 动态合并 Shuffle Partitions
- 动态调整 Join 策略
- 动态优化倾斜 Join
动态合并 Shuffle Partitions
运行时动态调整 shuffle 分区数量,一开始可以设置相对较多的shuffle分区数,AQE 会在运行时将相邻的小分区合并为较大的分区
可以看到,有三个分区的数据量要明显小于其余分区,因此将这三个分区合并
动态调整 Join 策略
有时错误估计大小会导致执行计划性能不佳,如两表 join,预估大小相似,但实际上有一个表很小(filler等算子会减小表),这样使用默认的 join 策略效率就会不高,Spark 会在运行时将sort merge join 转换为 broadcast hash join,广播小表避免大量的网络交互,进一步提升效率
动态优化倾斜 Join
数据倾斜会导致负载不均衡。AQE 从 Shuffle 文件统计信息中检测到倾斜后,会将分区分割为更小的分区,并将他们与另一侧的相应分区连接起来。
触发条件:同时满足
①分区大小 > 所有分区大小中位数 * spark.sql.adaptive.skeewedPartitionFactor(default=10)
②分区大小 > spark.sql.adaptive.skeewedPartitionThresholdInBytes(default=256MB)
动态分区裁剪
列值裁剪是一种静态裁剪,在运行之前确定不读取哪些列的内容,减小数据量提升性能
那能不能进一步过滤无用数据,提升查询性能呢?这就是动态分区裁剪
动态分区裁剪是基于运行时推断出来的信息额外地对表进行分区裁剪,举个栗子:现有如下查询
select *
from dim_iteblog join fact_iteblog
on dim_iteblog.partcol = fact_iteblog.partcol
where dim_iteblog.othercol > 10
可以看到,根据SQL,在 join 之前先对 dim 表进行过滤,可以提升性能。
但是对dim表的过滤会导致 fact 的 partcol 字段中的某些值不可能满足 join 条件,因此在运行时对 fact 表基于 partcol 字段也进行一次过滤,只读取可以满足 join 条件的数据,减少读取数据量,提升性能
通过动态的谓词下推来获取传统静态谓词下推无法获得的更高过滤属性,减少操作的分区数据量,提高性能
增强的 Python API:PySpark & Koalas
Python 是目前 Spark 中使用较为广泛的编程语言,因此也是Spark3.0 的重点关注领域
很多开发人员使用pandas API 进行数据处理,但是该API仅限于单节点数据处理,无法进行分布式计算
因此 Spark 公司开发了 Koalas—基于 Spark 的 Pandas API实现,使得 Pandas API 的底层以分布式的方式计算
因此开发者可以使用 PySpark 库,调用原生的 Spark API,创建 DataFrame 进行开发;也可以用 Koalas 库,通过 Pandas 的 API 完成数据处理