外观
推测执行与长尾任务
难度与前置
难度:进阶
前置:知道 Stage 被最慢 Task 拖死;接触过 spark.speculation。
读完能做什么:区分真长尾与数据倾斜,合理开关 speculation,用 p99/max 任务时长与重复计算成本验收。
适用场景
- Stage 进度 99%,剩 1~2 个 Task 跑很久
- 集群有坏盘/慢节点,偶发拖垮批作业
- 评估是否打开
spark.speculation及阈值 - 打开后 CPU 飙升、重复写,需要治理
不适用:
- 明显数据倾斜(先治倾斜,见 Spark Join 倾斜)
- 输出不可幂等且 speculation 会导致双写冲突(先改写出)
- 秒级小作业(开销可能大于收益)
真实场景(一条)
日批 ETL 常在最后 2 个 Task 卡 40 分钟。打开 speculation 后偶发变快,但某周出现大量 duplicate task,集群 CPU 打满,作业反而更慢。排查:一半真慢节点,一半是倾斜 key。治理:倾斜侧加盐;speculation 仅对「无倾斜证据」的队列开启,并收紧 multiplier/quantile;写出改为分区覆盖幂等。p99 Stage 时长下降,重复 Task 占比可控。
怎么做(步骤清单)
1. 先看 Task 耗时分布,再谈 speculation
Spark UI → Stage → Tasks:
| 现象 | 更可能原因 | 优先动作 |
|---|---|---|
| 少数 Task 输入字节极大 | 数据倾斜 / 分区不均 | 倾斜治理、重分区 |
| 输入相近但耗时差一个数量级 | 慢节点 / GC / 本地 IO | speculation、隔离黑名单 |
| 大量 Task 失败重试 | 代码/资源/超时 | 修根因,不靠赌重跑 |
| Shuffle 读阻塞 | 网络/服务端 | 查 shuffle 服务与反压 |
text
经验:speculative 适合「同输入、异环境」;
不适合「同代码、异数据量」。2. 关键配置(先懂参数)
scala
// spark-defaults / 作业级
spark.speculation = true
spark.speculation.multiplier = 1.5 // 超过中位数倍数视为慢
spark.speculation.quantile = 0.75 // 参考分位
spark.speculation.minTaskRuntime = 60s
spark.speculation.interval = 5s要点:
multiplier过小 → 误杀正常尾巴,浪费资源minTaskRuntime过小 → 短任务频繁双跑- 与动态分配同时开时,注意资源被投机任务占满
3. 定位长尾 Task
bash
# 从事件日志/UI 导出最慢 task(示意)
# 关注:executorId、host、inputBytes、shuffleRead、GC time、错误栈
echo "task_id,host,input_mb,duration_sec,gc_sec"对照:
- 同一 host 是否反复出现慢 Task → 节点问题
- input/shuffle 是否显著更大 → 倾斜
- GC 占比是否过高 → 内存/对象生命周期(见 OOM)
- Spill 是否严重 → 见 Spill
4. 慢节点侧手段(不仅靠 speculation)
- YARN/K8s 黑名单或淘汰坏盘节点
- 本地盘健康、网络丢包检查
- 避免「超卖极端」导致个别 executor 饿死
- 关键
spark.excludeOnFailure等(版本以官方为准)
5. 倾斜侧:先削峰再谈尾巴
sql
-- 分区键基数与热 key(示意,离线探查)
SELECT part_key, COUNT(*) AS cnt
FROM stage_input_sample
GROUP BY part_key
ORDER BY cnt DESC
LIMIT 20;热 key 用加盐、两阶段聚合、AQE skew join 等;speculative 只会双倍烧 CPU。
6. 写出幂等(speculation 安全前提)
投机任务可能同时写:
- 使用任务临时目录 + commit 协议(Spark 正式输出机制)
- 业务表:分区
INSERT OVERWRITE/ 动态分区覆盖约定清晰 - 避免「追加且无唯一键」的外部系统无幂等 sink
text
危险:foreach 写 JDBC 无主键去重 + speculation
安全:写 Hive 分区覆盖;或幂等 upsert7. 观察重复计算成本
打开后监控:
- Speculative 启动次数、成功「抢赢」比例
- 集群 CPU / 队列等待
- 作业总 vcore·小时是否上升超过收益
bash
echo "job_id,stage_id,speculative_launched,speculative_won,stage_max_sec,stage_p50_sec"8. 分级策略建议
| 队列/作业类型 | 建议 |
|---|---|
| 稳定批、无倾斜史 | 可开,multiplier 保守 |
| 已知倾斜大盘 | 先关或仅非 Join Stage |
| 交互小作业 | 默认关 |
| 写外部非幂等 | 关,直至写出改造完成 |
常见坑(至少牢记这些)
倾斜当节点慢,狂开 speculation
热 key Task 双跑双挂,账单与失败率双升。multiplier 设成 1.1
正常方差也被赌跑,资源抖振。非幂等 Sink + speculation
重复写入外部库,数据污染难追。只看平均 Task 时间
Stage 由 max 决定;看 max 与 p99。忽略 GC 与 Spill
表象是长尾,根因是内存与落盘。全公司默认 true 无例外清单
必须有作业类型白名单/黑名单。
如何验收(可量化)
- [ ] 长尾 Case 分类:倾斜 vs 节点 vs GC/Spill 有结论
- [ ] 打开 speculation 的作业:Stage max 下降,且 speculative 浪费比可接受
- [ ] 写出路径验证无重复脏数据
- [ ] 倾斜作业在治理后不再依赖 speculative 硬扛
- [ ] 默认策略写入团队规范
bash
# 同作业同数据回放对比
echo "run,speculation,stage_max_sec,vcore_hours,output_dup_check"
# before,false,2400,12.0,PASS
# after,true,900,13.5,PASSsql
-- 若输出表有唯一键:重复应为 0
SELECT biz_key, COUNT(*) c
FROM target_table
WHERE dt = '${biz_date}'
GROUP BY biz_key
HAVING COUNT(*) > 1
LIMIT 20;相关面试题
占位:与题库小程序对齐后补链接(建议关联:Spark speculation、straggler、数据倾斜、Stage 长尾)。
改写说明:结构与判断标准参考 Wood 原创数仓/面试文,已改写成可执行清单;未包含未公开公司数据与内部表名。