Shuffle Write阶段特别慢,磁盘IO打满了——Shuffle原理和优化
先说结论
Shuffle慢的核心原因是:磁盘IO + 网络IO。Map端要把数据写磁盘(Shuffle Write),Reduce端要通过网络拉取数据(Shuffle Read)。优化的核心就是:减少Shuffle数据量,或者干脆避免Shuffle。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
Shuffle是Spark里最“贵”的操作。每次看到作业跑得慢,打开Spark UI看到Shuffle Write/Read数据量巨大,心里就知道问题在哪了。这道题面试官不仅要你说“Shuffle慢”,还要你说清楚“为什么慢、慢在哪一步、怎么优化”。
Shuffle的完整过程
一次Shuffle分为两个阶段:
Shuffle Write(Map端): 每个Map Task把输出数据按目标分区号排序,写入本地磁盘。生成两个文件:数据文件(.data)和索引文件(.index)。
Shuffle Read(Reduce端): 每个Reduce Task通过网线从所有Map Task的输出中拉取属于自己分区的数据。拉取后在内存中排序或聚合,内存不够就溢出到磁盘。
举个例子:假设有200个Map Task和100个Reduce Task,那么每个Reduce Task要从200个Map Task各拉取一份数据,总共产生200×100=20000次网络请求。这就是为什么Shuffle慢的核心原因。
哪些操作会触发Shuffle?
| 操作类型 | 典型操作 | 是否Shuffle |
|---|---|---|
| 分组聚合 | groupByKey, reduceByKey, groupBy | ✔ 是 |
| Join | join, leftJoin(非Broadcast) | ✔ 是 |
| 重分区 | repartition, coalesce(加大分区时) | ✔ 是 |
| 排序 | sortByKey, orderBy | ✔ 是 |
| 去重 | distinct, dropDuplicates | ✔ 是 |
| 映射/过滤 | map, filter, select, where | ✖ 否 |
5种实战优化手段
① 用reduceByKey代替groupByKey
这是最经典的优化。groupByKey把所有数据先Shuffle再聚合,reduceByKey先在Map端做局部聚合再 Shuffle,传输的数据量大幅减少。
// ✖ 慢:所有数据先Shuffle再聚合
rdd.groupByKey().mapValues(_.sum)
// ✔ 快:先Map端局部聚合,再Shuffle
rdd.reduceByKey(_ + _)
// DataFrame版本自动优化,不需要手动选择
df.groupBy("key").agg(sum("value"))
② 用Broadcast Join避免Shuffle
上一篇已经详细讲过。小表广播后,大表不需要Shuffle,直接在本地Join。
③ 调整shuffle.partitions
-- 默认200,对于大数据量可能太少,导致每个分区数据量过大
SET spark.sql.shuffle.partitions = 400;
-- 或者开启AQE自动调整(推荐)
SET spark.sql.adaptive.enabled = true;
SET spark.sql.adaptive.coalescePartitions.enabled = true;
-- AQE会根据实际数据量自动合并过小的分区
④ 减少Shuffle写磁盘的数据量
在Shuffle前做filter和select,只保留需要的列和行,减少传输量。这个看起来简单,但很多人写SQL时习惯性地SELECT *,把不需要的列也带过去了。
⑤ 开启Shuffle压缩
// 开启Shuffle数据压缩,减少磁盘IO和网络传输
spark.conf.set("spark.shuffle.compress", "true") // 默认已开启
spark.conf.set("spark.shuffle.spill.compress", "true")
spark.conf.set("spark.io.compression.codec", "lz4") // 或zstd
实战经验:Shuffle优化效果
我在出海硬件公司做过一次系统性的Shuffle优化。对所有核心作业做了三件事:
1. 把所有SELECT *改成只选需要的列——Shuffle数据量减少30%
2. 把autoBroadcastJoinThreshold调到200MB——多个Join从Shuffle变成Broadcast
3. 开启AQE自动合并小分区——避免过多的小Task拉取开销
综合效果:整体ETL作业的执行时间减少35%,Shuffle数据量减少45%。
总结
Shuffle慢因为磁盘IO+网络IO。优化的核心是减少Shuffle数据量或避免Shuffle。五种手段:reduceByKey代替groupByKey、Broadcast Join、调整partitions、减少列数、开启压缩。面试时能说清楚Shuffle的完整过程、哪些操作触发Shuffle、怎么优化,就很稳。
下一篇聊一个很容易用错的功能:cache()和persist()用了反而更慢了——缓存什么时候该用什么时候不该用?
你们生产环境做过哪些Shuffle优化?评论区聊聊~
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长