1、什么是数据倾斜?

在Hive这类分布式计算框架里,数据分布不均匀,导致某些节点处理的数据量远大于其他节点,进而影响整体性能。

例如,就像10个人分100个苹果,其中9个人各拿1个苹果,剩下1个人要拿91个。这时候拿91个的人会花很长时间,等他吃完其他人才开始吃,最后整体完成时间完全取决于这个最慢的人。

2、具体表现?

1)看执行日志像看心电图

执行计划中某个reduce节点(通常是编号末尾带"r=0"的那个)的进度条会卡在99%长时间不动,其他节点早就显示"已完成"

2)资源利用率两极分化

90%的节点CPU利用率不到10%,而个别节点占用全部内存还报错(常见OOM Killer被触发)

3)时间线诡异

任务总时长显示"10分钟",但前9分50秒所有节点都处于空闲状态,最后20秒突然疯狂滚动日志

4)数据分布可视化

如果用SELECT key, COUNT(*) FROM table GROUP BY key统计,会发现某些key对应的数值比其他大几十甚至上百倍

3、典型场景举例

1)场景一:热点数据倾斜(比如北京的数据特别多)

-- 假设有一个全国用户订单表,北京的数据占70%
SELECT province, COUNT(*) 
FROM user_orders 
GROUP BY province; 

现象:执行时发现北京的数据量是其他省份的几十倍,导致处理北京数据的任务卡死

解决方案:

方法一:提前过滤热点(把大石头单独搬走)

-- 先处理北京的数据
SELECT province, COUNT(*) 
FROM user_orders 
WHERE province = 'Beijing';
-- 再处理其他省份(用NOT IN排除热点)
SELECT province, COUNT(*) 
FROM user_orders 
WHERE province != 'Beijing' 
GROUP BY province;

方法二:随机数打散(把大石头砸成小石子)

-- 给北京的数据加上随机后缀(比如分10个小任务)
SELECT 
  CASE WHEN province = 'Beijing' 
       THEN CONCAT(province, '_', FLOOR(RAND()*10)) 
       ELSE province 
  END AS new_province,
  COUNT(*)
FROM user_orders 
GROUP BY new_province;

场景2:空值/默认值倾斜(比如null值太多)

-- 用户地址表中,80%的地址字段是NULL
SELECT address, COUNT(*) 
FROM user_address 
GROUP BY address;

现象:处理NULL值的任务堆积如山,其他地址任务秒完成

解决方案:

方法:给空值加盐(把垃圾袋拆分成多个小袋)

-- 把NULL变成多个随机值(比如null_1, null_2...)
SELECT 
  COALESCE(address, CONCAT('null_', CAST(FLOOR(RAND()*5) AS STRING))) AS new_address,
  COUNT(*)
FROM user_address 
GROUP BY new_address;

场景3:JOIN关联倾斜(比如大V用户关联时卡死)

-- 用户表(user)有1亿条数据,其中用户ID=123的超级用户有100万条行为数据
-- 行为表(action)有10亿条数据,超级用户的行为占10%
SELECT u.user_id, COUNT(a.action_id)
FROM user u
JOIN action a ON u.user_id = a.user_id
GROUP BY u.user_id;

现象:JOIN时处理用户ID=123的任务卡在99%,其他任务早就完成

解决方案:

方法1:MapJoin广播小表(把字典直接发给所有人)

-- 如果用户表比较小(比如<2G),直接强制广播
SET hive.auto.convert.join=true; -- 默认开启
SET hive.mapjoin.smalltable.filesize=20000000; -- 小表阈值
SELECT /*+ MAPJOIN(u) */ 
  u.user_id, COUNT(a.action_id)
FROM user u
JOIN action a ON u.user_id = a.user_id
GROUP BY u.user_id;

方法2:打散大Key(把大象切成牛排)

-- 给大V用户的ID加上随机前缀(比如0~9)
-- 行为表也做同样处理,让数据分散到不同任务
SELECT 
  u.user_id, 
  SUM(a.cnt)
