a↗Agent 工程实战中文课程 / v1.0.0⌕ 搜索课程/
本页目录

M07 持久执行、幂等与恢复

本章产出:明确的崩溃窗口分析、稳定操作标识、事务内去重与外部结果核对。你应能说明系统保证了什么,以及保证不成立的边界。

1. 最重要的故障:成功了,但不知道成功了

Worker提交退款请求。退款服务已入账,响应却在网络中丢失。Worker只看到超时。此时存在两个与本地观测相容的世界:请求根本没到;请求已成功但响应丢了。

仅凭这次超时,客户端不能区分两者。盲目重试可能重复退款;不重试可能永远不完成。解决路径需要外部接口配合:稳定幂等键、按操作ID查询、业务唯一约束,或者进入人工核对。换一个更复杂的Agent框架不能消除信息缺失。

I14 · 互动推演独立打开 ↗

退款提交后断电

把未知响应与业务事实分开,用同一意图恢复。

合成教学数据 · 未运行真实模型
静态推演与完整规格

I14 退款提交后断电

关联章节:M07 · 交互类型:fault-injection · 状态:提供纯逻辑参考函数,UI待实现

教学目标

把未知响应与业务事实分开,用同一意图恢复。

初始状态

{
  "approved": false,
  "expired": false,
  "ledger_count": 0,
  "committed": false,
  "status": "PENDING",
  "amount_cents": 10000
}
操作控件
  • 批准

  • 正常提交

  • 提交前崩溃

  • 提交后丢响应

  • 同参重试

  • 改变金额重试

  • 推进至过期

  • 重置

状态变化规则
  • 批准只改变approval,不产生账本。

  • before_commit不留下业务写入;after_commit账本+1但响应状态UNKNOWN。

  • 同参重试有已确认结果时只返回,不再加账本。

  • 同意图不同金额拒绝;过期只禁止新执行。

黄金案例与反馈
lost-response

输入:


[
  "approve",
  "crash_after_commit",
  "retry"
]

期望:


{
  "ledger_count": 1,
  "status": "CONFIRMED"
}

恢复读取已有操作结果。

precommit

输入:


[
  "approve",
  "crash_before_commit"
]

期望:


{
  "ledger_count": 0,
  "status": "RETRYABLE"
}

本地事务完整回滚。

unapproved

输入:


[
  "submit"
]

期望:


{
  "ledger_count": 0,
  "status": "APPROVAL_REQUIRED"
}

正常模型输出也不能绕过批准。

完成条件
  • 完成两种崩溃对照

  • 保持重复重试账本=1

反馈层次
  • 提交预测后显示结果;允许跳过预测但记录跳过,不阻止阅读

  • 结果同时显示状态变化、原因与被拒绝动作;不是只给红绿灯

  • 第一次错误给边界提示,第二次提供最小反例;参考推导始终可主动打开

  • 重置恢复固定初始状态并保留历史尝试供用户手动清除

无障碍与静态替代
  • 全部操作有原生button/input/select替代,不依赖拖拽

  • 图与动画提供同步文字状态表;支持prefers-reduced-motion

  • 状态改变用aria-live=polite摘要,不逐字朗读长日志

  • 焦点不跳走;错误提示关联输入;颜色不是唯一编码

无JavaScript时直接展示初始输入、规则和黄金案例表,读者仍能完成纸面推演。状态动画仅作解释,不能成为获取答案的唯一途径。

持久化与重置

使用版本化键agent-course:1:I14,仅保存本地学习状态。支持单卡重置和全站清除;存储失败时以内存模式继续,不阻断阅读。

必须避免的误导

模拟的是单数据库事务;远程服务须依赖其幂等/查询契约。

执行边界

纯本地教学状态机;不执行任意代码,不连接真实资金/模型/宿主Shell。

逻辑参考

web/reference/engines.mjs中的refundStep;对应测试在web/reference/engines.test.mjs。UI可复用或等价移植,但不得改变黄金结果。

2. 把一项操作拆成可检查的阶段

课程退款意图具有稳定operation_id与参数摘要。执行状态为PREPARED、SUBMITTED、UNKNOWN、CONFIRMED或REJECTED。这些描述操作结果,不等于run的状态。

崩溃点 已知事实 恢复动作
保存意图前 没有可恢复操作 从任务决策重新开始,尚无授权提交
意图已保存、请求未发出 有稳定键与有效提案 检查授权后使用同一键提交
请求已发出、未获确定结果 外部可能成功 查询同一操作;必要时以同一键重试
外部已确认、本地结果未保存 外部有事实 通过原键取回结果并保存
本地已确认、Checkpoint未推进 操作已完成 复用保存结果,不新建业务意图

