ARTICLE · 1233126
【LangGraph源码精读】第06期|节点失败了怎么办?LangGraph的重试和部分失败恢复
【LangGraph源码精读】第06期|节点失败了怎么办?LangGraph的重试和部分失败恢复
30篇源码精读系列·第06篇(执行引擎层收官篇)。你的Agent跑到第5个节点崩了,前面4个节点的结果全丢了?不会。LangGraph的pending writes机制让已成功节点的结果立即持久化,恢复时跳过它们只重跑失败的节点。但这背后有一个你必须知道的代价:恢复不是从崩溃的那行代码继续,而是从整个节点的开头重跑。这篇把失败重试、部分恢复、优雅关闭、幂等性一次讲透。
一、开头:你的Agent崩了,前面的工作全白做了?
这是一个经典场景。
你写了一个5步Agent:搜索→分析→生成→审核→发布。跑到"生成"节点时,LLM API超时了,整个Agent崩溃。你以为前面"搜索"和"分析"的结果都丢了,需要从头重跑——但实际上没有。
LangGraph的pending writes机制让每个节点完成后立即持久化结果。 同Super-step中,A节点成功了,B节点失败了,A的结果已经写入checkpoint_writes表。恢复时A不会重跑,只重跑B。
但这里有一个反直觉的点:恢复不是从B节点崩溃的那行代码继续,而是从B节点的整个函数开头重跑。 如果B节点内部有3个步骤,第1步已经执行了(比如调用了一个外部API产生了副作用),恢复时第1步会再执行一次。这就是为什么"节点必须幂等"不是建议,是硬性要求。
这篇讲LangGraph的完整容错体系:pending writes部分恢复、两层持久化、三种持久化模式、重试源码、error_handler补偿、drain优雅关闭、恢复重放机制、幂等性要求。 搞懂这些,你的Agent才能真正"生产可用"。
二、失败的三种类型
在深入机制之前,先分清LangGraph中三种不同的失败。
这三种失败的恢复机制完全不同。 节点异常是"软件层面"的,重试和补偿就能解决;系统崩溃是"硬件层面"的,需要持久化和恢复;人为中断是"控制层面"的,需要协作式停止。
三、pending writes:部分失败恢复的核心
pending writes是LangGraph容错体系中最精妙的设计。
3.1 什么是pending writes?
回顾执行流程:节点执行完后,commit()把结果写入task.writes缓冲区,然后调用loop.put_writes(task_id, writes)。
关键:put_writes是在节点完成时立即调用的,不是等整个Super-step结束。 这意味着即使同Super-step的其他节点还在跑,已完成节点的结果已经安全地写入了持久化存储。
3.2 pending writes的存储结构
Checkpointer有两张核心表:
writes表是pending writes恢复的基础。 恢复时,LangGraph读取writes表中当前checkpoint下所有已成功task的writes,把它们重新挂到对应的task上,标记为"已完成",然后只执行还没有writes的task。
3.3 一个具体例子
假设Super-step有3个并行节点:A(搜索)、B(分析)、C(生成)。
A和B没有重跑,只重跑了C。 这就是"部分失败恢复"——不是整个Super-step重跑,而是只重跑失败的节点。
四、两层持久化:put_writes和put
LangGraph的持久化分两层,对应不同的恢复粒度。
4.1 为什么需要两层?
两层持久化是"粒度"和"完整性"的平衡。 put_writes提供细粒度的部分恢复,put提供完整的状态快照用于时间旅行和快速恢复。
4.2 恢复时的tick()逻辑
恢复场景下,tick()做了一件关键的事:
这就是为什么恢复时已成功节点不重跑。 它们的writes已经在writes表里了,tick()把它们当作"缓存命中"直接复用。
五、三种持久化模式
LangGraph支持三种持久化模式,在性能和数据安全之间权衡。
5.1 怎么选?
注意:exit模式下pending writes也不会立即保存。 因为put_writes也是持久化操作,exit模式下会延迟到退出时统一写入。所以exit模式下系统崩溃会丢失当前Super-step的所有节点结果。
六、重试的源码实现:run_with_retry
重试是节点级容错的第一道防线。
6.1 run_with_retry的执行流程
6.2 退避策略
指数退避+随机抖动是分布式系统的标准做法。 指数退避避免频繁重试打垮下游服务,随机抖动避免"惊群效应"(多个Agent同时重试导致流量尖峰)。
6.3 重试的关键语义
七、error_handler和Saga补偿
重试解决不了所有问题。有些错误重试也没用(支付失败、库存不足、参数错误),这时候需要error_handler做补偿。
7.1 error_handler的执行时机
7.2 Saga补偿模式
error_handler最经典的用法是Saga模式——分布式事务中的补偿机制。
error_handler可以返回Command(update=..., goto=...),同时更新状态和路由到补偿节点。 这比"整个graph失败回滚"灵活得多——你可以精确控制失败后做什么、跳到哪里。
7.3 error_handler的限制
八、drain优雅关闭:v1.2的生产级能力
drain是v1.2新增的功能,解决"部署时怎么不丢正在运行的任务"的问题。
8.1 为什么需要drain?
8.2 drain的协作式语义
drain是协作式的,在Super-step之间生效,永远不抢占正在运行的工作:
核心原则:drain不会打断正在做的事,只会在"安全点"(Super-step边界)停止。 这保证了停止时状态一定是一致的。
8.3 节点内感知drain
节点可以通过runtime参数检查drain状态,提前调整行为:
这对长任务特别有用。 如果节点内部有一个10步的循环,第3步时收到drain,可以提前结束循环返回部分结果,而不是硬跑完10步才停止。
8.4 SIGTERM处理的标准模式
生产环境中处理SIGTERM的标准做法:
九、恢复的起点和重放机制
这是最容易被误解的一点。
9.1 恢复不是从崩溃的那行代码继续
恢复的起点是节点的开头,不是崩溃的代码行。 因为LangGraph不保存节点内部的执行状态(Python的调用栈、局部变量),只保存节点的输入输出。恢复时只能重新调用节点函数,传入恢复后的state。
9.2 不同API的恢复起点
9.3 重放机制
恢复时,LangGraph会重放从起点到停止点的所有步骤:
重放不是"从头开始",而是"从checkpoint的状态开始,跳过已成功节点"。 已成功节点的结果从writes表直接复用,只有失败的节点会重跑。
十、幂等性:恢复不重复副作用
因为恢复时节点会从开头重跑,所以节点必须幂等。
10.1 什么是幂等?
10.2 怎么保证幂等?
@task是Functional API提供的幂等机制。 被@task装饰的函数,其结果会被持久化;恢复时如果task已成功执行,直接返回持久化的结果,不重复执行。这解决了"节点内部多个副作用步骤"的幂等问题。
十一、常见坑:容错的5个隐蔽问题
十二、面试关联:容错的5道高频题
十三、本期小结
失败重试和部分恢复,核心就6句话:
- 1. pending writes是部分失败恢复的核心。 每个节点完成后立即put_writes持久化结果,同Super-step中一个节点失败不影响已成功节点。恢复时从writes表恢复已成功节点的结果,只重跑失败的节点。恢复粒度是节点级,不是Super-step级。
- 2. 两层持久化对应不同恢复粒度。 put_writes是节点级,在节点完成时立即写入,用于部分恢复;put是Super-step级,在after_tick时写入完整快照,用于时间旅行和快速恢复。两层配合实现"细粒度恢复+完整快照"。
- 3. 三种持久化模式在性能和安全间权衡。 exit只在退出时存(性能最好但崩溃全丢),async异步存(平衡点),sync同步存(最安全但有开销)。生产场景默认async,涉及支付订单用sync。
- 4. 重试是节点级的,不影响同Super-step其他节点。 run_with_retry用指数退避+随机抖动,默认重试网络异常和5xx,不重试代码错误。重试前清除失败尝试的writes,超时后重试时钟重置。重试耗尽后才触发error_handler。
- 5. drain是协作式优雅关闭,在Super-step边界生效。 收到drain信号后,当前节点和重试循环跑完,下一个Super-step抛出GraphDrained并保存checkpoint。节点内可以通过runtime.drain_requested提前调整行为。drain保证停止时状态一致,可恢复。
- 6. 恢复不是从崩溃的代码行继续,而是从节点开头重跑。 LangGraph不保存调用栈和局部变量,只保存节点输入输出。所以节点必须幂等——副作用操作用幂等键、先查后写、@task包装或拆分节点。不幂等的节点在恢复时会产生重复副作用。
十四、执行引擎层收官 + 下一期预告
执行引擎层6篇(01-06)全部完成:
下一期(源码07),进入用户API/编译层第一篇:
【LangGraph源码精读】第07期|add_node/add_edge做了什么?我读源码发现3个隐藏校验
从StateGraph的add_node到add_edge,从节点注册到边的校验,搞懂你写的每一行builder代码在底层触发了什么。
你在生产中遇到过Agent崩溃恢复的问题吗?评论区聊聊,我优先写你最关心的场景。
本文为《LangGraph源码精读30讲》系列第06篇。内容基于LangGraph v1.2.11源码结构梳理,核心文件位于libs/langgraph/langgraph/pregel/目录下的_loop.py(pending writes恢复、drain处理)、_runner.py(run_with_retry)、_retry.py(重试逻辑),以及libs/checkpoint/langgraph/checkpoint/base.py(BaseCheckpointSaver的put/put_writes接口)。drain和per-node timeout为v1.2新增功能。版本更新后部分细节可能变化。如有错漏欢迎指正。















































































