请求超时不等于失败:把写操作恢复设计成事实回读

工程里有一个很常见的冲突:稳定性组件希望"失败就重试",业务系统却担心"重试会重复扣款、重复发消息、重复发布"。两边都没错,真正的问题是我们把超时错误地等同于失败。
超时表达的是调用方没有拿到结果。服务端可能尚未处理,也可能已经完成写入,只是响应丢失。面对这种未知态,正确动作不是立即再写一次,而是先读取事实。
工程场景与决策冲突
通用重试提高了网络层成功率,却可能降低业务层正确性。写操作恢复必须优先保护业务副作用,而不是追求每个请求都尽快得到一个成功响应。
把二态结果升级为三态
很多 SDK 只有成功和异常两条路径,业务层便自然写出:
python
try:
submit(payload)
except TimeoutError:
submit(payload)
这段代码对查询通常没问题,对有副作用的写操作却缺少第三个状态:ambiguous。它表示当前证据不足,既不能宣布成功,也不能确认失败。

三态设计带来一个直接收益:重试策略不再由网络异常类型决定,而由业务事实决定。
决策链:先回查,再重放
方案拆解与关键权衡
一条可落地的恢复链路可以拆成四层。
第一层:稳定操作身份
在发起请求前生成稳定的 operation_id 和 idempotency_key。如果调用过程超时,后续所有恢复动作都沿用它们,不能为每次重试生成新键。
第二层:精确内容身份
操作 ID 只能证明"是哪件事",不能证明"是哪一版内容"。因此还要绑定 revision 与 content_sha256:
text
identity = operation_id + revision + content_sha256 + idempotency_key
这能阻止旧授权在内容修改后继续生效,也能识别同一对象被不同正文竞争写入。

第三层:权威事实回读
超时后通过只读接口按稳定 ID 查询:
| 回读事实 | 决策 |
|---|---|
| 对象存在,内容 SHA 一致 | 视为第一次已成功,返回已有结果 |
| 对象存在,内容 SHA 不同 | 冲突,禁止自动重放 |
| 对象不存在,且查询权威 | 可以使用原幂等键受控重放 |
| 查询失败或只读副本可能延迟 | 保持未知态,稍后再回查 |
关键权衡在"查询权威"四个字。最终一致的只读副本查不到记录,不足以证明主库没有提交。
第四层:服务端幂等事务
客户端的恢复只能降低风险,最终去重仍要由服务端保证。服务端应在一个事务内检查幂等键、执行业务写入、保存结果摘要;重复请求返回原结果,不再次执行副作用。
为什么还要一次性批准
对于公开发布、生产变更等高风险动作,仅靠幂等键还不够。批准本身也需要绑定对象、revision、正文 SHA 和有效期,并且成功消费一次后立即失效。
这样可以同时挡住两类问题:
- 同一内容因网络超时被重复执行;
- 内容修改后继续复用旧批准执行新版本。
真实工程测试至少要覆盖批准错配、过期、重复消费,以及进程跨界后仍只能消费一次。
恢复代码应该长什么样
实现链路与最小示例
python
def recover(command, checkpoint):
fact = read_fact(command.operation_id)
if fact.matches(command.content_sha256):
return fact.result
if fact.exists:
raise ContentConflict(command.operation_id)
if not fact.authoritative:
return checkpoint.keep_ambiguous()
approval.consume_once(
command.operation_id,
command.revision,
command.content_sha256,
)
return submit_with_same_idempotency_key(command)
这里故意没有"最多重试三次"。次数不是安全依据;事实回读、内容身份和幂等事务才是。
自动化边界
证据、限制与自动化边界
本文依据固定版本的分布式系统知识、一次性批准数据模型和对应失败测试。示例没有假设所有查询都强一致;当事实接口不权威时,唯一安全动作是继续保持未知。
- 只读回查可以自动执行;
- 内容完全一致且权威确认不存在时,可以按策略受控重放;
- 内容冲突、批准失效、查询不权威时必须停止;
- 不能通过清理本地日志、删除检查点或换一个幂等键来绕过未知态;
- 写命令返回超时后,禁止盲目再次调用同一写入口。
可复用检查清单
- 写操作有稳定业务 ID,而非只依赖请求 ID;
- 超时进入独立的未知状态;
- 存在权威的只读事实接口;
- 重放复用原幂等键;
- 内容身份包含 revision 与 SHA-256;
- 高风险批准短时、一次性并绑定精确内容;
- 服务端原子记录幂等结果;
- 测试覆盖回查成功、回查不存在、内容冲突和查询不可用。
分布式系统让通信失败成为常态,但"失败常见"不意味着可以随意重试。把超时保留为未知态,再用事实回读收敛它,系统才真正具备可恢复性。
收束
如果你的系统已经有幂等键,不妨继续追问:超时后是谁证明第一次没有成功,内容变化后谁让旧授权失效?这两个问题决定了幂等是否真正闭环。
发布前门禁
- 工程选择、代价和停止条件已明确
- 没有虚构指标与平台效果
- 正文图片均为本主题本地资产
- 本轮未认领实验