订阅更新

某个Task比其他慢了10倍——Spark数据倾斜怎么解决?

Spark手册 第 3 题 约 3 分钟读完

本文目录
  1. 先说结论
  2. 怎么发现倾斜?
  3. 方案一:开启AQE自动处理(最简单)
  4. 方案二:加盐打散(两阶段聚合)
  5. 方案三:Broadcast小表避免Shuffle
  6. 方案四:拆分热点Key单独处理
  7. 实战经验
  8. 总结

先说结论

解决倾斜的优先级:AQE自动处理 > 加盐打散 > 广播小表 > 拆分热点Key。先试最简单的,不行再上复杂的。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

Spark的数据倾斜和Hive类似,但解决方案更丰富。我做了9年大数据,倾斜是遇到最多的性能问题。今天把Spark里解决倾斜的四种方案按优先级讲清楚。

怎么发现倾斜?

在Spark UI的Stage详情页,看每个Task的“Shuffle Read Size”。如果多数Task只读了10MB,但有一个Task读了5GB,那就是倾斜。

也可以用代码检查:

-- 检查Key分布,找出倾斜Key
SELECT join_key, COUNT(1) AS cnt
FROM table_a
GROUP BY join_key
ORDER BY cnt DESC
LIMIT 20;

方案一:开启AQE自动处理(最简单)

Spark 3.0+的AQE能自动检测倾斜分区并拆分。这是最简单的方案,90%的倾斜场景可以解决。配置见上一篇。

方案二:加盐打散(两阶段聚合)

用于GROUP BY倾斜场景。核心思想:先给Key加一个随机后缀做第一次聚合(打散到多个Task),再去掉后缀做第二次聚合。

// 第一次聚合:加盐打散
val salted = df.withColumn("salted_key",
  concat(col("country"), lit("_"), (rand() * 10).cast("int")))
 
val partial = salted.groupBy("salted_key")
  .agg(sum("amount").as("partial_sum"), count("*").as("partial_cnt"))
 
// 第二次聚合:去盐汇总
val result = partial
  .withColumn("country", split(col("salted_key"), "_")(0))
  .groupBy("country")
  .agg(sum("partial_sum").as("total_amount"),
       sum("partial_cnt").as("total_count"))

方案三:Broadcast小表避免Shuffle

如果Join倾斜且其中一张表足够小,直接用Broadcast Join就可以避免Shuffle,从根本上消除倾斜。这是最优雅的解法,但前提是有一张小表。

方案四:拆分热点Key单独处理

把倾斜的热点Key和非热点Key分开处理,热点Key用Broadcast Join,非热点Key用普速SORT Merge Join,最后UNION ALL合并结果。

实战经验

我在出海硬件公司做用户行为分析时,有个按device_id做GROUP BY的作业,某个测试设备的device_id的数据量占总量的30%(自动化测试产生的大量数据)。开启AQE后自动解决,作业从40分钟降到8分钟。

总结

倾斜解决优先级:AQE自动 > 加盐打散 > Broadcast小表 > 拆分热点Key。先用最简单的,不行再上复杂的。面试时能说清楚“怎么发现倾斜 + 多种解决方案 + 实战案例”,就很稳。

下一篇聊一个很多人志题的问题:DataFrame和RDD到底用哪个?为什么现在几乎没人用RDD了?

你们生产环境的倾斜是用什么方案解决的?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

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