KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
05. 重试为什么可能把一次事故变成两次? — keel 龙骨
配置服务已提交 pool_limit=40,但响应在网络中丢失。客户端看到 timeout,于是再次发送请求。如果第二次请求被当作新意图,审计记录、配置版本和下游事件都会重复。
配置服务已提交 pool_limit=40,但响应在网络中丢失。客户端看到 timeout,于是再次发送请求。如果第二次请求被当作新意图,审计记录、配置版本和下游事件都会重复。
幂等保护的是业务效果
幂等不要求网络请求只发送一次,而是要求同一个稳定业务意图被重复提交时,不产生额外业务效果。
intent_id: change-2026-08-22-001
step: set_pool_limit
idempotency_key: change-2026-08-22-001:set_pool_limit
随机 UUID 如果每次重试都重新生成,只能标识网络尝试,不能识别业务重复。
服务端需要保存什么
idempotency_key
fingerprint
state: reserved / pending / succeeded / failed / unknown
first_receipt
external_reference
created_at / expires_at
处理规则:
| 情况 | 处理 |
|---|---|
| key 不存在 | 原子占位并开始执行 |
| key 存在且 fingerprint 相同 | 返回旧回执或查询同一 Operation |
| key 存在但 fingerprint 不同 | 返回 idempotency_conflict |
| key 对应 unknown | 先对账,不创建新动作 |
AWS Builders' Library 特别强调“相同 client request ID、不同意图”和迟到请求;Stripe 也会比较后续请求参数与原请求。共同原则是:幂等键必须与原始业务意图绑定。
哪些错误可以重试
invalid_arguments 不重试,先修正输入
policy_denied 不重试,不能靠重试绕过策略
precondition_failed 重新读取,不直接重放旧命令
rate_limited 在 deadline 内退避重试
unavailable 有幂等保护时有限重试
timeout 先判断是否可能已提交
outcome_unknown 对账,不盲目重试
指数退避解决服务压力,不解决重复副作用。jitter 减少大量客户端同时重试,也不替代幂等。
“Exactly once”为什么容易误导
队列可能至少投递一次,worker 可能提交后崩溃,响应可能丢失。端到端业务语义通常来自:
at-least-once delivery
+ idempotency key
+ atomic dedup record
+ reconciliation
= effectively-once business effect
这不是数学意义的全局 exactly-once,但它是可验证、可恢复的工程方案。
运行实验
python courses/advanced/real-world-execution/course/project/examples/04_idempotency.py
同一 Command 执行两次,配置只变化一次;复用同一 key 但把值改成 80,应得到稳定冲突。
检查理解
- 为什么 attempt ID 不能当幂等键?
- 连接超时后,指数退避为什么仍可能重复付款?
- 幂等记录的 fingerprint 为什么必须检查?