FROM 
  (SELECT user_id, 
          CASE WHEN user_id = '123' 
               THEN CONCAT(user_id, '_', FLOOR(RAND()*10)) 
               ELSE user_id 
          END AS join_key
   FROM user) u
JOIN 
  (SELECT user_id, 
          CASE WHEN user_id = '123' 
               THEN CONCAT(user_id, '_', FLOOR(RAND()*10)) 
               ELSE user_id 
          END AS join_key, 
          COUNT(*) AS cnt
   FROM action 
   GROUP BY user_id, join_key) a 
ON u.join_key = a.join_key
GROUP BY u.user_id;

4、通用处理步骤(急救包)

1)先定位哪里倾斜

  1. 执行GROUP BY看哪个Key的数据量爆炸(哪张表引用的数据最多)
  2. 看Hive日志里哪个任务卡在99%

 2)简单处理三板斧:

  1. 过滤热点:用WHERE把大Key单独处理
  2. 打散数据:给大Key加随机数(RAND()或FLOOR(RAND()*N))
  3. 调参数:临时加大内存(set mapreduce.reduce.memory.mb=8192;)

5、总结

习惯1:写SQL前先做「数据体检」

查分布:任何涉及GROUP BY/JOIN的字段,先跑一次:

SELECT key_column, COUNT(*) AS cnt 
FROM table 
GROUP BY key_column 
ORDER BY cnt DESC 
LIMIT 10;  -- 看TOP10的数据量是否均衡

看空值:用SUM(CASE WHEN column IS NULL THEN 1 ELSE 0 END)统计空值比例

防爆雷:如果发现某个key占比超过30%,立刻警惕倾斜风险

习惯2:学会「拆快递式」处理数据

分治思想:遇到大key时主动拆分

-- 原写法(危险):
SELECT * FROM logs WHERE user_id = '超级用户';

-- 安全写法(拆成10份):
SELECT * FROM logs 
WHERE user_id = '超级用户' 
  AND SUBSTR(时间戳, -1) IN ('0','1','2'); -- 按时间尾数分批查

多用采样:测试阶段用TABLESAMPLE(BUCKET 1 OUT OF 100)随机取1%数据验证逻辑

习惯3:给常用SQL加「安全气囊」

配置参数:在Hive脚本开头养成设置这些参数的习惯

-- 自动处理倾斜的JOIN
SET hive.optimize.skewjoin=true;         -- 开启倾斜JOIN优化
SET hive.skewjoin.key=100000;            -- 超过10万行的key视为倾斜

-- 增加并行度
SET hive.exec.reducers.bytes.per.reducer=256000000; -- 每个reduce处理256MB
SET hive.exec.reducers.max=200;                     -- 最多开200个reduce

-- 内存保护
SET mapreduce.reduce.memory.mb=4096;     -- 调大reduce内存

习惯4:学会「看红绿灯」——监控任务日志

关键观察点:        

  1. 进度条:如果某个reduce卡在99%超过5分钟,大概率倾斜
  2. 资源监控:在YARN界面看哪个container内存/CPU爆红
  3. 数据分布:执行完成后用desc table extended看各分区大小是否均衡

习惯5:设计表时「提前分篮子」

预防性设计

分区策略:对常作为过滤条件的字段(如日期、省份)建立分区(取数的时候也针对性的取出分区)

分桶表:对高频JOIN的字段(如user_id)用CLUSTERED BY分桶

CREATE TABLE user_orders 
CLUSTERED BY (user_id) INTO 100 BUCKETS  -- 强制均匀分布

避免全表扫描:永远不在WHERE条件中使用无分区过滤的裸查!!!

习惯6:掌握「三大保命口诀」

遇到倾斜问题时按顺序尝试:

  1. 「能过滤就不计算」:先用WHERE剔除大key
  2. 「打不过就分家」:用RAND()或MOD()拆分倾斜数据
  3. 「打不过就摇人」:超过30分钟解决不了,立刻找技术老司机求助

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