Skip to content

推测执行与长尾任务 ​

难度与前置

难度:进阶
前置:知道 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 / 本地 IOspeculation、隔离黑名单
大量 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"

对照:

  1. 同一 host 是否反复出现慢 Task → 节点问题
  2. input/shuffle 是否显著更大 → 倾斜
  3. GC 占比是否过高 → 内存/对象生命周期(见 OOM)
  4. 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 分区覆盖;或幂等 upsert

7. 观察重复计算成本 ​

打开后监控:

  • Speculative 启动次数、成功「抢赢」比例
  • 集群 CPU / 队列等待
  • 作业总 vcore·小时是否上升超过收益
bash
echo "job_id,stage_id,speculative_launched,speculative_won,stage_max_sec,stage_p50_sec"

8. 分级策略建议 ​

队列/作业类型建议
稳定批、无倾斜史可开,multiplier 保守
已知倾斜大盘先关或仅非 Join Stage
交互小作业默认关
写外部非幂等关,直至写出改造完成

常见坑(至少牢记这些) ​

  1. 倾斜当节点慢,狂开 speculation
    热 key Task 双跑双挂,账单与失败率双升。

  2. multiplier 设成 1.1
    正常方差也被赌跑,资源抖振。

  3. 非幂等 Sink + speculation
    重复写入外部库,数据污染难追。

  4. 只看平均 Task 时间
    Stage 由 max 决定;看 max 与 p99。

  5. 忽略 GC 与 Spill
    表象是长尾,根因是内存与落盘。

  6. 全公司默认 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,PASS
sql
-- 若输出表有唯一键:重复应为 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 原创数仓/面试文,已改写成可执行清单;未包含未公开公司数据与内部表名。