- 培歌行学LangGraph(1):从 LangChain 到 LangGraph 的思维跃迁]
- 培歌行学LangGraph(2):Reducer(归约器)彻底搞懂
- 培歌行学LangGraph(3):编译、执行与可视化
- 培歌行学LangGraph(4):一文搞懂图的状态(State)管理
- 培歌行学LangGraph(5):状态管理与
graph.invoke入参深度解析 - 培歌行学LangGraph(6):Multi Schema多状态管理详解
- 培歌行学LangGraph(7):预定义状态MessagesState与AgentState
- 培歌行学LangGraph(8):控制流详解
- 培歌行学LangGraph(9):控制流详解:defer延迟节点——让收尾工作自动排到最后
- 培歌行学LangGraph(10):多分支汇聚Fan-in——多个分支如何汇聚到一起?
- 培歌行学LangGraph(11):用循环结构实现 ReAct Agent
- 培歌行学LangGraph(12):递归限制——别让你的图无限跑下去
- 培歌行学LangGraph(13):节点执行与容错机制
- 培歌行学LangGraph(14):启用可恢复执行(Checkpoint)
一、为什么需要不同的持久化模式?
启用检查点后,LangGraph 会在执行过程中保存状态快照。但这里有一个权衡:
保存越频繁,容灾能力越强,但性能开销也越大。
- 每执行一步都保存 → 崩溃后恢复最完整,但速度慢
- 只在结束时保存 → 速度快,但崩溃后无法恢复
LangGraph 提供了三种持久化模式,让你在容灾能力和性能开销之间做选择。
二、三种模式详解
2.1 exit 模式(退出模式)
写入时机:只在图正常结束、异常退出或被中断时保存。
graph.invoke(input_data, config=config, durability="exit")
特点:
- ✅ 性能开销最小
- ✅ 响应延迟最低
- ❌ 容灾能力最弱
- ❌ 进程中途崩溃,所有中间状态丢失
适用场景:
- 短时间运行的任务
- 不需要中途恢复的简单流程
- 对性能要求极高的场景
2.2 async 模式(异步模式)⭐ 默认模式
写入时机:每个超步结束后,在后台异步写入检查点。
graph.invoke(input_data, config=config, durability="async")
特点:
- ✅ 容灾能力较强
- ✅ 写入在后台进行,不阻塞主流程
- ❌ 性能开销较高(但用户感知不明显)
适用场景:
- 大多数生产环境
- 多轮对话系统
- 需要平衡性能和可靠性的场景
2.3 sync 模式(同步模式)
写入时机:每个超步结束后,等待检查点写入完成,再进入下一个超步。
graph.invoke(input_data, config=config, durability="sync")
特点:
- ✅ 容灾能力最强
- ❌ 性能开销最大
- ❌ 响应延迟最高(等待磁盘写入)
适用场景:
- 金融交易系统
- 不允许丢失任何中间状态的场景
- 对可靠性要求极高、对延迟不敏感的场景
三、三种模式对比
| 模式 | 写入时机 | 性能开销 | 响应延迟 | 容灾能力 | 推荐场景 |
|---|---|---|---|---|---|
| exit | 运行结束时 | 低 | 最低 | 最弱 | 简单任务、批处理 |
| async | 每个超步后异步写入 | 中 | 低 | 较强 | ⭐ 大多数场景 |
| sync | 每个超步后同步写入 | 高 | 最高 | 最强 | 金融、关键业务 |
四、三种模式的实际表现
假设你的图有 3 个节点:A → B → C,每个节点执行 1 秒。
exit 模式
执行 A(1秒)
执行 B(1秒)
执行 C(1秒)
→ 保存检查点
总耗时:3秒
如果在执行 B 时进程崩溃 → 所有状态丢失,无法恢复。
async 模式
执行 A(1秒)→ 后台异步保存
执行 B(1秒)→ 后台异步保存
执行 C(1秒)→ 后台异步保存
总耗时:≈3秒(异步保存几乎不增加耗时)
如果在执行 B 时进程崩溃 → 可以从 A 的检查点恢复。
sync 模式
执行 A(1秒)→ 等待保存完成(额外耗时)
执行 B(1秒)→ 等待保存完成(额外耗时)
执行 C(1秒)→ 等待保存完成(额外耗时)
总耗时:>3秒(每次都要等待写入)
如果在执行 B 时进程崩溃 → 可以从 A 的检查点恢复,且保证 A 的状态已持久化。
五、代码示例
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "test_session"}}
# 使用 exit 模式
result = graph.invoke(
{"messages": [HumanMessage("你好")]},
config=config,
durability="exit"
)
# 使用 async 模式(默认)
result = graph.invoke(
{"messages": [HumanMessage("你好")]},
config=config,
durability="async"
)
# 使用 sync 模式
result = graph.invoke(
{"messages": [HumanMessage("你好")]},
config=config,
durability="sync"
)
注意:三种模式下,正常执行完成时返回的结果是一样的。区别只体现在异常中断后的恢复能力上。
六、如何选择?
你的应用场景是什么?
│
├── 简单任务,不需要恢复 → exit
│
├── 多轮对话、常规 Agent → async(默认,推荐)
│
└── 金融、医疗、关键业务 → sync
经验法则:
- 90% 的场景用
async就够了 - 开发调试时可以用
exit提高速度 - 只有对数据一致性要求极高的场景才用
sync
七、面试题
面试题1:三种持久化模式的核心区别是什么?
答:核心区别在于检查点的写入时机:
exit:只在运行结束时写入async:每个超步结束后异步写入sync:每个超步结束后同步写入,等待完成
写入越及时,容灾能力越强,但性能开销也越大。
面试题2:为什么 async是默认模式?
答:因为 async在容灾能力和性能开销之间取得了最好的平衡。它在每个超步后保存检查点,保证了较强的容灾能力;同时写入操作在后台异步进行,不会阻塞主流程,用户感知不到明显的性能下降。
面试题3:exit模式真的有用吗?什么时候用它?
答:有用。在以下场景中 exit模式是合适的选择:
- 批处理任务:一次性执行,不需要中途恢复
- 开发调试:追求最快的迭代速度
- 短流程:整个图几毫秒就跑完了,崩溃概率极低
面试题4:sync模式为什么会导致响应延迟增加?
答:因为 sync模式在每个超步结束后,会等待检查点写入完成才进入下一个超步。如果检查点后端是数据库(如 PostgreSQL),每次写入都需要网络 I/O 和磁盘 I/O,这会增加额外的等待时间。
面试题5:三种模式会影响正常的业务输出吗?
答:不会。三种模式只影响检查点的写入时机和容灾能力,不影响图本身的执行逻辑。在正常执行完成的情况下,三种模式返回的结果是一样的。
持久化模式的选择,本质上是在问自己:我能接受丢失多少状态?
如果答案是"一点都不能丢",选 sync;
如果答案是"丢了也无所谓",选 exit;
大多数时候,答案是"尽量别丢,但别太慢",那就选 async。