在大数据计算领域,最让人头疼的问题之一就是 数据倾斜。它常常是导致任务跑得慢、集群资源被白白浪费、甚至直接失败的元凶。别看名字挺玄乎,其实本质并不复杂。
1. 数据倾斜的本质原因
一句话:数据分布不均导致计算资源不均。
更具体点说:
如果某些 Key 的数据量远远大于其他 Key,那就会导致部分计算节点的任务负担过重,结果就是——有的 Task 很快跑完,有的 Task 却累得要死要活,整个任务只能等最慢的那几个收尾。这就是典型的 “木桶效应”,也被叫做 长尾效应。
2. 怎么判断是不是数据倾斜?
数据倾斜的典型现象,往往都能在 Spark、Flink 的 Web UI 上直接看到:
任务执行时间异常变长:某些 Task 执行特别久,把整体进度拖慢。
Task 卡住在 99% 不动:总有几个 Task 死活跑不完。
频繁失败重试 / OOM:个别节点的内存频繁爆掉,反复 GC,甚至直接挂掉。
单节点资源飙升:某台机器 CPU 或内存占用远超其他节点。
在 ODPS 里,Fuxi 就会直接把耗时超过平均值 2 倍的实例判定为长尾实例。
一句话总结:大部分任务都跑得轻松,就有几个“拖后腿”的。
3. 怎么排查数据倾斜?
通常要分两步走:
(1)先看是不是数据分布问题
在 Spark UI / Flink Web UI / Doris Profile 里看 Task 的执行时间分布;
关注 Shuffle 阶段的统计:是否有某些 Task 的 shuffle read 特别大;
看日志里是否有频繁的 Task attempt 重试、OOM。
还可以直接在数据源里跑一条 SQL 来看 Key 的分布情况:
SELECT key, COUNT(*)
FROM table
GROUP BY key
ORDER BY COUNT(*) DESC
LIMIT 10;如果结果显示 某个 key 出现了一千万次,而其他只出现几次,那基本就实锤了。
(2)再看是不是资源/节点问题
有些情况是节点本身的问题,比如:
某台机器 CPU 长时间 90%+;
内存频繁爆掉或者 swap;
磁盘 IO 异常,shuffle 文件写得很慢;
网络带宽不足,传输速度被卡住。
这时候就要结合 Prometheus/Grafana 或 Node Exporter 看节点指标,排除掉系统层面的问题。
4. 数据倾斜的高发场景 & 解决思路
常见的导致倾斜的 SQL 操作有几类:Join、GroupBy、Count(Distinct)、ROW_NUMBER (TopN)、动态分区。不同场景有不同的优化方法。
以ODPS为例
Join 倾斜
大表 + 小表:用 MapJoin,小表直接放内存里,避免 Shuffle。
大表 + 中表:用 DistMapJoin。
热点 Key:
手动拆分热点值,把热点值单独处理;
开启 SkewJoin 参数:set odps.sql.skewjoin=true;;
使用 SkewJoin Hint,明确告诉执行引擎哪些是热点值;
或者干脆用 倍数表取模法 来均摊负载。
GroupBy 倾斜
设置参数:set odps.sql.groupby.skewindata=true;
加随机数打散:把热点 Key 拆成多个小组,后面再合并。
滚存表:提前预聚合,减少最终的 GroupBy 规模。
Count(Distinct)
参数优化:set odps.sql.groupby.skewindata=true;
两阶段聚合:先拼接随机数打散,再在上层聚合。
用 分区字段 + 业务字段 组合聚合,比如 (ds, shop_id)。
ROW_NUMBER (TopN)
SQL 写法的两阶段聚合:拼接随机数,让热点 Key 分担到多个分区。
用 UDAF 实现:基于 最小堆队列,提高计算效率。
5. Doris 中的数据倾斜处理案例
在 Doris 里,数据倾斜也经常出现在 聚合查询 或 Join 查询 场景。常见做法:
分桶策略优化
建表时,选择合适的 DISTRIBUTED BY HASH(key) 字段。如果 user_id 明显是热点,可以改用 (user_id, dt) 等组合字段来更均匀分布数据。
CREATE TABLE orders (
order_id BIGINT,
user_id BIGINT,
dt DATE,
amount DECIMAL(10,2)
)
DISTRIBUTED BY HASH(user_id, dt) BUCKETS 32;随机数打散再聚合
查询时给热点 key 增加随机前缀,打散后再二次聚合:
SELECT user_id, SUM(amount)
FROM (
SELECT user_id, FLOOR(RAND()*10) AS salt, amount
FROM orders
) t
GROUP BY user_id, salt;再在外层做一次聚合:
SELECT user_id, SUM(sum_amount)
FROM (
SELECT user_id, salt, SUM(amount) as sum_amount
FROM orders_salt
GROUP BY user_id, salt
) tmp
GROUP BY user_id;利用物化视图
对热点聚合场景,提前做物化视图,避免查询时产生严重的倾斜:
CREATE MATERIALIZED VIEW mv_user_sum AS
SELECT user_id, SUM(amount)
FROM orders
GROUP BY user_id;6. 总结
数据倾斜问题,说白了就是 数据不均衡 + 资源调度受限。定位时要先分清是 业务数据分布问题,还是 节点资源问题。
解决思路也很明确:
•从 SQL 逻辑上入手(打散热点 key、两阶段聚合、MapJoin);
•从执行参数入手(开启防倾斜参数、设置 SkewJoin);
•从资源角度入手(排查机器负载、网络 IO、磁盘 IO)。
一句大白话总结:遇到数据倾斜,别慌,先找“胖子 Key”,再决定是拆分、合并,还是直接交给引擎参数去搞定。
