如果供应商没有幂等或查询能力,只能更保守地阻止自动重试并标记UNKNOWN。课程不伪造一种本地数据库魔法来保证任意外部API恰好执行一次。

3. 幂等键代表意图,不代表一次网络尝试

同一业务意图的每次重试使用同一键。用户明确发起另一笔退款,必须经过新资格计算和审批,生成新意图。不要用“参数相同”自动认定重复:用户可能真的想订两件相同商品。

键的作用域至少包含租户/调用者与操作类型。保存参数摘要;同一键搭配不同参数应返回冲突,不能返回旧结果掩盖调用错误。去重记录保留多久,也属于接口契约:过早清理会让迟到重试产生第二次效果。Amazon Builders Library对意图标识、原子记录和参数不一致有直接讨论。S29

课程使用服务端生成的operation_id。attempt_id和工具call_id不能替代它:模型重新规划可能产生新的call_id,Worker重启也会产生新attempt,但业务意图不应因此改变。

4. 同一数据库内可以做出的强保证

如果订单余额、审批、操作记录和退款账本都在同一个支持事务的数据库,我们可以把检查、扣减余额、写账本、写操作结果放在一个事务里。唯一约束阻止同一operation写入多次;参数摘要阻止错用键;条件更新阻止余额超限。

参考实现用SQLite的写事务演示这个不变量。它证明的是教学范围内的原子性,不是PostgreSQL多节点服务或真实支付网络的吞吐/可用性。生产系统还要处理备份、故障转移、事务重试和幂等记录保留。

错误示例是先在Redis写“已处理”,再在数据库退款。两步之间崩溃会使操作永远被跳过;反过来先退款再写Redis会重复。若两个系统没有共享事务,就需要定义协议,而不是声称“用了锁所以一致”。

5. Checkpoint、事件重放与业务去重

Checkpoint保存执行进度快照;事件重放根据持久历史恢复状态;幂等性保证重复尝试不重复产生业务效果。三个概念相关,但不能互相替代。

只保存messages,无法知道某次写操作是否已成功。只保存“当前节点=退款”,恢复时仍可能再次退款。只使用外部幂等键,则执行状态和用户审批仍可能丢失。

对模型调用同样要考虑重复:模型已产生响应但结果没有可靠保存,恢复可能再次调用,增加费用且得到不同答案。若框架能复用已记录结果,可以减少重复;但结果记录之前的故障窗口仍需按照实际平台语义分析,不能承诺零重复计费。

6. Temporal应该怎样学习

Temporal把可重放的工作流控制逻辑与执行I/O的Activity区分开。学习重点是历史、重放、重试和外部副作用之间的边界,而不是把所有Python函数套上装饰器。S22

把模型请求或退款提交放进Activity后,故障时Activity仍可能再次尝试,因此外部操作要有幂等保护。审批可以成为持久工作流接收的事件;它不需要一个进程持续占用线程等用户。S23

本课程要求你用同一组故障情景比较手写执行器与Temporal或LangGraph中的一种,不要求三套完整重复实现。对照报告必须写清:恢复点在哪里、哪些代码重跑、哪些结果可复用、哪些副作用由业务层去重。框架升级也要重跑这组测试。

7. 租约过期后,旧Worker可能仍在运行

假设A拿到租约和fence=7,发生长时间暂停;租约过期后B拿到fence=8。A恢复后仍可能继续写。如果只检查“我曾经有锁”,A会覆盖B。

fencing token是单调递增的所有权代次。受保护的资源必须检查token,拒绝低于当前代次的写入。只在日志里记录fence毫无作用。本地状态提交可以用WHERE fence=8;外部系统若不支持fencing,不能因此宣布外部副作用已受保护,仍要靠稳定意图键和业务约束。

租约检查、授权和业务写入之间也可能有竞态。对同库资源,检查与写入必须处在合适的事务或条件写里;对远端资源,需要其接口参与。交互卡展示这个边界,而不是把token画成万能盾牌。

I15 · 互动推演独立打开 ↗

旧Worker为什么还会写入?

lease到期与资源拒绝旧拥有者是两个机制。

合成教学数据 · 未运行真实模型
静态推演与完整规格

I15 旧Worker为什么还会写入?

关联章节:M07 · 交互类型:lease-race · 状态:提供纯逻辑参考函数,UI待实现

教学目标

lease到期与资源拒绝旧拥有者是两个机制。

初始状态

