数据倾斜调优(上):数据倾斜的成因与现象、定位倾斜的Spark UI分析方法

数据倾斜,说白了就是集群里大部分节点都在摸鱼,只有一两个节点累死累活。我刚开始调优那会儿,最怕看到的就是某个Task跑了半小时,其他Task几十秒就结束了。这种场景,十有八九就是数据倾斜在作怪。

这一节,咱们先把数据倾斜的根儿挖出来,再学会怎么用Spark UI这把手术刀,精准定位问题。

数据倾斜的成因:为什么会出现「一头重」?

数据倾斜的本质,是Partition(分区)之间的数据量分布不均。Spark的并行计算依赖分区,每个Task处理一个分区。如果某个分区的数据量是其他分区的几十倍甚至上百倍,那这个Task自然就成了整个作业的瓶颈。

我遇到过最常见的几种成因,列出来给你看看:

成因 典型场景 影响程度
Key分布不均 用户ID、商品ID、地域ID等热点Key 极高
Join Key倾斜 大表Join小表时,小表Key重复率高 高
Group By Key倾斜 聚合操作中某个Key的数据量巨大 高
分区策略不当 默认Hash分区导致数据扎堆 中
数据源本身倾斜 读取HDFS时某些文件过大 中

核心结论:数据倾斜的本质是Partition间数据量不均衡,导致少数Task承担了绝大部分计算压力。

数据倾斜的现象:怎么判断「中招了」?

你不需要看什么复杂的指标,光从Spark UI的Stage页面就能看出端倪。我总结了几条「一眼识别」的经验:

  • Task运行时间差异巨大:大部分Task在1分钟内完成,但有几个Task跑了10分钟以上还没结束。
  • Shuffle Read数据量异常:某个Task的Shuffle Read Size是其他Task的几十倍。
  • OOM(内存溢出):个别Task频繁报OOM,但集群整体内存还有富余。
  • GC时间过长:某个Task的GC时间占比超过50%,其他Task正常。
  • Stage进度卡住:进度条走到99%就不动了,最后几个Task迟迟不结束。

嗯,这里要注意:不是所有Task慢都是数据倾斜。有时候是数据本地性差,有时候是资源分配不足。所以,咱们得用Spark UI来验证。

定位倾斜的Spark UI分析方法

Spark UI是调优的第一手工具。我个人习惯,遇到性能问题,先打开UI看Stage页面。下面我带你一步步操作。

第一步:看Stage列表,锁定「异常Stage」

打开Spark UI,点击Stages标签页。你会看到所有Stage的列表。重点关注:

  • Duration(持续时间):哪个Stage耗时最长?
  • Shuffle Read/Write:哪个Stage的Shuffle数据量最大?
  • Tasks数量:Stage的Task数量是否合理?

通常,数据倾斜发生在Shuffle阶段,也就是Stage之间有Shuffle Read/Write的环节。比如Group By、Join、Distinct等操作。

第二步:点进Stage,看Task分布

点击耗时最长的Stage,进入详情页。往下翻,找到Tasks表格。这里能看到每个Task的详细信息:

指标 正常情况 倾斜情况
Duration 所有Task时间接近 少数Task时间远超平均值
Shuffle Read Size / Records 各Task数据量相近 某个Task数据量是其他Task的10倍以上
GC Time GC时间占比 < 10% GC时间占比 > 30%
Shuffle Spill (Memory/Disk) 无或少量Spill 大量Spill到磁盘

我的小技巧:在Tasks表格里,按Shuffle Read Size降序排序。如果排第一的Task数据量是第二名的5倍以上,基本可以确认数据倾斜了。

第三步:看Shuffle Details,定位倾斜Key

这一步稍微进阶一点。在Stage详情页,点击Shuffle Details标签。这里能看到每个Task的Shuffle Read分布。如果某个Task的Shuffle Read数据量异常大,说明这个Task对应的Partition里包含了热点Key。

