ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

第 6 篇:「失败之后谁来补救」—— 容错、Lineage 与重建

第 6 篇:「失败之后谁来补救」—— 容错、Lineage 与重建 仓库https://github.com/ray-project/ray官方文档https://docs.ray.io/en/latest/ray-core/fault-tolerance.html技术栈CObjectRecoveryManager · TaskManager · ReferenceCounter · GcsActorManager解读版本master 3281306dd05f033016e152339137760c0b14c524解读视角总架构师评审架构 / 源码 / 生产 / 进阶精品标识v2.0 打穿 C 容错闭环owner 模型 → lineage 重建四步算法 → 按错误类型区分的重试退避 → 五类 ObjectLostError 归因第 6 篇「失败之后谁来补救」—— 容错、Lineage 与重建阅读本文你将了解Ray 能自动恢复数据丢失但不能恢复 owner 死亡——源码里这条边界写在ObjectRecoveryManager::RecoverObject的第一行注释里src/ray/core_worker/object_recovery_manager.h:71-73。官方讲的 lineage reconstruction在源码里是一段写死在注释中的四步算法:71-85查缺失 → 找副本并 pin → 重提交任务 → 递归恢复参数。官方与源码逐条对应。ray.put出来的对象不可恢复不是策略选择而是机制限制重建靠重执行创建它的 task而ray.put没有 task 可重跑。重试退避按错误类型区分OOM 走指数退避基数 1000ms:531普通错误走固定延迟默认 0:527Actor 不可用走 100ms→5000ms 的退避:535/:538——GetTaskRetryDelayMssrc/ray/core_worker/task_manager.h:61。官方列的 5 种ObjectLostError子类各自对应一条不同的源码路径可以按错误信息反查根因。lineage 不是免费的max_lineage_bytes默认1GB:224超限由SetReleaseLineageCallbacksrc/ray/core_worker/reference_counter.h:168驱逐。6.0 一句话定性Ray 的容错设计有一条清晰主线用重算代替复制用owner 元数据代替全局副本表。对象不靠多副本保命那太贵而是靠记住它是怎么算出来的lineage丢了就重算一遍。代价是三件事任务必须确定且幂等重算链条可能很长递归恢复参数owner 一死就全盘失效元数据没了谁也不知道该怎么重算。理解了这条主线官方文档里那些限制就都不是随口一提的注意事项而是这套设计的必然推论。6.1 架构视角Architect两类失败与四道防线官方把失败分成两类fault-tolerance.html类别成因Ray 的应对application-level用户代码 bug、外部系统故障抛异常 重试可配置 用户手动兜底system-level节点故障、网络故障、Ray 自身 bug自动恢复lineage 重建、Actor 重启、节点摘除对应到实现Ray 有四道防线各自归属不同模块防线触发条件责任模块关键入口① 任务重试任务执行失败/所在节点挂TaskManagerFailOrRetryPendingTasksrc/ray/core_worker/task_manager.h:610② Lineage 重建对象值丢失ObjectRecoveryManagerRecoverObjectsrc/ray/core_worker/object_recovery_manager.h:92③ Actor 重启/重建Actor 所在 worker 或节点挂GcsActorManagerRestartActorsrc/ray/gcs/actor/gcs_actor_manager.h:339④ 节点摘除节点心跳/探测失败GcsNodeManagerGcsHealthCheckManagerOnNodeFailuresrc/ray/gcs/gcs_node_manager.h:122这四道防线是层层递进的节点挂了④→ 其上的 Actor 要重建③→ Actor/Task 产出的数据丢了要重算②→ 重算过程中任务自己也可能失败①。第 5 篇讲过 ③④ 的 GCS 侧本篇重点在①②以及它们与 owner 模型的关系。6.2 owner 模型一切容错的地基官方文档的定义The owner of an object is the worker process that creates the originalObjectRef, e.g., by callingf.remote()orray.put().关键区分owner ≠ 值的生产者。f.remote()的 owner 是调用方driver而值可能由集群里任意节点上的 worker 算出来。源码侧ReferenceCounter就是这套模型的账本src/ray/core_worker/reference_counter.h方法行号作用OwnedByUs:74判断对象是否由本进程拥有AddOwnedObject:100登记新拥有的对象GetOwner/HasOwner:135/:139查询 ownerAddLocalReference/RemoveLocalReference:76/:79本地引用增减AddBorrowedObject:130登记借来的引用跨进程传递UpdateSubmittedTaskReferences:83提交任务时更新引用UpdateResubmittedTaskReferences:89重提交时更新引用lineage 重建配套UpdateFinishedTaskReferences:92任务完成时更新引用FreePlasmaObjects:151释放 plasma 对象AddObjectRefDeletedCallback:159注册引用被删除回调03 篇讲过为什么 owner 模型是容错的地基因为重建需要两个信息这个对象的元数据在哪owner 持有、这个对象是怎么算出来的lineage也在 owner 手里。owner 一死两样都没了——这就是为什么官方说Ray can automatically recover from data loss but not owner failure。源码里这句话被写成了RecoverObject的第一步检查:72-731. Check that the object is missing from the direct memory store and that we own the object. If either is false, then fail the recovery operation.注意we own the object——重建只能由 owner 自己发起。别的节点发现对象丢了只能去找 owner。6.3 Lineage 重建官方说法 ↔ 源码四步算法官方文档Ray will first automatically attempt to recover the value by looking for copies of the same object on other nodes. If none are found, then Ray will automatically recover the value by re-executing the task that previously created the value. Arguments to the task are recursively reconstructed through the same mechanism.源码RecoverObjectsrc/ray/core_worker/object_recovery_manager.h:92的注释把这段自然语言精确展开成四步:71-85步源码注释原文要点对应方法1确认对象确实缺失、且由本进程拥有否则失败RecoverObject:922在全局目录中查找其它副本位置有副本就尝试pin住它成功即写入本地内存存储PinExistingObjectCopy:1033所有位置都 pin 失败或根本没有位置→重提交创建该对象的 task若是未完成的 streaming generator 则先取消再重提交ReconstructObject:1084重提交成功后递归恢复该 task 的 plasma 参数task 完成并写入新值时恢复才成功PinOrReconstructObject:98三个只有读源码才知道的工程细节重建是幂等的:62同一对象上的重叠恢复操作不会重复触发。AddOwnedObject与内部状态保证这一点。重建是异步的:67-69重提交后不等待完成相关对象在被使用时才阻塞解析。这意味着ray.get的等待时间可能包含整条重算链。失败通过回调通知:38-39ObjectRecoveryFailureCallback带rpc::ErrorType reason与pin_object两个参数——前者就是官方那几种错误类型§6.9后者区分是 pin 失败还是重建失败。6.4 什么能重建、什么不能限制的源码解释官方列了四条限制每一条都能在源码里找到机制性原因官方限制机制原因对象及传递依赖必须由 task 生成 →ray.put的对象不可恢复重建的本质是重执行创建它的 task第 3 步。ray.put没有 task只有一次直接写入无处可重跑假设 task 确定且幂等 →默认 actor task 结果不可重建需max_task_retries非 0Actor 有状态重跑同一方法可能得到不同结果。Ray 不替你做这个假设除非你显式声明可以重试只会重执行到最大重试次数非 actor task 默认3 次actor task 默认0由 task spec 的 retry 字段控制见 §6.5owner 必须存活元数据与 lineage 都在 owner§6.2一个容易忽略的推论ray.put的对象不可恢复但ray.put的 owner 是调用方。所以第 5 篇与官方都强调那个反模式——在 task 内部ray.put()再返回ObjectRef会让对象的 owner 变成那个 task 的 worker而 task 一结束 worker 可能就没了 →OwnerDiedError。正确做法是直接返回值让 driver 当 owner。6.5 任务重试退避策略按错误类型区分这是本篇最实用、也最藏在源码里的一节。GetTaskRetryDelayMs(attempt_number, error_type)src/ray/core_worker/task_manager.h:61的注释:51-59写明OOM 错误指数退避基数task_oom_retry_delay_base_ms其它错误固定延迟task_retry_delay_ms实际源码有三条分支对应三类错误错误类型退避策略配置项默认值位置OOM指数退避task_oom_retry_delay_base_ms1000 mssrc/ray/common/ray_config_def.h:531Actor 不可用指数退避带上限task_actor_unavailable_retry_delay_base_ms/_max_delay_ms100 ms → 5000 ms:535/:538其它固定延迟task_retry_delay_ms0 ms:527为什么 OOM 要指数退避因为 OOM 通常意味着内存还没释放立刻重试只会再 OOM 一次。指数退避给系统留出回收时间。而其它错误如用户代码异常默认 0 延迟——因为重试大概率能立刻成功加延迟纯属浪费。这个差异化设计很能体现 Ray 的工程成熟度。生产上如果遇到任务失败后疯狂重试打满集群可以设task_retry_delay_ms加一点固定延迟。6.6 任务失败的处理路径TaskManager 方法矩阵方法行号语义FailOrRetryPendingTask:610待定任务失败时的主入口能重试就重试否则失败RetryTaskIfPossible:607判断是否还有重试额度并触发重试FailPendingTask:617直接标记失败不再重试MarkTaskReturnObjectsFailed:630把该任务的返回对象标记为失败 → 触发ObjectRecoveryFailureCallbackMarkTaskNoRetry:639显式标记为不可重试MarkGeneratorFailedAndResubmit:605streaming generator 失败后取消并重提交对应RecoverObject第 3 步的 generator 分支AsyncRetryTaskCallback:80异步重试回调类型方法名里的 “OrRetry” 是本节的题眼Ray 的任务失败处理默认先尝试重试只有确认无额度或显式MarkTaskNoRetry才真正失败。MarkGeneratorFailedAndResubmit:605值得单独提流式 generator 是有中间状态的失败后不能简单重跑必须先取消再重提交——这就是RecoverObject注释第 3 步里那句“If the task is a streaming generator task that has been pushed to the worker and hasn’t finished, cancel the task and resubmit it”。6.7 Actor 容错重启restart与重建recreate不是一回事Actor 有两个独立参数官方文档里容易混淆参数控制什么失败表现max_restartsActor进程最多重启几次超限后 Actor 永久死亡调用方收到RayActorErrormax_task_retriesActor 的单个方法最多重试几次影响 §6.4 里actor task 结果是否可重建区别很关键max_restarts管的是这个 Actor 还能不能活max_task_retries管的是它算出来的值能不能被重算。GCS 侧的入口第 5 篇已列此处补齐容错相关方法行号作用HandleRestartActorForLineageReconstructionsrc/ray/gcs/actor/gcs_actor_manager.h:126为 lineage 重建而重启 Actor——重建依赖的 actor task 时走这里RestartActor:339重启指定 ActorDestroyActor:317销毁PollOwnerForActorRefDeleted:302轮询 owner 是否已删除引用detached actor 的生命周期依据OnNodeDead/OnWorkerDead:176/:188节点/worker 死亡时的响应SchedulePendingActors:166调度等待中的 Actor重启后重新走一遍调度而 Actor重建的凭据就存在第 5 篇讲过的actor_task_spec_table_src/ray/gcs/gcs_table_storage.h:224——Actor 的创建参数TaskSpec。没有这张表Actor 死了就只能让应用自己重建官方示例中手动Actor.remote()那种做法。HandleRestartActorForLineageReconstruction:126是全篇最能体现lineage 贯穿一切的一个方法当某个对象需要重建、而它的生产者是 actor task 时Ray 会去 GCS 请求重启那个 Actor然后重跑那个方法。整条链路跨了 CoreWorker → GCS → Raylet 三个组件。6.8 对象丢失的错误类型图谱按错误信息反查根因官方列了 5 种错误每种对应一条不同的源码路径异常官方语义源码/机制落点OwnerDiedErrorowner 进程死亡RecoverObject第 1 步检查we own the object失败src/ray/core_worker/object_recovery_manager.h:72-73owner 侧ReferenceCounter随之销毁ObjectReconstructionFailedError因 §6.4 的限制无法重建ObjectRecoveryFailureCallback:38被调用带rpc::ErrorType reasonReferenceCountingAssertionError对象已被删除分布式引用计数已归零官方标注为已知边缘 caseissue #18456ObjectFetchTimedOutError从远端拉取副本超时fetch_fail_timeout_milliseconds默认600000 ms 10 分钟src/ray/common/ray_config_def.h:332ObjectLostError通用创建成功但无副本可达禁用 lineage 重建RAY_TASK_MAX_RETRIES0时的兜底错误排查口诀先看是谁的错owner 活着没再看能不能重算task 生成的吗、有重试额度吗最后看是不是系统慢fetch 超时。6.9 Lineage 的内存代价与驱逐Lineage 不是免费的。官方Lineage reconstruction can cause higher than usual driver memory usage because the driver keeps the descriptions of any tasks that may be re-executed in case of failure. To limit the amount of memory used by lineage, set the environment variableRAY_max_lineage_bytes(default 1GB).源码对应机制位置默认值lineage pinning 总开关lineage_pinning_enabledsrc/ray/common/ray_config_def.h:208truelineage 内存上限max_lineage_bytes:2241024×1024×1024 1 GB释放回调SetReleaseLineageCallbacksrc/ray/core_worker/reference_counter.h:168—机制链条ReferenceCounter负责记账 → 超限 → 通过SetReleaseLineageCallback:168注册的回调驱逐 lineage → 被驱逐的 lineage 对应的对象不再可重建之后丢失就真的丢了。生产含义如果你的 driver 长期运行且提交海量任务lineage 可能撞上 1GB 上限。此时不会报错但部分老对象会静默失去可重建性——直到某天节点故障你才发现某些对象恢复不了。监控 driver 内存、必要时调大RAY_max_lineage_bytes是长生命周期应用必须做的一件事。另外max_direct_call_object_size:274默认100 KB也值得一提小于该值的对象直接内联返回不进 plasma因此根本不存在丢失问题——这是最廉价的容错。6.10 节点失败的连锁反应第 5 篇讲过节点失败判定GcsNodeManager::OnNodeFailuresrc/ray/gcs/gcs_node_manager.h:122、GcsHealthCheckManager::FailNode:109。这里补齐下游连锁节点被摘除 →src/ray/gcs/gcs_resource_manager.h:102的OnNodeDead清掉该节点的资源账本该节点上的 Actor →GcsActorManager::OnNodeDeadsrc/ray/gcs/actor/gcs_actor_manager.h:176触发重启/重建RestartActor:339该节点上的对象副本 → 变为不可达 → 有人ray.get时触发RecoverObjectsrc/ray/core_worker/object_recovery_manager.h:92PlacementGroup →src/ray/gcs/gcs_placement_group_manager.h:155的OnNodeDead重新装箱关键的时间差节点判定失败第 1 步与对象真正被重建第 3 步之间取决于谁先去ray.get那个对象。Ray 不做主动扫描式重建——是用到才恢复的惰性策略这也印证了 §6.3 的重建是异步的。另一个反直觉的点官方说running Ray tasks and actors remain alive是指没受影响的。真正在故障节点上的任务会走FailOrRetryPendingTasksrc/ray/core_worker/task_manager.h:610重试到别的节点——前提是它的资源需求不绑定特定节点§6.12 反模式二。6.11 生产实践Production两个必须避免的反模式官方文档点名反模式一让 ObjectRef 活过它的 owner# 错误task 内 ray.put 再返回 → owner 是 task 的 workerray.remotedefa():x_refray.put(1)returnx_ref# owner worker 一死 → OwnerDiedError# 正确直接返回值 → owner 是 driver可 lineage 重建ray.remotedefa():return1反模式二使用只有特定节点能满足的 custom resource# 错误节点挂了 Ray 无法在别处重试b.options(resources{node:127.0.0.3:1}).remote()# 正确soft 亲和目标节点挂了可以换地方b.options(scheduling_strategyNodeAffinitySchedulingStrategy(node_id...,softTrue)).remote()值得调的参数参数默认位置什么时候调RAY_max_lineage_bytes1 GBsrc/ray/common/ray_config_def.h:224driver 长期运行、任务海量时调大RAY_TASK_MAX_RETRIES3非 actor task—设 0 可彻底关闭重建换取确定性行为task_retry_delay_ms0:527失败重试风暴时加一点延迟task_oom_retry_delay_base_ms1000:531OOM 频繁且内存回收慢时调大task_actor_unavailable_retry_delay_base_ms/_max100 / 5000:535/:538Actor 重建慢时放宽fetch_fail_timeout_milliseconds600000:332大对象跨节点拉取慢时调大lineage_pinning_enabledtrue:208关掉可省内存但失去重建能力排查手册现象先看什么可能原因OwnerDiedError是不是在 task 内ray.put再返回反模式一owner worker 已退出ObjectReconstructionFailedError对象是否由ray.put产生 / 是否 actor task 且max_task_retries0/ 是否已到max_retries§6.4 四条限制ObjectFetchTimedOutError网络与大对象大小10 分钟超时src/ray/common/ray_config_def.h:332官方提示多为系统级 bug任务失败后疯狂重试task_retry_delay_ms默认 0加固定延迟或降低max_retriesActor 反复重启max_restarts与重启原因Actor 初始化代码有 bug或依赖的外部资源不可用节点挂了任务没重试是否用了节点专属 custom resource反模式二lineage 悄悄失效driver 内存 /RAY_max_lineage_bytes超限被SetReleaseLineageCallback驱逐6.12 进阶Advanced与同类系统的容错哲学对比维度Raylineage 重算Sparklineage 重算Flinkcheckpoint 重放恢复依据任务血缘per-object lineageRDD lineageper-partition全局一致性快照恢复粒度单个对象单个 RDD 分区整个作业回滚到检查点状态支持弱Actor 状态不重建除非显式声明弱强有状态计算的原生支持确定性要求要求 task 幂等要求 RDD 计算确定性要求可重放输入source 可回溯恢复代价重算该对象及其依赖重算丢失分区回滚并重放该检查点之后的记录元数据依赖owner 单点DriverJobManager可 HA核心差异在状态Ray 与 Spark 都是无状态优先的重算模型Flink 是快照模型。Ray 的独特性在于它把血缘下沉到单个对象并且把这个能力交给了 owner一个普通 worker 进程——这使得 Ray 的容错极其轻量无需全局协调代价就是 owner 单点不可恢复。一个可以迁移的设计经验把元数据托管给数据生产链条上的某个参与者能换来极低的协调开销但必须接受该参与者失效时的能力降级。Ray 选择了这个权衡因为它的主要负载AI 训练/推理里对象大多可重算。如果你的场景是算一次要 8 小时且不可重放那么 lineage 重建帮不了你要靠 checkpoint 外部存储。6.13 小结与下篇预告本篇把 Ray 的容错拆成了三层owner 模型元数据与 lineage 的归属地决定容错的边界、重算机制RecoverObject四步算法 按错误类型区分的重试退避、错误归因5 类ObjectLostError各对应一条源码路径。四条最值得记住的结论能恢复数据丢失不能恢复 owner 死亡——源码把它写成RecoverObject的第一步检查src/ray/core_worker/object_recovery_manager.h:72-73。ray.put的对象不可恢复是机制限制不是策略选择task 内ray.put再返回是官方点名的反模式。重试退避按错误类型区分src/ray/core_worker/task_manager.h:61OOM 指数退避 1000ms 起步普通错误默认 0 延迟——这个细节生产上很有用。lineage 有内存成本且有静默失效风险max_lineage_bytes默认 1GB:224超限由SetReleaseLineageCallbacksrc/ray/core_worker/reference_counter.h:168驱逐被驱逐后对象丢失就不再可重建。下一篇07 资源与 PlacementGroup将回到调度主题展开第 4 篇与第 5 篇各留了一半的内容Raylet 侧的placement_group_resource_managersrc/ray/raylet/placement_group_resource_manager.h:49与 GCS 侧的src/ray/gcs/gcs_placement_group_manager.h:121如何配合完成预留一块资源这件事以及src/ray/raylet/scheduling/policy/hybrid_scheduling_policy.h:142的装箱策略细节。关键源码事实ObjectRecoveryManagersrc/ray/core_worker/object_recovery_manager.h:41构造依赖:43-58四步算法注释:71-85RecoverObject:92PinOrReconstructObject:98PinExistingObjectCopy:103ReconstructObject:108失败回调类型:38-39TaskManager退避计算src/ray/core_worker/task_manager.h:61FailOrRetryPendingTask:610RetryTaskIfPossible:607FailPendingTask:617MarkTaskReturnObjectsFailed:630MarkTaskNoRetry:639MarkGeneratorFailedAndResubmit:605ReferenceCountersrc/ray/core_worker/reference_counter.h:74OwnedByUs/:89重提交引用/:100AddOwnedObject/:130借用的引用/:135GetOwner/:159引用删除回调/:168SetReleaseLineageCallbackGCS Actor 容错src/ray/gcs/actor/gcs_actor_manager.h:126为 lineage 重启/:166/:176/:302/:339凭据表src/ray/gcs/gcs_table_storage.h:224节点失败src/ray/gcs/gcs_node_manager.h:122src/ray/gcs/gcs_health_check_manager.h:109资源侧src/ray/gcs/gcs_resource_manager.h:102PG 侧src/ray/gcs/gcs_placement_group_manager.h:155配置src/ray/common/ray_config_def.h:208lineage_pinning_enabledtrue/:224max_lineage_bytes1GB/:274max_direct_call_object_size100KB/:332fetch_fail_timeout600s/:527task_retry_delay_ms0/:531OOM 退避基数 1000ms/:535/:538actor 不可用 100→5000ms
返回列表