5.3 容错与推测执行


文档摘要

5.3 容错与推测执行 本节摘要:上千台机器跑几小时,故障不是意外而是常态。MapReduce 用三层机制兜底:任务级重试(幂等重放)、作业级重算(依赖图上溯)、计数器心跳(死亡判定)。另一侧的敌人是"慢"——推测执行让快机器抢跑同一任务的备份副本,谁先完成用谁。本节推演各机制的触发条件与代价,给出参数与关闭时机,并算一笔推测执行的收益账。 故障模型:要对付哪几类死法 先列清楚敌人名单,机制才有靶子: 故障类型 | 表现 | 检测者 任务进程崩溃 | JVM 异常退出、抛运行时异常 | 框架捕获退出码 节点失联 | 心跳超时 | JobTracker 或 ResourceManager 磁盘坏 | 写失败、校验错 | HDFS 校验和、任务失败 慢机器 | 任务远超同侪耗时 |

5.3 容错与推测执行

本节摘要:上千台机器跑几小时,故障不是意外而是常态。MapReduce 用三层机制兜底:任务级重试(幂等重放)、作业级重算(依赖图上溯)、计数器心跳(死亡判定)。另一侧的敌人是"慢"——推测执行让快机器抢跑同一任务的备份副本,谁先完成用谁。本节推演各机制的触发条件与代价,给出参数与关闭时机,并算一笔推测执行的收益账。

故障模型:要对付哪几类死法

先列清楚敌人名单,机制才有靶子:

故障类型 表现 检测者
任务进程崩溃 JVM 异常退出、抛运行时异常 框架捕获退出码
节点失联 心跳超时 JobTracker 或 ResourceManager
磁盘坏 写失败、校验错 HDFS 校验和、任务失败
慢机器 任务远超同侪耗时 推测执行机制
提交者宕机 调度中枢本身死机 经典架构无解,YARN 半解

核心洞察来自 1.1 节的老命题:廉价商用机集群里,故障率乘以规模就是必然事件。容错不是可选项,而是这个范式得以成立的入场费。

第一层:任务重试与幂等重放

任务失败的处理简单粗暴:换台机器重跑。默认每个任务允许失败 4 次(超过则整个作业失败),因为偶发错误(硬件抖动、JVM 崩溃)重跑大概率就好。

重跑能成立的前提藏在 3.1 节的流水线里:Map 任务的输入是只读的 HDFS 分片,输出是可整体删除重建的中间文件——任务的每样东西都从持久化输入确定性推导而来。这就是幂等重放的根基:同一任务跑一百遍,产物一模一样,跑几遍无所谓。用户代码要守住两条军规才能不破坏这份礼物:

其一,map 与 reduce 不写外部副作用(不直连数据库、不发外部请求),副作用只通过 context.write 出口;其二,不依赖不可重放的输入(比如读系统时钟做分区、用随机数做键)。

一个反例推演:某 ETL 作业在 map 里直接向 MySQL 插数据。任务第一次跑到一半崩溃,已插入 12 万行;框架重跑,又插 12 万行(其中 7 万与上次重叠)。数据库里出现重复数据,且故障越多重越乱。正确做法是写 HDFS 输出文件,由独立的全量导出流程同步到数据库。口诀:副作用出文件,文件再出世界

对连续失败还有一种诊断思路:同一个任务换个节点就失败,多半是坏节点;到哪个节点都失败,多半是数据本身有毒(脏行触发解析异常)。框架默认参数也体现了这个区分:

<property> <name>mapreduce.map.maxattempts</name> <value>4</value> </property> <property> <name>mapreduce.reduce.maxattempts</name> <value>4</value> </property> <property> <name>mapreduce.map.failures.maxpercent</name> <value>0</value> </property>

failures.maxpercent 允许放宽到比如 5%——容忍少量毒数据任务失败换整体结果可用,适合"宁可少 1% 数据也要今天出报表"的离线场景。

第二层:作业重算与上溯依赖

Reduce 任务依赖所有 Map 的输出。若某个 Map 任务所在节点的磁盘在中途损坏(中间文件没了),或任务历史被清理,Reduce 拉不到数据怎么办?框架的答案是沿依赖图上溯重算:把丢失中间输出的那个 Map 任务重新调度执行。更极端的情况——作业级失败(如输出目录被误删)——则整个作业重提。

重算的代价与丢失的环节成正比,好在 HDFS 层已经挡掉了大部分底层故障:DataNode 定期向 NameNode 块报告,副本丢失由副本复制自动补齐,块级故障根本上升不到计算层。分层的防御部署是:

