Skip to content

10.11 持久执行与容错:崩溃复活与自动重试 ​

长流程跑一半,最怕三件事:进程崩了、第三方接口超时、机器重启。普通脚本只能从头来,前面烧掉的 Token 和时间全部作废。LangGraph 的 Durable Execution(持久执行) 把长流程变成可以断点续跑的工程:只要挂了会落盘的 Checkpointer,节点执行完的那一刻进度就落袋;崩了,从最近一张快照复活。

可以把它想成登山时每隔一段打一根冰锥。滑坠了不会掉回山脚,只会挂在最后一根冰锥上——抖抖雪,接着爬。


1. 断点续跑:恢复粒度是“整个节点” ​

python
from langgraph.checkpoint.memory import MemorySaver   # 生产换 SqliteSaver / PostgresSaver

graph = builder.compile(checkpointer=MemorySaver())
config = {"configurable": {"thread_id": "order_1001"}}

try:
    graph.invoke(inputs, config)
except Exception:
    # 同一 thread_id 再次 invoke:已完成的节点不会重跑
    graph.invoke(None, config)

两个反直觉但必须记住的细节:

  1. 半路上没跑完的那个节点会整个重跑。 所以官方反复强调:节点内部的网络请求、写库,要尽量幂等——重复执行一次和执行多次结果相同。写入用 upsert,扣款先查状态再改,发邮件带去重键。
  2. MemorySaver / InMemorySaver 只活在内存里。 进程一崩,冰锥等于打进雪里。想抗重启,必须换 SqliteSaver 或 PostgresSaver。

幂等键应来自一次业务请求的稳定标识(订单号、请求 ID),而不是每次重试 uuid4() 一个新值。否则“重试”会变成“再下一单”。

故障演练很值得做:在节点执行前后主动抛异常,观察恢复后哪些步骤再次发生。只有验证了外部副作用没有重复,才能说“断点续跑”适用于该业务。


2. RetryPolicy:只对值得再试的错误出手 ​

网络抖动、限流 429、偶发超时,这些瞬时故障不该让整张图报废。往 add_node 上挂 RetryPolicy:

python
from langgraph.types import RetryPolicy

builder.add_node(
    "call_flight_api",
    call_flight_api_node,
    retry_policy=RetryPolicy(
        max_attempts=3,                            # 含第一次,一共最多 3 次
        initial_interval=0.5,                      # 首次重试前等 0.5 秒
        backoff_factor=2.0,                        # 之后 1s、2s……
        retry_on=(TimeoutError, ConnectionError),  # 只对这两类异常重试
    ),
)

像客服热线没打通:隔一会儿再拨,而且间隔越来越长,别在一秒钟内狂拨二十次。retry_on 必须精确。默认策略会跳过 ValueError、TypeError 这类“再试也没用”的编程错误。把参数写错放进重试名单,只是在交同样的学费。

要区分暂时故障和永久错误:

错误要不要重试为什么
超时、连接断开、429要,指数退避过几秒可能就好了
400 参数错误、403 没权限不要同一请求再发也不会过
业务规则拒绝(库存不足)不要,走错误分支或人工这是结果,不是故障

重试次数、间隔和最终状态应写入运行记录,方便事后对账。


3. 超时与缓存:两道省钱省时的保险 ​

  • 超时: 1.x 支持给节点声明 timeout(超时当异常,可与重试叠加)。节点内部的 HTTP 调用也建议设客户端超时,防止单个下游把整张图拖死。
  • 缓存: 相同输入的昂贵节点(尤其是 LLM)可以复用上次结果。编译时挂缓存实现,节点上声明策略:
python
from langgraph.cache.memory import InMemoryCache
from langgraph.types import CachePolicy

builder.add_node(
    "expensive_llm_node",
    node_fn,
    cache_policy=CachePolicy(ttl=120),
)
graph = builder.compile(cache=InMemoryCache(), checkpointer=memory)

缓存只适合“输入确定、输出可复用”的节点。实时报价、验证码、带随机性的生成,缓存会帮倒忙。TTL 要和业务时效对齐:航班余票缓存 2 分钟可以,缓存 2 天不行。


4. 容错组合拳 ​

手段防什么关键配置
Checkpointer 落盘进程崩溃、机器重启SqliteSaver / PostgresSaver
节点幂等恢复后重跑造成重复副作用upsert、先查再改、业务幂等键
RetryPolicy瞬时网络故障、限流max_attempts + retry_on + 退避
超时下游把全图拖死节点 timeout + HTTP 客户端 timeout
缓存重复的昂贵调用CachePolicy + TTL
recursion_limit死循环持续烧钱config={"recursion_limit": 25}
RemainingSteps接近上限时主动收尾写在 State 里,路由函数提前走 END

组合逻辑可以记成三句话:Retry 管“再试一次能不能成”,Checkpointer 管“实在不行下次从哪继续”,缓存管“能不能根本不用试”。 三者叠加,才是完整的“跑到一半崩了能续跑”。

02 节提过的 RemainingSteps 在这里特别有用:与其在外层抓 GraphRecursionError,不如让图在还剩两步时自己总结、收工。

📁 本节示例代码:code/examples/11_durable_execution_demo.py —— 真实重试三次 + 崩溃后从 step_1 之后复活。


扩展阅读 ​


下一节: 旅行助手采用平铺节点与真子图混合。先拆开“图中有图”,再按当前官方分类看清 Subagents、Handoffs、Skills、Router 与 Custom workflow。