102:17 三条告警:倾斜还是抢占
凌晨 02:17,值班群连续弹出三条告警:日批任务 ads_user_trade_daily 第三次失败;
YARN 队列 root.batch 资源占用 98%;同时 root.adhoc 里分析师提交的即席查询全部 PENDING。
值班同学第一反应是「倾斜了」,改了几个 Hive 参数重跑,任务在 Reduce 阶段跑了 40 分钟后被 KILL,
日志里只有一行 Application killed by preemption。
这是复合场景(虚构,用于教学):表面现象是 Hive 跑不完,根因却横跨 YARN 队列配置、 Tez Shuffle 阻塞与 Join Key 倾斜三层。线上真实事故往往也是多因素叠加——只盯 SQL 或只盯队列, 都会把排查拖成「反复重跑、碰运气」。
事后推演:若把 preempt 日志与 Tez 最慢 task 时间线画在同一张纸上,通常能在十五分钟内确认是 「倾斜拉长作业 → 触发抢占」还是「纯资源不足」。本篇就是把这套手工方法沉淀成文档化 SOP。
本篇假设读者已掌握 HDFS / YARN / Hive 基础(可参考同系列 T01、T02),聚焦排障框架与 全链路 SOP:先建立「现象分类 → 工具链 → 根因 → 处置 → 预防」的不变量, 再用两个复合案例走完整复盘。分享约 45 分钟:20 分钟框架与 YARN / Hive 分诊,20 分钟双案例 walkthrough, 5 分钟 SOP 与 Runbook 讨论。
开始敲命令之前,先确认手上有四类信息:YARN applicationId 与队列名、Hive SQL 或脚本版本、
任务开始 / 失败时间窗、Tez DAG 截图或 History Server 链接。缺任一项都容易误判——例如没有队列名就无法对照
capacity 配置,没有精确时间窗就无法和 RM 审计日志对齐。
值班现场最常见的失败模式不是「不会查日志」,而是层次选错:看到进度条卡在 99% 就去改
hive.exec.reducers.max;看到 Application KILLED 就去扩集群;看到 Shuffle 慢就去加带宽。
这些动作各自在单层故障里可能有效,但在复合场景里往往把窗口浪费在症状上。本篇刻意把「分层次」写成可执行口令:
任何告警的第一分钟只做一件事——读 YARN 终态与 Diagnostics,再决定走资源、执行还是数据入口。
另一个隐性成本是交接失真。夜班同学改完参数重跑「看起来好了」,白班接手时看不到 counter 基线与队列快照, 就会把偶然成功当成根因消除。因此后文所有 SOP 都要求把关键输出写进工单:应用终态、队列 Used/Max、 最慢 task 耗时、采样 TOP N。没有这四项,复盘会退化成「又挂了再改」。
| 层次 | 管辖范围 | 典型背锅点 | 首选工具 |
|---|---|---|---|
| 资源层 | YARN 队列、NM 健康、Container 生命周期 | 抢占、ACCEPTED 饥饿、Node 磁盘满 | yarn application、RM UI、capacity-scheduler.xml |
| 执行层 | Tez DAG、Map / Shuffle / Reduce 并行度 | 小文件 Map 爆炸、Spill 过多、Reducer 不足 | Tez UI、counter、EXPLAIN |
| 数据层 | 分区裁剪、Join Key 分布、脏数据 | 热点 key、空值 / 0 值、维表膨胀 | 采样 SQL、表统计信息、数据质量规则 |
未确认 YARN 层就无限调大 hive.exec.reducers.max,会制造更多 Container,在抢占环境下适得其反。
未看 Tez 阶段就启用倾斜参数,可能对 Map 阶段问题无效。把「重跑成功」当「问题解决」而不留 counter 对比,
下次数据量上涨故障会复发。机制描述(Capacity Scheduler 抢占顺序、Tez 阶段划分)属于机制共识;
参数调优阈值与队列配比建议属于作者经验总结,落地前需结合集群规模与 SLA 验证。
后文章节安排:第二章给出三层排障地图;第三至五章分别展开 YARN 抢占、Hive 卡顿、倾斜识别框架; 第六、七章保全两个复合案例全链路;第八章划定「适合深挖 / 不适合先分析」边界;第九章沉淀统一 SOP; 第十章收束为决策矩阵与相邻知识地图。
2三层排障地图:先资源,后阶段,再数据
大数据离线任务排障有三条不变量。先分层次、再选工具:YARN preempt、Tez Shuffle、Join Key 倾斜 的证据入口不同,混在一起会浪费值班窗口。时间线对齐优于单点截图:KILL 时刻、首次 preempt、 最慢 Reduce 启动时刻必须画在同一轴上。缩短长尾优先于加资源:倾斜任务占用越多 Container、跑得越久, 越容易成为抢占 victim——只加节点或只调 max-capacity 往往治标。
值班口诀可记为:「99% 看 Reduce,个位数看 Shuffle,均匀慢看 Map 或资源」。 若 Diagnostics 含 preemption,先走资源层六步命令链,再交叉 Tez 最慢 task;若终态 RUNNING 且 Shuffle 低位, 优先查 Join 输出与脏 key,而不是先加机器。
三层地图不是三套互斥手册,而是优先级与证据入口。资源层回答「为什么任务被杀死或根本起不来」; 执行层回答「活着的任务卡在哪一段」;数据层回答「为什么某一段异常长」。复合故障的典型形态是: 数据层制造长尾 → 执行层把长尾表现为进度假死或单 Task 99% → 资源层在队列触顶时把长作业选为 victim。 所以复盘时要能同时画出两条线:业务时间轴(提交、Reduce 启动、KILL)与资源时间轴(Used 贴 Max、pending 出现、preempt 事件)。
工具链选择也服从层次:RM UI / yarn application 属于资源层;Tez UI / counter / EXPLAIN
属于执行层;采样 SQL / 表统计 / 质量规则属于数据层。跨层抄工具会制造噪声——例如在资源层故障时去跑大规模
GROUP BY 采样,只会再烧一轮队列。正确做法是:资源层先用秒级命令确认是否 preempt;确认需要数据层证据时,
再用与失败任务相同分区谓词做小范围采样,而不是全表扫描。
3YARN 资源抢占:现象识别与队列排查
在 Capacity Scheduler 下,抢占(Preemption)是 RM 为更高优先级队列回收资源的机制,并非「随机杀任务」。 Hadoop 3.x 默认由 CapacityScheduler 的 PreemptionMonitor 周期扫描;当某 leaf 队列使用的资源超过其 max-capacity,且同父队列下有更高优先级 pending 应用时,RM 会向 NM 下发 preempt 指令。 排障时先把现象归入三类,避免与「任务本身慢」混淆。
现场沟通时建议统一术语:说「被抢占」时,要区分 Application 级 KILL 与 Container 级回收。
前者在 UI 上终态明确;后者可能表现为 task 失败重试、AM 重启、进度回退,Diagnostics 不一定立刻写出
preemption——此时必须用 yarn logs 检索 preempt 关键字,并与 RM Timeline 对齐,
否则会被误判成「Tez 不稳定」或「代码偶发 NPE」。
| 现象类别 | 典型信号 | 首要怀疑点 |
|---|---|---|
| 应用被直接 KILL | YARN UI 状态 KILLED,Diagnostics 含 preemption | 跨队列优先级 / 队列 max-capacity 超限 |
| Container 被回收 | AM 日志 Container preempted,任务重试或失败 | 集群整体超卖、user-limit 或 leaf 队列无弹性 |
| 长期 ACCEPTED / 无法启动 AM | yarn application -list 长时间 ACCEPTED | 队列 capacity 已满,非抢占但同属资源层 |
3.1 会读三层数字:capacity / maximum-capacity / user-limit-factor
排障队列问题时要会读三层数字:capacity(保证容量占比)、
maximum-capacity(弹性上限)、user-limit-factor(单用户可占 leaf 队列比例)。
leaf 队列上的 RUNNING 应用消耗的是该 leaf 的份额;当 leaf 使用超过 max-capacity 且兄弟队列有 pending,
抢占监视器才会介入。Hive 3.x 提交任务时,队列来自 mapreduce.job.queuename 或
tez.queue.name。若 HS2 默认队列与脚本不一致,会出现「以为在 batch 实际跑在 default」的错位——
务必用 yarn application -status 核对实际 Queue 字段。
读配置时还要区分「保证」与「弹性」。capacity 描述的是父队列内的相对份额,不是绝对核数; maximum-capacity 才是该 leaf 在集群紧张时仍可冲到的上限。很多团队把 batch 的 max-capacity 设成 100, 意图是「凌晨没人时尽量吃满」,结果白天 adhoc 一旦 pending,监视器会把超份额的 batch Container 收回。 反过来,若 max-capacity 与 capacity 几乎相等,队列失去弹性,大促回刷会长期 ACCEPTED——这不是抢占, 但同属资源层饥饿,处置手段是临时上调 max 或独立 leaf,而不是去改 Hive SQL。
ordering-policy 与优先级语义同样关键。Capacity Scheduler 可按 FIFO 或 Fair 在 leaf 内排序; 跨队列抢占则更依赖父队列下的优先级配置与 pending 状态。排障时不要只盯「有没有 preempt 字样」, 还要问:被杀的是哪个 leaf、催命的是哪个兄弟队列、失败时刻 Used 是否已经越过 Max。 三问答齐,才能判断是「配置本就如此」还是「倾斜把作业拖进了抢占窗口」。
Capacity Scheduler 的抢占由 ResourceManager 侧监视器驱动,目标是保障队列间公平与优先级语义,
而不是保证单个长作业永不被回收。官方文档明确队列通过 capacity / maximum-capacity 等属性描述份额与弹性上限;
具体 victim 选择与等待窗口受 yarn.resourcemanager.monitor.capacity.preemption.* 系列参数影响。
详见上述 Capacity Scheduler 文档。生产排障时,把「官方机制语义」与「本集群阈值经验」分开记录:前者可直接引用文档,
后者必须标注为经验值并在变更窗口复测,避免把别的集群的 wait/kill 毫秒数原样照搬。
3.2 抢占排查六步闭环
值班时按固定顺序执行,避免跳步导致误判。每一步产出应写入工单,便于跨班次交接。
| 步骤 | 目的 | 命令 / 操作 | 判读要点 |
|---|---|---|---|
| 1. 锁定应用 | 确认终态与队列归属 | yarn application -status <appId> | State=KILLED 且 Diagnostics 含 preemption 才进入抢占链路;Queue 与脚本不一致则先修正提交配置 |
| 2. 对齐时间窗 | 关联 RM 事件与业务高峰 | RM UI Timeline;yarn logs ... | grep -i preempt | 记录首次 preempt 与终态 KILL 间隔;间隔短说明 victim 选择果断,需优先缩短长尾 |
| 3. 快照队列 | 还原失败时刻资源争用 | yarn queue -status 对照 batch / adhoc | Used 持续贴 Max 且兄弟队列有 pending,抢占条件成立 |
| 4. 枚举 pending | 确认高优先级催命者 | yarn application -list -appStates ACCEPTED | 无 pending 却 preempt,需查配置不一致或误配置 |
| 5. 核对配置 | 排除 XML 与热更新漂移 | 核对 capacity-scheduler.xml 与 yarn-site 抢占开关 | 确认 preemption.enabled、max_wait_before_kill、ordering-policy |
| 6. 交叉 Tez | 区分纯抢占与倾斜诱发 | Tez UI 最慢 task;AM restart 次数 | 最慢 Reduce 超 30 分钟且 Container 远高于均值,根因是倾斜,抢占只是放大器 |
# 查看应用终态与诊断信息(Hadoop 3.x)
yarn application -status application_1688888888888_0001 | grep -E 'State|Final|Diagnostics|Queue'
yarn queue -status root.batch.etl
yarn logs -applicationId application_xxx | grep -i preempt
3.3 关键参数与处置优先级
| 参数名 | 含义 | 排障关联 | 调优方向(经验) |
|---|---|---|---|
yarn.resourcemanager.monitor.capacity.preemption.enabled | 是否开启容量抢占监视器 | false 时不会 preempt KILL,但 pending 可能永久饥饿 | 生产通常 true;临时关闭仅作止血 |
...preemption.max_wait_before_kill | 标记可抢占后等待多久强制回收 | 过小则长任务无完成机会;过大则高优等待过久 | 批处理窗口可试 30000~60000 ms,需压测 |
...queue-path.capacity / maximum-capacity | 保证容量与弹性上限 | 突破 max 且兄弟 pending 时触发评估 | 大促临时上调 batch max,窗口结束回滚 |
...user-limit-factor | 单用户可占 leaf 比例倍数 | 过大导致单用户独占诱发误伤 | 多团队共享 leaf 时保持 1~2 |
mapreduce.job.queuename / tez.queue.name | 任务实际提交队列 | 与脚本注释不一致时所有 queue 结论失效 | HS2 默认队列与批处理脚本双重校验 |
把 user-limit-factor 设为很大(如 10)会让单用户占满 leaf 队列,其他团队任务进入 pending 后触发抢占「误伤」。
排查时务必同时看用户名 / 队列 / 时间窗三维。处置优先级:时间窗隔离 → 调 max-capacity →
调大 max_wait_before_kill → 配合 AM 重试;但单纯加 Hive 并行度可能加剧 Container 数量,更容易成为 victim。
监控建议接入 leaf 队列 Used/Max、每小时 preempt 计数、单 application 运行时长 P99(阈值需自校准)。
| 阶段 | YARN 抢占专项 SOP |
|---|---|
| 现象 | 任务失败或反复重试,Diagnostics 含 preemption;或非自身队列错误却 Container 丢失 |
| 工具 | yarn application -status、yarn queue -status、yarn logs、capacity-scheduler.xml |
| 定位 | 对比失败时刻队列使用率、max-capacity、pending 高优先级队列 |
| 方案 | 时间窗隔离 / 调 max-capacity / 调抢占等待 / 降低单任务 Container 碎片 |
| 预防 | 队列容量月度复盘;批处理与 adhoc 隔离;抢占事件接入监控大盘 |
4Hive 任务卡顿:Map / Shuffle / Reduce 阶段定位
Hive 3.x 默认 Tez 引擎下,「卡住」必须精确到阶段。UI 上进度条长时间不动,可能是 Map 读 HDFS 慢、 Shuffle 网络 / 磁盘瓶颈、Reduce 倾斜或外部表锁表,处置手段完全不同。Tez 将 MR 的多 Job 链合并为 DAG, Vertex 之间通过 Edge 传递数据;因此「卡住」有时发生在上游 Vertex 已全部完成、下游尚未启动的调度间隙—— 这种短暂停顿不应与真正卡顿混淆,需看 Vertex 状态是 INIT 还是 RUNNING。CLI Stage 名与 Tez Vertex 不一一对应, 排障以 Tez UI 为准。
进度条本身是聚合视图,容易骗人。Hive CLI 或调度平台显示的「78%」往往把多个 Vertex 的完成比例做了加权; 真正有诊断价值的是:哪个 Vertex 占墙钟时间最多、该 Vertex 内 task 的 max/avg 比、以及该阶段的 Spill / HDFS_BYTES_READ / GC 是否相对历史基线异常。习惯「只看总进度」的值班同学,会在 Shuffle 假死与 Reduce 长尾 之间来回猜——两者在总进度上都可能表现为长时间不动,但对策完全相反。
还要注意「假卡顿」三类:一是编译阶段卡在 Metastore 锁或 CBO,Tez 根本未启动;二是 AM 刚被抢占重启, DAG 重新调度导致进度回退;三是外部依赖(JDBC 外表、远端 HTTP UDF)阻塞个别 task。 进入第四章后文的阶段分流前,先用「Tez 是否已有 RUNNING Vertex」把这三类剔掉,否则会误用 Map/Shuffle/Reduce 参数。
4.1 Tez UI 五步扫视与 Counter 触发线
- DAG 概览:Vertex 数量是否异常多(小文件)?是否有重复扫描同表?
- 各 Vertex 耗时占比:找出占总时长 50% 以上的阶段,后续只深查该阶段。
- Task 耗时分布:max 远大于 avg 十倍级,倾斜概率极高。
- Counter 面板:对比同作业历史基线,而不是绝对值。
- Diagnostics / Errors:是否有 OOM、Disk full 等硬错误。
| Counter 名称 | 异常信号(经验) | 可能含义 |
|---|---|---|
NUM_FAILED_TASKS | 持续增加且伴随 AM restart | 抢占、NM 故障或 OOM |
SPILLED_RECORDS | 相对历史同 SQL 超 5 倍 | Shuffle 压力过大或 sort buffer 偏小 |
HDFS_BYTES_READ | 远高于表统计预估 | 分区未裁剪、全表扫 |
REDUCE_INPUT_GROUPS | 单 task 远超 siblings | Reduce 侧 key 倾斜 |
GC_TIME_MILLIS | 占 task 时间 30% 以上 | 堆过小或单 task 数据量过大 |
-- Hive 3.1.x 排障 Session 参数示例(临时开启)
SET tez.grouping.min-size=67108864;
SET tez.grouping.max-size=268435456;
SET hive.exec.reducers.bytes.per.reducer=256000000;
SET hive.tez.auto.reducer.parallelism=true;
SET hive.task.progress=true;
Tez 的 hive.exec.reducers.max 只是上限;真正并行度由输入大小与 bytes.per.reducer 决定。
遇到卡顿时先算「合理 Reduce 数 = 输入字节 / bytes.per.reducer」,再对比 UI 上是否出现单 Task——
这是区分「资源不够」与「倾斜」的十秒判据。若合理 Reduce 数为 200 而 UI 显示 1 个 Task 极慢,
几乎可断定倾斜而非集群算力不足。部分遗留任务仍走 MapReduce,SOP 分层思路不变,仅工具入口不同。
Map 阶段还有一个高频误判:把「Map 数很多」直接等同于「小文件问题」。真正需要同时满足两件事:
Map 数相对输入体积异常偏高,且单 Map 运行时间极短(秒级)。若 Map 数高但单 Map 也很长,更可能是分区谓词过宽或
单文件本身很大。处置前用 hdfs dfs -count 与表分区统计对照,避免一上来就调
tez.grouping.min-size 掩盖了全表扫描。
| 卡顿阶段 | 关键信号 | 根因类别 | 快速验证 |
|---|---|---|---|
| Map | Map 数过大;单 Map 耗时极短 | 小文件、分区扫描过宽 | hdfs dfs -count;收紧分区谓词 |
| Shuffle | Spill 高、Shuffle 占比过大 | 数据量过大、sort buffer 不足 | 调 tez.runtime.io.sort.mb;查 ORC 压缩 |
| Reduce | 1 个 task 耗时为其他百倍 | Key 倾斜 | Tez task 分布;采样 key |
| 全局 | 频繁 AM restart、Container 骤降 | YARN 抢占 | RM Diagnostics 与时间线对齐 |
| Metastore | 编译阶段卡住 | 锁表、元数据慢查询 | SHOW LOCKS;Metastore 慢 SQL |
5数据倾斜识别四步法
第六、七章都属于倾斜类问题,但表现阶段不同。进案例前,先统一倾斜识别四步法—— 这是框架训练的核心产出。T02 侧重建表、分区、Explain 与倾斜参数详解;本篇侧重值班现场如何把倾斜与 YARN / Hive 卡顿串联。两文互补:T02 学「怎么写对」,T04 学「怎么在告警里判对错」。
-- 倾斜 key 采样模板(Hive 3.1.x)
SELECT join_key, COUNT(*) AS cnt
FROM your_table
WHERE dt = '${bizdate}'
GROUP BY join_key
ORDER BY cnt DESC
LIMIT 50;
在 Tez UI 查看同一 Vertex 下 task 耗时分布。若 max 耗时大于 median 的 10 倍,且最慢 task 的
REDUCE_INPUT_GROUPS(或 Map 输出 records)远高于其他 task,倾斜成立。若所有 task 耗时接近但都很长,
更可能是数据量整体过大或资源不足,应回到第三章查队列与第四章查 Map 数。除 TOP N 外,建议再看
空值桶:许多 Join 倾斜不是「头部太大」,而是「默认值太多」——加盐对 0 值往往不如直接过滤有效。
优化后对比四项:总耗时、最慢 task、preempt 次数、结果准确性;并确认 EXPLAIN 中 MapJoin 真正生效。
四步法里最容易偷懒的是第四步「验证回归」。只看「这次跑完了」不够:必须同 SQL、同数据量窗口对比 counter, 并确认 preempt 次数下降而不是碰巧没有 adhoc 冲进来。若加盐后最慢 task 缩短但总耗时反而上升, 可能是盐度过高导致 Reduce 数暴涨、调度开销吞噬收益——此时应回调盐桶数,而不是继续加大并行度。 框架训练的目标,是让值班同学在压力下仍能按步骤产出可复验证据,而不是凭感觉选一种「听说能治倾斜」的参数。
与 T02 的边界再强调一次:T02 讲清楚建表、分区、Explain 与各类倾斜改写的语法细节;本篇只保留值班必需的 识别判据与选型顺序。若你在现场已经确认是 GROUP BY 热点,具体两阶段写法以 T02 为准;若你还不确定是倾斜、 抢占还是 Shuffle 假死,应先走本篇第五至七章的分诊,再进入改写。
6案例一:大促回刷倾斜叠加 YARN 抢占
标注:复合场景,要素来自常见生产模式组合,非单一真实事故拷贝。
6.1 背景与现象
大促后需回刷 7 天订单汇总以修正退款口径,下游 ads_trade_report 依赖 dws_order_agg
在 06:00 前就绪。常规定时 45 分钟完成;回刷窗口 02:30~05:00。集群约 120 节点,batch 队列白天占用约 40%,
凌晨 ETL 并发 30+ 作业——没有无限重试窗口。02:35 开始,03:20 Reduce 进度 76% 后多次重启;
03:58 YARN 状态 KILLED,Diagnostics 含 preemption。告警渠道包括调度失败短信、队列使用率超 85%、
Tez 最慢 task 超 40 分钟阈值。
| 工具 | 命令 / 入口 | 本次产出 |
|---|---|---|
| YARN 终态 | yarn application -status | Queue=root.batch.etl,03:58 KILLED,含 preemption |
| 队列快照 | yarn queue -status root.batch.etl | Used 82%,Max 80%,已突破弹性上限 |
| pending 枚举 | yarn application -list -appStates ACCEPTED | adhoc 12 个 ACCEPTED |
| Tez task 分布 | Tez UI → Reduce Vertex | 1/120 task 47min,其余 3~5min |
| 倾斜采样 | shop_id GROUP BY Top10 | top1 shop 占 38% 行数 |
| preempt 日志 | yarn logs | grep -i preempt | 03:45、03:52 Container preempted,03:58 Application killed |
6.2 时间线与根因
| 时刻 | 观察 | 当时误判 | 正确动作 |
|---|---|---|---|
| 02:35 | 任务提交,Map 正常 | — | 记录 applicationId |
| 03:05 | Reduce 启动,1/120 task 极慢 | 「集群负载高」 | 采样 shop_id 分布 |
| 03:20 | 进度 76%,adhoc pending 上升 | 「再加 reducer」 | 查 queue 是否触顶 |
| 03:45 | AM 第一次 restart | 「Tez bug」 | grep preempt 日志 |
| 03:58 | KILLED,Diagnostics preempt | 「纯资源问题」 | 确认倾斜仍存在 |
主根因:shop_id 严重倾斜导致单 Reduce 长尾。
放大因子:倾斜任务占用大量 Container 且运行过久,在 batch 触顶后成为抢占首选 victim;
adhoc 与 batch 未做凌晨隔离。因果链:大促流量集中 → top shop_id 哈希桶亿级 → 单 Reduce 47 分钟 →
Used 超过 Max → adhoc pending 触发监视器 → Container 回收 → AM restart 后 Application KILLED。
缺少任一环节都不会在 03:58 以 preempt 终态失败——这是典型的数据层根因 + 资源层放大复合故障。
复盘时特别容易犯「归因单一化」:平台组看到 preempt 就改队列;数据组看到 shop_id 倾斜就只改 SQL。 正确结论必须写清两句:第一句说明若不存在倾斜,作业会在抢占窗口前结束;第二句说明若存在倾斜但凌晨封网, 作业虽慢却未必被 KILL。两句都成立,才能解释为何「只改一边」在历史故障里反复失效。 工单模板建议固定填写:根因层次、放大器层次、验证指标(最慢 Reduce / Used-Max / preempt 计数)三项。
-- 两阶段聚合 + 加盐打散热点 shop_id(Hive 3.1.x)
WITH salted AS (
SELECT shop_id, CAST(RAND() * 32 AS INT) AS salt, order_amt
FROM ods_order
WHERE dt BETWEEN '2026-06-25' AND '2026-07-01'
),
partial AS (
SELECT shop_id, salt, SUM(order_amt) AS partial_sum
FROM salted GROUP BY shop_id, salt
)
SELECT shop_id, SUM(partial_sum) AS total_amt
FROM partial GROUP BY shop_id;
资源层同步:batch.etl maximum-capacity 临时上调;adhoc 00:00~06:00 禁止新提交;
preemption.max_wait_before_kill 由 15s 调到 45s。改完后回刷 52 分钟完成,最慢 Reduce 8 分钟,
preempt 归零,抽样聚合误差小于 0.01%。预防:大促前热点 key 清单、凌晨封网、Grafana 三指标同屏
(最慢 Reduce、Used/Max、preempt 计数),季度做「倾斜 SQL + 人为 adhoc 洪峰」演练。
倾斜与抢占同时出现时,先缩短长尾(SQL / 参数),再谈队列扩容。 同一 application 名每周 preempt 超过 2 次,应进入技术债 backlog,而不是依赖值班重跑。 组织层面建立「大促回刷」专项 checklist:SQL 评审、队列预留、adhoc 封网三条,避免每年重复踩坑。
| 阶段 | 案例一:倾斜 + 抢占 |
|---|---|
| 现象 | Reduce 长尾 + Application preempt KILL |
| 工具 | YARN Diagnostics、Tez task 耗时分布、倾斜 key 采样 SQL |
| 定位 | 确认倾斜 key 存在且失败前队列争用 |
| 方案 | 两阶段聚合 / 加盐;时间窗隔离;适度调抢占等待与 max-capacity |
| 预防 | 大促前倾斜演练;batch/adhoc 分队列;preempt 事件告警 |
7案例二:维表 Join 倾斜引发 Shuffle 假死
标注:复合场景。与案例一不同:终态始终 RUNNING,无 KILL,易被误判为「集群算力不足需加机器」。
7.1 现象与工具链
任务 INSERT OVERWRITE ads_user_profile:事实表 dwd_event 单日约 8 亿行 Join
维表 dim_user 约 2000 万行(CSV 外表未分桶)。开发环境数据量小未暴露问题。
上线后 YARN 状态 RUNNING 超过 2 小时,Tez UI Shuffle 进度约 12% 停滞;无 preempt 记录;
部分 NM outbound 带宽打满。队列 Used 约 55%,可排除纯资源不足。
| 工具 | 本次产出 |
|---|---|
EXPLAIN EXTENDED | Common Join,MapJoin 未触发,维表约 1.8GB 超广播阈值 |
| Tez DAG Timeline | Shuffle Vertex 占总时长 78%,Map 仅 12% |
| Counter | Spill 为基线 18 倍;单 Map output 约 1.2TB |
| Join key 分布 | user_id=0 约 1.2 亿行(占当日 15%),第二名仅约 800 万 |
| 网络监控 | 若干 NM outbound 持续 90%+,与活跃 Shuffle task 节点重合 |
7.2 根因、方案与预防
脏 key user_id=0 造成 Join 倾斜;维表未过滤无效用户;Shuffle 中间数据膨胀导致「假死」——
进度条缓慢而非单 Reduce 99%。因果链:ODS 未校验 user_id → 15% 行落入 0 值 → Common Join 同 key 海量匹配 →
单 Map 输出 TB 级 → Shuffle 网络热点 → 进度 12% 停滞两小时。仅调 Tez 参数无法消除 1.2 亿行脏 key 带来的膨胀;
仅过滤而不检查 MapJoin 仍会留下 Common Join 开销。
与案例一对照:案例一终态是 KILLED,入口必须先走 YARN;案例二终态一直是 RUNNING,入口应先走 Tez 阶段与 EXPLAIN。两者都叫「倾斜」,但证据链不同——前者用 task 耗时分布打穿 Reduce 长尾,后者用 Spill 与 Join key TopN 打穿 Shuffle 膨胀。把两套证据链写进 Runbook,比再增加一张「倾斜参数大全」更有用。
-- 过滤脏 key + MapJoin(维表可广播时)
SET hive.auto.convert.join=true;
SET hive.auto.convert.join.noconditionaltask.size=512000000;
INSERT OVERWRITE TABLE ads_user_profile PARTITION (dt='2026-08-01')
SELECT /*+ MAPJOIN(dim) */
f.event_id, f.user_id, dim.user_level, dim.register_channel
FROM (
SELECT * FROM dwd_event
WHERE dt='2026-08-01' AND user_id <> 0 AND user_id IS NOT NULL
) f
JOIN dim_user dim ON f.user_id = dim.user_id;
数据治理侧:ODS 增加 user_id 有效性规则;DWD 默认过滤 user_id=0。
优化后 Shuffle 数据量降约 62%,总耗时约 38 分钟。若维表无法广播,采用热点分离:将
user_id=0 事实行单独写入无效分区,主链路 Join 干净数据。
预防:Join Code Review 附 key 基数截图;维表体积接近广播阈值时需论证为何不 MapJoin;
Shuffle 进度长时间低位告警;SPILLED_RECORDS 超基线数倍告警;维表行数突增触发阈值重评。
Join 类任务卡 Shuffle,优先查脏 key / 空 key / 默认枚举值,再查网络。
维表接近广播阈值时应显式 MAPJOIN 或拆热点链路。分桶表仅对分布均匀的 key 有效——
脏数据不治理,分桶也救不了倾斜。测试环境应用生产级抽样(如十分之一分区)做 Join 压测,避免「小数据上线必炸」。
| 阶段 | 案例二:Join 倾斜 + Shuffle 卡顿 |
|---|---|
| 现象 | Shuffle 进度长期低位;Spill counter 异常;带宽打满 |
| 工具 | EXPLAIN EXTENDED、Tez counter、Join key 分布 SQL |
| 定位 | 脏 key 或热点 key 导致 Shuffle 数据倾斜 |
| 方案 | 过滤脏 key;MapJoin / Skew Join;拆分热点链路 |
| 预防 | ODS/DWD 数据质量规则;Join 前 key 基数评估纳入 Code Review |
8什么时候该深挖,什么时候该先止血
SOP 手册容易让人「工具上瘾」:明明 SLA 已破、下游报表已延迟,仍坚持完整采样与加盐改写。 反过来说,也有人把每次 Reduce 毛刺都当成必须改 SQL 的倾斜。边界如下——适合与不适合不是价值判断, 而是对可观测窗口与业务损失的权衡。
适合深挖本篇 SOP
- 任务仍 RUNNING 或可重跑同分区,证据窗口未关闭
- Tez UI / History 可访问,能拿到 task 分布与 counter
- 同类 SQL 近期重复失败,需要复盘与预防回流
- 怀疑倾斜诱发抢占,需要时间线交叉验证
- Join / 聚合上线前评审,主动做 key 基数评估
不适合「先分析后恢复」
- 核心报表 SLA 已破且有明确回滚 / 降级路径——先止血再离线复盘
- 整个 leaf 队列 ACCEPTED 堆积、平台组已启动紧急限流
- NM 磁盘打满导致多任务 FAILED——先清磁盘再谈倾斜
- Metastore 全局锁导致编译全停——先释放锁 / 限流 HS2
- 无 Tez History、无日志权限——应走平台侧采集,而不是空转
也建议在架构评审里把「排障证据是否可执行」列为上线检查项:调度平台是否保留 SQL 版本与 applicationId、 Tez History 是否可达、队列与 preempt 是否进监控、大促窗口是否有 adhoc 封网策略。 缺一项,值班时就会被迫在压力下临时造轮子。工具链手册只有嵌进发布与值班流程,才从「好看的文档」变成「可执行的组织能力」。
一句话决策:SLA 未破且证据链完整时,值得深挖;SLA 已破或平台级故障已宣告时,先止血、后复盘。 深挖不是「更敬业」,止血也不是「不专业」——二者对应不同损失函数。把这条边界写进值班频道置顶, 可以减少「明明该降级却还在采样」与「明明该复盘却只重跑」两类组织浪费。
9统一排障 SOP 与大促预防清单
将第三至七章收敛为一线值班可打印的总表与决策树:先资源、后阶段、再数据。 使用时先匹配「故障现象」列,再横向展开五列动作。若约 15 分钟内无法归入任一行,升级到平台组二线, 附带 applicationId、Tez DAG 截图、queue -status CSV 三件套。
| 故障现象 | 排查工具链 | 根因定位要点 | 处置方案 | 预防机制 |
|---|---|---|---|---|
| Application KILLED,含 preemption | yarn application -status;queue-status;capacity XML |
失败时刻 batch/adhoc 使用率;max-capacity;高优 pending | 时间窗隔离;调 max-capacity / 抢占等待;降 Container 碎片 | 队列月度评审;preempt 监控;批 adhoc 分队列 |
| RUNNING 数小时无产出 | Tez UI;counter;EXPLAIN EXTENDED | 定位 Map / Shuffle / Reduce;最慢 task 分布 | 按阶段调 Tez/Hive;MapJoin;合并小文件 | 上线前 Explain 审查;SLA 分级 |
| Reduce 99%,单 Task 超长 | Tez task 视图;key 分布采样 | 热点 key / 长尾 GROUP BY / 脏枚举值 | groupby.skewindata;加盐两阶段;拆分热点 SQL | 大促热点清单;DWD 脏数据规则 |
| Shuffle 进度极低,Spill 高 | SPILLED_RECORDS;网络监控 | Join 输出膨胀;脏 key | 过滤脏 key;MapJoin;调 sort buffer | Join Code Review;维表广播阈值规范 |
| 反复 AM restart,Container 骤降 | RM 日志;grep preempt | 与抢占叠加;或 NM 磁盘满 | 先治倾斜缩短任务;NM 磁盘清理 | NM 磁盘水位告警;与倾斜治理联动 |
| Map 数爆炸,单 Map 秒级结束 | hdfs dfs -count;Tez Map Vertex | 小文件过多;grouping 过小 | 合并小文件;调 grouping;分区 compact | ODS 单文件体积规范;分区文件数监控 |
| 长期 ACCEPTED,AM 无法启动 | application -list ACCEPTED;queue-status | leaf 满;AM 资源占比触顶;user-limit | 释放低优作业;临时调高 max-capacity | AM 占比监控;提交前 queue 校验 |
| 编译阶段卡住,Tez 未启动 | SHOW LOCKS;Metastore 慢查询;HS2 日志 | 元数据锁;CBO 超时;HS2 连接池耗尽 | 释放锁;ANALYZE TABLE;扩容或限流 HS2 | 锁超时告警;核心表统计定时更新 |
| Reduce OOM / Java heap space | task 日志;GC_TIME_MILLIS | 倾斜导致单 task 过大;内存参数偏低 | 先治倾斜;再调 tez.task.resource.memory.mb | 单 task input groups 基线;勿盲目加倍内存 |
| 复合:倾斜 + preempt KILL | 六步命令链 + Tez Reduce 分布 + key 采样 | 倾斜拉长作业 → 队列触顶 → 被选为 victim | 先 SQL 打散;再时间窗;最后调 max / wait | 三指标同屏;大促前倾斜演练 |
| 复合:脏 key + Shuffle 假死 | Explain + Spill + key TopN + NM 带宽 | Join 输出倾斜 → Shuffle 网络热点 | 过滤脏 key;MapJoin / 热点分离 | ODS 有效性规则;Shuffle 进度 SLA 告警 |
大促 / 回刷窗口专项预防清单
| 检查项 | 标准 | 责任人 |
|---|---|---|
| batch 队列 max-capacity 预留 | 回刷窗口内单独上调或独立 leaf 队列 | 平台组 |
| adhoc 凌晨封网 | 00:00~06:00 无新 adhoc 提交或自动 kill | 平台组 / 调度 |
| 热点 key 清单 | 大促店铺 / 类目已登记,SQL 已加盐或两阶段 | 数据开发 |
| preempt 告警 | 接入监控,值班 Runbook 含第三章 SOP | SRE |
| 回刷结果校验 | 与抽样聚合误差小于业务阈值 | 数据开发 / QA |
将上表粘贴到内部 Wiki 时,建议在「工具」列加跳转链接:RM UI、Tez History、调度平台作业详情。 「预防」列绑定责任人:平台组负责队列与 preempt 监控,数据组负责倾斜 key 清单与脏数据规则, 应用组负责 SQL Explain 审查。三角责任清晰后,复合故障才不至于在值班电话里变成「三方互相等待」。
统一 SOP 的使用纪律有三条。第一,十五分钟内必须落到表中某一行;落不下去就升级,而不是继续「再试一个参数」。 第二,任何临时调参(max-capacity、抢占等待、广播阈值)必须写回滚时间与责任人,避免大促窗口结束后配置漂移。 第三,复合行(倾斜 + preempt、脏 key + Shuffle)优先于单行——若现象同时命中两行,按复合行的处置顺序执行, 不要并行开两套互相打架的改动。这三条比记住表里每一个单元格更重要。
10决策矩阵与相邻知识地图
10.1 决策矩阵
| 现场信号 | 继续当前 SOP | 切换 / 并行另一 SOP | 选型式处置(临时) |
|---|---|---|---|
| KILLED + Diagnostics 含 preempt | 第三章六步闭环 | Tez 最慢 Reduce 极长 → 并行第六章倾斜治理 | adhoc 封网 / 临时上调 max-capacity |
| RUNNING,Reduce 单 Task 99% | 第五章四步法 + 第六章手法 | 同时队列触顶 → 并行抢占链路 | 先杀无关 adhoc,再改 SQL 重跑 |
| RUNNING,Shuffle 个位数,Spill 倍增 | 第七章 Join / 脏 key 路径 | 队列 Used 很高 → 排除纯资源误判后仍查数据层 | 限流并发 Join;降级跳过非关键维表字段 |
| Map 数爆炸,均匀极慢 | 第四章 Map / 小文件路径 | 伴随 ACCEPTED 堆积 → 回资源层 | 调 grouping;合并输入;收紧分区 |
| 编译卡住,Tez 未启动 | Metastore / HS2 路径 | 与任务倾斜无关,勿套用加盐 | 释放锁;限流 HS2;补统计信息 |
| 连续 3 次同 SQL preempt | 停止「只重跑」 | 升级二线 + 进入倾斜技术债 | 窗口外重调度;临时独立队列 |
10.2 相邻知识地图
- T01 Hadoop 核心架构:理解 YARN 队列与 Container 生命周期,才能读懂抢占 Diagnostics。
- T02 Hive 深度实战:Explain、MapJoin、倾斜参数与建表规范的「怎么写对」侧。
- T03 离线数仓分层:把脏 key 过滤与质量规则前移到 ODS / DWD,从排障走向预防。
- T19 Kafka 高可靠:若故障表现为实时链路堆积而非离线 Tez,切换到消息积压 SOP。
三类故障共享「现象 → 工具 → 定位 → 方案 → 预防」五列,但不共享入口。 值班价值不在于记住更多参数,而在于缩短「从并发告警到正确层次」的路径。 框架训练可迁移;现场数字必须用本集群复测。把决策矩阵贴在值班频道置顶,比再开一次参数培训更能减少无效重跑。
决策矩阵的读法是「先匹配现场信号,再选继续 / 切换 / 临时选型」三列,而不是从上到下背诵。 「继续当前 SOP」表示证据链仍闭合;「切换 / 并行」表示出现跨层信号,需要同时采集另一层证据; 「选型式处置」是止血动作,事后必须回到对应章节做根因闭环。若团队只执行第三列而不回写第一、二列, 会把临时手段固化成长期配置债。
10.3 带走什么
- 排障顺序:先分资源 / 执行 / 数据 → 再选工具 → 时间线对齐 → 处置 → 预防。
- 抢占常是症状在资源层、病因可能在数据层;单纯加并行度可能更易成为 victim。
- Hive Tez 卡顿必须定位到 Map、Shuffle、Reduce;「99%」与「Shuffle 12%」指向不同方案。
- 倾斜识别四步法(确认 → 定位 key → 选手法 → 验证)可脱离具体案例复用。
- 两个复合案例共性:热点 / 脏 key 是数据层元凶;队列与时间窗是放大器;处置顺序是缩短长尾 → 隔离资源 → 调参。
- 本篇结论基于 Hadoop 3.3.x + Hive 3.1.x + Tez;MR 引擎阶段名不同但分层思路仍适用。
本篇以复合场景串联抢占、卡顿与倾斜,核心交付是三层排障地图 + 双案例全链路 + 统一 SOP + 决策矩阵。 下一步请把 SOP 表与决策树写进团队 Runbook,并在大促前完成一次「倾斜 SQL + adhoc 洪峰」演练—— 那一页纸与那次演练,才是这篇分享真正留下的东西。若只有三十分钟阅读时间,请优先带走:先读 YARN Diagnostics、 再看 Tez 阶段、倾斜四步法,以及「先缩短长尾再调队列」的处置顺序。
最后提醒版本边界:文中命令与参数名以 Hadoop 3.3.x、Hive 3.1.x、Tez 为准;若集群仍跑 Fair Scheduler 或 MapReduce 引擎,抢占触发条件与 UI 入口会不同,但「先资源、后阶段、再数据」的分层不变量仍然成立。 迁移到 Spark SQL 时,也可把同一决策树映射到 Stage / Shuffle Read 视图,只是工具名需要替换。