HDFS 层 块损坏 → 校验和发现 副本补读 成本最低 HDFS 层 节点掉线 → 其余副本重平衡 无需计算层知情 计算层 任务崩溃 → 重跑该任务 秒到分钟级 计算层 中间丢失 → 重算上游 Map 分钟级 计算层 调度中枢 → 经典架构单点作业丢 YARN 可恢复部分

YARN 相比经典架构的改进正在最后一行:ResourceManager 重启后可从状态存储恢复运行中应用的信息,JobHistoryServer 保住已完成作业的历史——单点故障从"全集群作业蒸发"缓解为"个别作业重提"。

第三层:心跳、超时与计数器

死亡判定靠心跳。经典架构里 TaskTracker 周期性向 JobTracker 汇报,YARN 里 NodeManager 向 ResourceManager 汇报。任务层面另有一条细粒度心跳:任务进程定期汇报进度与计数器,超时不报即判定挂死(即使进程还活着)。默认超时 10 分钟:

<property> <name>mapreduce.task.timeout</name> <value>600000</value> </property>

这个参数的两难值得体会:设太短,一个长时间的 GC 停顿或复杂单条记录就误杀任务,引发重跑风暴;设太长,真死锁的任务占着槽位拖小时级。经验是:代码里对耗时操作主动汇报进度context.progress() 或累加自定义计数器),让超时可以设短,而不是把超时设长。

计数器因此在容错里客串了看门狗角色,本职则是可观测性:框架计数器(如 MAP_INPUT_RECORDSSHUFFLE_BYTES)与用户计数器(枚举名加计数)都会汇入心跳与作业日志。一个实用手法:给脏数据建计数器,作业跑完看数字决定是否放宽失败百分比——比翻日志快得多。

慢的对策:推测执行

故障之外,"没死但慢"同样致命:一台磁盘降速的机器、一台被别的进程抢走 CPU 的机器,会让它的任务耗时五倍于同侪,而作业必须等最慢的那个。MapReduce 的解法带点丛林法则:调度器发现某任务进度显著落后于同批任务的平均值,就在另一台空闲机器上启动同一任务的备份副本,两副本赛跑,谁先完成采用谁,另一个被杀

图 5.3-1 推测执行三种世界的时间线

图 5.3-1 推测执行三种世界的时间线

几个常被误解的点要掰正:

备份不是断点续传,是从头重跑整个任务,所以只有"重跑成本低于等待收益"时划算——判断依据正是进度落后比例与同批平均速度的估算。

reduce 不太值得抢跑:Reduce 的 copy 阶段依赖 Map 输出,中途情况多变,进度估算噪声大,误抢会白白复制数据。实践中常保留 Map 侧推测、视集群情况关 Reduce 侧。

幂等依旧是大前提:两副本并发写外部副作用(那个 MySQL 反例)会双倍放大事故;写 HDFS 输出文件则天然安全,因为最终只认先完成者的文件。

开关与阈值:

<property> <name>mapreduce.map.speculative</name> <value>true</value> </property> <property> <name>mapreduce.reduce.speculative</name> <value>false</value> </property>

什么时候该关掉 Map 侧推测?两类场景:任务本身高度同质且短(重跑纯属浪费),或者集群已满载(没有空闲机器可抢跑,备份只会加剧排队)。看作业计数器里的 LAUNCHED_SPECULATIVE_TASKSSPECULATIVE_TASKS_REDUCTIONS_SAVED 一对数字,就能评估本集群推测执行是赚是赔。

算一笔综合账

设 1000 个 Map 任务、平均 10 分钟,其中一台坏机器让它的任务跑 50 分钟。无容错的理想世界总时长 10 分钟;有拖尾无推测则 50 分钟;开启推测后备份副本在健康节点 10 分钟完成,总时长 11 分钟。再叠加节点彻底宕机情形:心跳 10 分钟判死、任务重跑 10 分钟,总时长 21 分钟——仍是故障场景里的优秀成绩。容错与推测的共同底层逻辑只有一句:输入是持久且只读的,输出是可丢弃可重建的,所以任何一段计算都可以安全地重来或赛跑

本节自查

  1. 说出任务可安全重跑的两个前提,并各举一个破坏前提的反例;
  2. 区分"任务失败 4 次作业失败"与"放宽失败百分比"两种策略各自的适用业务;
  3. 推演:map 里发外部 HTTP 请求且开启推测执行,最坏会发生什么;
  4. 解释为什么 reduce 侧推测执行的性价比通常低于 map 侧;
  5. 给"任务卡 30% 但进程健在"与"任务节点心跳消失"两种故障各指出判定机制与恢复动作。

至此第五落幕,舞台机关全部亮过相。下一章进入实战:用完整三幕解真实计算题。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U