{
  "current_token": 1,
  "lease_until": 10,
  "now": 0,
  "workers": {
    "A": 1,
    "B": null
  }
}
操作控件
  • 推进时钟

  • B接管并递增token

  • A尝试写

  • B尝试写

  • 关闭资源端token检查(危险对照)

状态变化规则
  • A在t0持有token1,lease至10。

  • t10后B接管得到token2;资源写入要求token相同且now<lease_until。

  • 危险对照只演示如果不检查会产生的错误,不改变正确答案。

黄金案例与反馈
old-owner

输入:


{
  "token": 1,
  "current": 2,
  "now": 11,
  "lease_until": 20
}

期望:


{
  "allowed": false
}

lease过期不会物理停止A,资源必须拒绝旧token。

new-owner

输入:


{
  "token": 2,
  "current": 2,
  "now": 11,
  "lease_until": 20
}

期望:


{
  "allowed": true
}

当前拥有者在有效lease内。

boundary

输入:


{
  "token": 2,
  "current": 2,
  "now": 20,
  "lease_until": 20
}

期望:


{
  "allowed": false
}

到期边界不可写。

完成条件
  • 构造A/B竞争并拒绝A

  • 说明外部工具也需相应边界

反馈层次
  • 提交预测后显示结果;允许跳过预测但记录跳过,不阻止阅读

  • 结果同时显示状态变化、原因与被拒绝动作;不是只给红绿灯

  • 第一次错误给边界提示,第二次提供最小反例;参考推导始终可主动打开

  • 重置恢复固定初始状态并保留历史尝试供用户手动清除

无障碍与静态替代
  • 全部操作有原生button/input/select替代,不依赖拖拽

  • 图与动画提供同步文字状态表;支持prefers-reduced-motion

  • 状态改变用aria-live=polite摘要,不逐字朗读长日志

  • 焦点不跳走;错误提示关联输入;颜色不是唯一编码

无JavaScript时直接展示初始输入、规则和黄金案例表,读者仍能完成纸面推演。状态动画仅作解释,不能成为获取答案的唯一途径。

持久化与重置

使用版本化键agent-course:1:I15,仅保存本地学习状态。支持单卡重置和全站清除;存储失败时以内存模式继续,不阻断阅读。

必须避免的误导

fencing不等于自动取消旧线程,且不能保护未验证token的远程资源。

执行边界

纯本地教学状态机;不执行任意代码,不连接真实资金/模型/宿主Shell。

逻辑参考

web/reference/engines.mjs中的canWrite;对应测试在web/reference/engines.test.mjs。UI可复用或等价移植,但不得改变黄金结果。

8. 补偿不是时间倒流

订票后酒店失败,可以尝试取消机票,但取消可能有费用、时限或失败。这是新的业务动作,不是把世界回滚到过去。记录补偿状态、权限与结果,并准备人工处理。

退款本身通常不能通过“负数退款”直接撤回。课程不允许这种工具;若业务需要追回款项,那属于另一套明确授权的流程。

9. 必做故障矩阵

L06覆盖提交前崩溃、提交后响应丢失、本地结果保存前崩溃、重复审批、两个Worker竞争、旧Worker迟到、参数变化复用键、审批过期、订单版本变化。

一个合格实现必须让账本事实成为判据:同一意图最多一笔退款;合法重试返回同一结果;不同参数被拒绝;无法判断的外部结果进入核对而非谎报完成。参考实现与模拟器都在包内,真实支付系统不在实验范围内。

K16 · 持久工作流的真正价值和边界

K16 持久工作流的真正价值和边界

类型:可靠后端 · 关联章节:M07 · 阅读方式:先做下面的推演,再看参考讨论。

原文在说什么

Temporal的AI教程展示持久工作流与活动的组织;AWS的幂等实践强调安全重试需要可识别的调用意图。

对本课程的具体启发(课程推论)

持久执行减少重启后协调状态丢失的问题;外部副作用是否可重复,仍取决于活动及其下游服务。两种机制需要组合,而不是互相替代。

两分钟推演

退款Activity提交成功后Worker退出,描述重放时允许重跑哪部分、必须去重哪部分。

参考讨论

调度/协调可以按框架语义恢复;实际退款继续使用同一operation_id,并由业务服务返还已有结果。框架历史不是支付账本。

不能从这里推出什么

课程参考仅证明本地事务语义;接入Temporal时需验证所选版本的重放与重试行为。

原始来源

本卡是原创中文概括与教学延伸,不是全文翻译,也不表示原作者认可本课程。不要把原文中的结果替换成本项目实测结果。

独立阅读 →
做当章练习 →