2. 数据倾斜原理与定位:成因、表现与排查方法
数据倾斜,说白了就是“忙的忙死,闲的闲死”。
你想想看,一个 Spark 任务分了 200 个分区,199 个跑完了,就剩一个在那吭哧吭哧算。或者 Hive 跑个 Join,Map 阶段挺快,Reduce 阶段卡住不动了。嗯,这就是典型的倾斜。
我刚开始做大数据那会儿,遇到这种问题第一反应就是加资源。加完发现,该慢还是慢,该 OOM 还是 OOM。后来才明白,不解决倾斜,加再多机器也是白搭。
2.1 数据倾斜的成因
倾斜的本质就一句话:数据在分区之间分布严重不均。
为什么会这样?我归纳了最常见的几个原因:
| 成因 | 典型场景 | 影响 |
|---|---|---|
| Key 分布不均 | 空值、默认值、热点 Key(如某个城市 ID 占 80%) | 单个分区数据量巨大 |
| Join 键倾斜 | 大表 Join 小表,小表 Key 重复极高 | Shuffle 阶段数据倾斜 |
| 分组聚合倾斜 | Group By 某个高基数字段,但某个值占比极高 | Reduce 阶段长尾 |
| 数据本身特征 | 日志中某个用户行为占比 90% | 所有算子都可能倾斜 |
| 分区策略不当 | Hash 分区导致大量数据落入同一分区 | 写入/读取阶段倾斜 |
核心认知:倾斜不是 Bug,是数据分布的自然现象。你没法改变数据,但可以改变计算方式。
2.2 常见表现:OOM 与任务长尾
倾斜的表现其实很直观。我把它分成两类:
2.2.1 OOM(内存溢出)
OOM 是最疼的那种。任务跑着跑着,Executor 突然挂了,日志里一堆 OutOfMemoryError。
为什么会 OOM?因为某个分区数据太多,单机内存装不下。比如你在做 groupByKey 或者 map-side join 时,某个 Key 对应的 Value 列表有几千万条,直接撑爆了。
我曾经踩过的坑:有一次做用户画像聚合,用 groupByKey 把用户所有行为塞到一个 Iterable 里。结果某个超级用户有 3000 万条记录,Executor 直接 OOM。后来改成 reduceByKey 加预聚合,问题就解决了。
2.2.2 任务长尾
长尾比 OOM 更隐蔽。任务没挂,但就是慢。你看 Spark UI 上,大部分 Task 几秒跑完,就几个 Task 跑了半小时还在转。
长尾的典型特征:
- Stage 进度条卡在 99% 不动
- 某个 Task 的 Shuffle Read 数据量是其他 Task 的 10 倍以上
- GC 时间异常高(超过 Task 运行时间的 20%)
2.3 定位方法:Spark UI 与 Hive 日志
定位倾斜,说白了就是“找证据”。我个人的习惯是三步走:
- 看宏观:哪个 Stage 最慢?
- 看微观:哪个 Task 数据量异常?
- 看根因:是哪个 Key 导致的?
2.3.1 用 Spark UI 定位
Spark UI 是我用得最多的工具。你打开 http://driver:4040,重点看这几个地方:
| UI 页面 | 看什么 | 倾斜信号 |
|---|---|---|
| Stages | 每个 Stage 的耗时、Shuffle Read/Write 量 | 某个 Stage 耗时是其他 Stage 的 5 倍以上 |
| Stage Detail | 每个 Task 的 Duration、Shuffle Read Size | 少数 Task 的 Shuffle Read 是平均值的 10 倍 |
| SQL Tab | 物理执行计划、每个算子的数据量 | Exchange 节点数据量差异巨大 |
| Executors | 每个 Executor 的 Shuffle Read、GC Time | 某个 Executor 的 Shuffle Read 异常高 |
我的小技巧:在 Stage Detail 页面,按 Shuffle Read Size 降序排序。如果前 3 个 Task 的数据量占了总量的 80% 以上,基本可以断定倾斜了。
2.3.2 用 Hive 日志定位
Hive 的倾斜定位稍微麻烦点,因为日志不像 Spark UI 那么直观。我一般这么干:
- 看 YARN 日志:找到
application_xxx,看每个 Container 的耗时。如果某个 Container 跑了 2 小时,其他 10 分钟,那就是它了。 - 看 Hive 执行计划:用
EXPLAIN看 Stage 划分,重点关注 Reduce 阶段的数据分布。 - 看 Counter:Hive 的 Counter 里有个
RECORDS_OUT,如果某个 Reducer 的输出记录数是其他 Reducer 的 10 倍以上,倾斜无疑。
-- 查看 Hive 执行计划
EXPLAIN
SELECT city, COUNT(*)
FROM user_logs
GROUP BY city;
-- 重点关注 Reducer 数量和数据分布
-- 如果某个 Reducer 的 RECORDS_OUT 异常高,就是它
2.4 知识体系:数据倾斜全景图
下面这张图是我自己总结的。每次遇到倾斜问题,我都会对照着看一遍。
2.5 实战定位流程
最后,我分享一个我常用的定位流程。遇到倾斜问题,按这个顺序查,基本不会漏:
- 打开 Spark UI,看 Stages 页面,找到耗时最长的 Stage。
- 点进 Stage Detail,按 Shuffle Read Size 排序,看前几个 Task 的数据量。
- 如果数据量差异大,说明是 Shuffle 倾斜。点开 Task 看它的 Shuffle Read 来自哪些分区。
- 如果数据量差异不大,但耗时差异大,说明是计算倾斜(比如某个 Key 的计算逻辑特别重)。
- 定位到具体 Key:在代码里加
map算子,输出每个 Key 的记录数,或者用countByKey采样。
避坑指南:我曾经有一次查了半天,发现是空值导致的倾斜。所有空值都 Hash 到了同一个分区。后来加了个 coalesce 把空值随机打散,问题就解决了。所以,先看空值,这是最常见的倾斜源。