怎么找到这个热点Key?我常用的方法是:

  1. 记下倾斜Task的Index(索引号)。
  2. 在代码里,用mapPartitionsWithIndex打印该Partition的Key样本。
  3. 或者,用df.groupBy("key").count().orderBy(desc("count")).show(10)直接统计Key分布。

举个例子,假设你怀疑某个Join操作有倾斜:

// 统计Key分布,找出热点
val keyCounts = df.groupBy("join_key")
    .count()
    .orderBy(desc("count"))

keyCounts.show(10)  // 看看前10个Key的数据量

// 如果某个Key的数据量特别大,比如超过100万条
// 那它就是倾斜的元凶

第四步:看Event Timeline,确认瓶颈

在Stage详情页,点击Event Timeline。这里用时间线展示了每个Task的执行过程。你能直观看到:

  • 哪些Task在「空转」等待?
  • 哪些Task在「疯狂计算」?
  • Task之间的调度延迟有多大?

如果某个Task的时间线特别长,而且大部分时间花在Computing阶段,那基本就是数据倾斜了。

避坑指南:我曾经遇到过一种情况——某个Task的Shuffle Read Size不大,但GC时间特别长。后来发现是数据倾斜导致的对象序列化/反序列化开销。所以,别只看数据量,GC时间也是一个重要信号。

知识体系总览

下面这张图,把数据倾斜的成因、现象、定位方法串起来了。你可以把它当作一个检查清单:

数据倾斜调优知识体系 数据倾斜 成因分析 现象识别 定位方法 验证确认 常见成因 • Key分布不均(热点Key) • Join Key倾斜 • Group By Key倾斜 • 分区策略不当 典型现象 • Task运行时间差异巨大 • Shuffle Read数据量异常 • 个别Task OOM • Stage进度卡在99% Spark UI定位步骤 1. 看Stage列表锁定异常 2. 点进Stage看Task分布 3. 看Shuffle Details 4. 看Event Timeline 验证方法 • 统计Key分布 • 打印倾斜Partition样本 • 对比GC时间占比 • 检查Shuffle Spill 核心:Partition间数据量不均衡 调优前必做:定位确认 1. 确认倾斜确实存在(不是资源不足或数据本地性问题) 2. 找到倾斜的Stage和Task 3. 定位倾斜的Key或Partition

实战案例:一次完整的定位过程

我记得有一次,一个用户行为分析任务跑了40分钟还没结束。我打开Spark UI,发现Stage 5的进度卡在99%,有2个Task一直在跑。

点进Stage 5,Tasks表格按Shuffle Read Size排序后,排第一的Task读取了2.3GB数据,其他Task平均只有80MB。差了将近30倍!

接着我看了Shuffle Details,发现这个Task对应的Partition 87数据量异常。我在代码里加了一段统计:

val skewedKey = df.filter(spark_partition_id() === 87)
    .groupBy("user_id")
    .count()
    .orderBy(desc("count"))
    .show(5)

结果发现,user_id = 'anonymous' 这个Key占了Partition 87里80%的数据。原来是一个未登录用户的默认ID,导致所有未登录行为都集中到了同一个Partition。

找到根因后,后续的调优就简单了——加盐、过滤、或者单独处理这个Key。这些方法咱们下一节再细聊。

我的经验:数据倾斜定位,80%的工作量在「找到倾斜的Key」这一步。一旦找到了,解决方案往往水到渠成。所以,别急着调优,先把问题定位清楚。

小结

这一节,咱们把数据倾斜的成因、现象和定位方法讲透了。你记住几个关键点:

  • 数据倾斜的本质是Partition间数据量不均衡。
  • Spark UI的Stage详情页是定位倾斜的第一战场。
  • 重点关注Task Duration、Shuffle Read Size、GC Time三个指标。
  • 找到倾斜的Key,调优就成功了一半。