订阅更新

Shuffle Write阶段特别慢,磁盘IO打满了——Shuffle原理和优化

Spark手册 第 6 题 约 5 分钟读完

本文目录
  1. 先说结论
  2. Shuffle的完整过程
  3. 哪些操作会触发Shuffle?
  4. 5种实战优化手段
  5. 实战经验:Shuffle优化效果
  6. 总结

先说结论

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✔ 是
Joinjoin, 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年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器