数据倾斜的本质与成因

数据倾斜,说白了就是「忙的忙死,闲的闲死」。

我刚开始做大数据那会儿,总觉得集群资源够用,任务跑得慢就加节点。直到有一次,一个 10 分钟的 Join 任务跑了 3 个小时还没结束,我点开 Spark UI 一看——好家伙,200 个 Task 里 199 个早就跑完了,就剩一个 Task 还在那吭哧吭哧处理 80% 的数据。嗯,这就是典型的数据倾斜。

数据倾斜的定义

数据倾斜是指:在分布式计算中,数据被分配到各个并行任务时,各分区的数据量严重不均。少数几个分区包含了绝大部分数据,导致这些分区的任务执行时间远长于其他分区,最终拖慢整个作业。

核心判断标准:如果集群中 80% 的 Task 在 20% 的时间内完成,而剩余 20% 的 Task 消耗了 80% 的时间,基本可以断定发生了数据倾斜。

常见场景分析

我在项目中遇到过三种最容易踩坑的场景,今天一个一个说清楚。

1. Join 场景

这是数据倾斜的「重灾区」。两个大表做 Join,如果 Join Key 的分布不均匀,比如用户 ID 为 null 或者某个热门 ID 占了大量数据,就会出问题。

-- 典型倾斜 SQL
SELECT a.user_id, b.order_amount
FROM user_info a
JOIN order_info b ON a.user_id = b.user_id
-- 假设 user_id = 'NULL' 的数据占了 60%

为什么会这样?因为 Spark 或 Hive 在做 Hash Join 时,相同的 Key 一定会被分到同一个分区。那个包含大量 null 或热门 Key 的分区,就成了整个任务的瓶颈。

我的经验:遇到 Join 倾斜,先检查 Join Key 的分布情况。我曾经在一个电商项目中,发现「未登录用户」的 user_id 全部是空字符串,导致那个分区处理了 70% 的数据。解决方案很简单——给空值加随机后缀打散。

2. GroupBy 场景

GroupBy 倾斜和 Join 倾斜的成因类似,都是因为分组 Key 分布不均。比如统计每个城市的用户数,北京、上海的数据量可能是其他城市的几百倍。

-- 倾斜 GroupBy
SELECT city, COUNT(*) AS user_cnt
FROM user_table
GROUP BY city
-- 北京分区数据量是其他分区的 500 倍

我个人习惯在做 GroupBy 之前,先跑一个简单的 count 看看 Key 的分布。如果发现某个 Key 的数据量远超其他 Key,就要考虑两阶段聚合或者加盐处理。

3. Count Distinct 场景

这个场景比较隐蔽。很多人以为 Count Distinct 只是去重计数,不会倾斜。但如果你对一个大字段(比如用户行为日志中的 session_id)做 Count Distinct,情况就不同了。

-- 看似无害的 Count Distinct
SELECT date, COUNT(DISTINCT session_id) AS session_cnt
FROM click_log
GROUP BY date

为什么这个也会倾斜?因为 Count Distinct 在底层实现时,需要把所有 Distinct 值 shuffle 到同一个 reducer 去重。如果某天的 session_id 数量特别大,那个 reducer 就会成为瓶颈。我记得有一次,双十一当天的数据量是平时的 100 倍,Count Distinct 跑了 2 个小时没出结果。

场景 倾斜原因 典型表现
Join Join Key 分布不均 单个 Task 处理 80% 数据
GroupBy 分组 Key 数据量差异大 少数分区数据量是其他分区的百倍
Count Distinct 单个 reducer 处理所有去重 某天数据量暴增导致任务卡死

如何定位数据倾斜

定位数据倾斜,说白了就是「看日志、看监控、看分布」。我总结了一套三步走的排查方法。

第一步:看 Task 执行时间

打开 Spark UI 或 YARN 的 Task 页面,看各个 Task 的执行时间。如果出现「长尾效应」——大部分 Task 很快完成,少数几个 Task 耗时极长——基本就是倾斜了。

避坑指南:我曾经只看 Task 的输入数据量,以为数据量大的 Task 慢是正常的。后来发现,有些 Task 数据量不大但处理慢,是因为数据中有大量复杂对象(比如嵌套 JSON),反序列化耗时严重。所以,既要看数据量,也要看处理时间。

第二步:查看数据分布

用 SQL 跑一下 Key 的分布情况,看看有没有「热点 Key」。

-- 检查 Join Key 分布
SELECT user_id, COUNT(*) AS cnt
FROM order_info
GROUP BY user_id
ORDER BY cnt DESC
LIMIT 10

-- 检查 GroupBy Key 分布
SELECT city, COUNT(*) AS cnt
FROM user_table
GROUP BY city
ORDER BY cnt DESC
LIMIT 10

如果发现某个 Key 的数据量是其他 Key 的几十倍甚至几百倍,那就是倾斜的根源。

第三步:分析 Shuffle 数据量

在 Spark UI 的 Stages 页面,看每个 Stage 的 Shuffle Read/Write 数据量。如果某个 Stage 的 Shuffle Write 数据量特别大,说明这个 Stage 的输出存在倾斜。再点进去看具体的 Task,就能定位到是哪个分区出了问题。

我的习惯:在开发环境先跑一个 1% 的采样数据,用上面三步快速验证。确认没有倾斜问题后,再上全量数据。这样能节省大量排错时间。

数据倾斜定位流程 观察 Task 执行时间 检查 Key 数据分布 分析 Shuffle 数据量 定位热点 Key 💡 建议:先在采样数据上跑一遍定位流程,确认无倾斜后再上全量数据。 如果发现热点 Key,记录其分布比例,后续章节会讲具体的解决方案。

定位到热点 Key 之后,下一步就是针对不同的场景选择优化策略。不过那是后面章节的内容了。今天先把「怎么发现问题」这件事吃透,你想想看,如果连问题都定位不到,优化就无从谈起。

一句话总结:数据倾斜的本质是数据分布不均,常见于 Join、GroupBy、Count Distinct 场景。定位方法就是三步走——看 Task 时间、查 Key 分布、分析 Shuffle 数据量。记住这个流程,遇到倾斜问题就不会慌了。