什么是 Durable Execution
Durable Execution 可以翻译成“持久化执行”。它解决的问题是:一段业务代码跑到一半时,进程、机器、网络、外部服务随时可能失败;系统恢复后,业务代码要从正确位置继续,而不是从头盲目重试。
普通重试为什么不够
看一个订单处理函数:
python
def process_order(order):
payment = charge_card(order.card, order.amount)
reserve_inventory(order.sku, order.quantity)
send_receipt(order.email, payment.id)
return {"payment_id": payment.id}如果 send_receipt() 前进程崩溃,普通 retry 会重新执行整个函数。后果可能是:
| 步骤 | 第一次执行 | 重试后风险 |
|---|---|---|
| 扣款 | 已成功 | 可能重复扣款 |
| 锁库存 | 已成功 | 可能重复占用库存 |
| 发邮件 | 未执行 | 可能最终成功 |
重试确实提升了成功概率,但它没有回答一个核心问题:哪些步骤已经真的发生过?
Durable Execution 的基本模型
Durable Execution 把函数执行拆成一串可以记录的步骤:
故障恢复时,运行时不会问“函数跑到哪一行了”,而是问“Journal 里已经有哪些事实”。
核心组件
| 概念 | 作用 |
|---|---|
| Invocation | 一次 handler 调用,是执行生命周期的单位 |
| Journal | Invocation 的执行历史,记录每个 durable step 的结果 |
| Durable Step | 被运行时保护的副作用或非确定性操作 |
| Replay | 失败后重新进入函数,用 Journal 结果替代已完成步骤 |
| Idempotency Key | 把重复入口请求映射到同一个 Invocation |
| Timer | 被持久化的等待,不依赖进程内存 |
| State | 与工作流或对象 key 绑定的持久状态 |
ctx.run() 解决什么问题
Restate 的 SDK 通过 context actions 暴露能力。以 Python SDK 为例,非确定性的数据库调用、HTTP 调用、随机数、LLM 调用等应放进 ctx.run / ctx.run_typed,让结果进入 execution log。官方文档强调,重放时这些结果会从日志恢复,而不是重新执行。
在教学版里,我们实现一个更小的接口:
python
payment = ctx.run("charge-payment", lambda: charge_card(order))它做三件事:
- 根据当前 invocation id 与 step index 查 Journal。
- 如果已有完成记录,直接返回记录里的结果。
- 如果没有记录,执行函数,并把结果写入 Journal 后再返回。
可恢复和可回滚不是一回事
Durable Execution 不等于事务回滚。扣款一旦真的调用成功,系统通常无法“自动撤销”。Durable Execution 的目标是避免重复副作用,并让补偿逻辑有可靠上下文。
| 机制 | 关注点 |
|---|---|
| 数据库事务 | 一组数据库写入要么提交,要么回滚 |
| 幂等 API | 同一个业务 key 多次调用只产生一次效果 |
| Durable Execution | 多步骤业务流程在失败后从正确位置继续 |
| Saga | 长事务失败后执行补偿步骤 |
本章练习
- 找一个你熟悉的三步骤业务流程,标出哪些步骤有副作用。
- 判断这些步骤是否天然幂等。
- 设计一个 Journal 表,至少包含
invocation_id、step_index、step_name、result。
下一章我们看 Restate 如何把这个模型做成生产级运行时。