本质:脏数据 → 数据倾斜 → OOM
业务背景:用户行为日志每日离线跑批,Hive 执行 group by user_id 统计用户行为次数。每日跑批,某天作业突然失败,Reduce 任务 OOM,绝大多数 task 很快跑完,仅 1 个 reduce 反复重试后 OOM 失败。报错日志:
java.lang.OutOfMemoryError: Java heap space
Container killed by YARN for exceeding memory limits.
AttemptID:attempt_xxxx_r_000012,多次重试失败,退出码143
区分:
一、完整排查步骤
步骤 1:YARN UI 观察现象,确认是 Reduce 侧问题
- 正常 reduce:每个输入记录 2~5 万条。
- 失败的 reduce12:输入记录 1200 万条,相差几百倍。👉 确认发生数据倾斜,问题出在这个 reduce。
Counter 只能看到数量,看不到具体 key。
步骤 2:定位异常 Reduce 对应的 Container,下载 task 日志
此时定位热点脏 key:‑99999。
步骤 3:回查原始 Hive 表,验证脏数据
执行 SQL 统计 key 分布,确认脏 key 的量级:
select user_id,count(*) as cnt
from dwd_user_log
group by user_id
order by cnt desc
limit 5;
输出:
表格
| -99999 | 1180 万 |
| 135xxxx | 2.3 万 |
| 136xxxx | 2.1 万 |
确认:上游埋点服务 bug,部分设备上报时用户 ID 解析失败,统一填默认值‑99999,产生千万级脏 key,shuffle 全部落到同一个 reduce,reduce 迭代器把海量 value 加载进内存,直接堆溢出 OOM。
注意:不是业务正常数据,属于脏数据。
步骤 4:临时应急方案(先让任务跑过,保障调度)
方案 1:过滤脏 key(业务允许),SQL 增加 where 条件
select user_id,count(1)
from dwd_user_log
where user_id != '-99999' –过滤脏数据
group by user_id;
临时应急,任务直接跑通。
方案 2:如果业务不能直接过滤,对脏 key 做加盐打散
select
if(user_id='-99999',concat(user_id,'_',floor(rand()*10)),user_id) as user_id_salt,
count(1)
from dwd_user_log
group by if(user_id='-99999',concat(user_id,'_',floor(rand()*10)),user_id)
把脏 key 打散到 10 个 reduce,避免单 reduce 过载;后续再做聚合合并结果。
方案 3:开启 hive.groupby.skewindata=true,拆两轮 MR 做自动倾斜处理(消耗更多资源)。
❗不要优先只调大 reduce 内存:mapreduce.reduce.memory.mb。就算调到 8G,脏数据量继续涨,早晚还会 OOM,治标不治本。
步骤 5:根治源头(防止次日再次发生)
二、关键点
MR reduce 函数接收 (key, Iterable<value>),shuffle 拉取过来该 key 全部的 value;虽然 Iterable 是迭代读取,但 shuffle 合并阶段大量数据会压入内存缓冲区;当同一个 key 千万条,内存缓冲区撑爆,就报 Java heap space OOM。
不能。脏数据量持续增长,再大内存也会被打满;调内存只能临时续命,根本要处理脏数据 / 热点 key。
查询原始表,看该 key 业务含义;如果是异常默认值、null、异常编码,就是脏数据;如果是真实业务对象(爆款商品、头部大用户)属于业务热点。
Counter 看每个 reduce 输入记录数,发现倾斜现象;下载对应 Container 的 task 日志拿到真实热点 key。
三、精简版
线上遇到 Hive on MR 的 reduce OOM。现象是 map 全部跑完,大部分 reduce 快速结束,只有一个 reduce 反复重试 OOM。
首先去 YARN UI 看 job 的 Counter 中 Reduce‑input‑records,发现单个 reduce 输入千万条,远大于其他 reduce,确认倾斜。
找到失败 reduce 对应的 Container,下载 task 日志,发现大量重复 key‑99999。
回查源表统计 key 分布,确认是上游埋点 bug 产生脏数据,千万条记录 user_id 等于‑99999,shuffle 全部落到同一个 reduce,造成 OOM。
应急在 SQL 过滤脏 key 先保障调度跑通;
同时通知上游修复埋点,DWD 层增加清洗逻辑和分布监控,从源头解决。
没有单纯调大 reduce 内存,因为调内存只是临时掩盖问题。
四、延伸:Spark 版本同类现象
Spark 中对应现象:Spark UI 某个 Stage,少数 task 的 Shuffle Read 几百 MB‑GB 级别,Executor 报 OOM;
排查思路:看 task 的 Shuffle Read → 找到慢 task 对应 Executor 日志 → SQL 统计 key 分布定位脏 key。



