数据倾斜调优(上):数据倾斜的成因与现象、定位倾斜的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?我常用的方法是:
- 记下倾斜Task的Index(索引号)。
- 在代码里,用
mapPartitionsWithIndex打印该Partition的Key样本。 - 或者,用
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时间也是一个重要信号。
知识体系总览
下面这张图,把数据倾斜的成因、现象、定位方法串起来了。你可以把它当作一个检查清单:
实战案例:一次完整的定位过程
我记得有一次,一个用户行为分析任务跑了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,调优就成功了一半。