好的实现不会依赖用户反复刷新碰运气,而会让每个阶段有明确输入、输出、超时和终态。 超时表示调用方不知道结果,不等于服务端没有执行。幂等记录必须与业务写入处于同一原子边界,并绑定主体和规范化请求摘要。
分布式正确性来自幂等键、租约、单调版本和可对账事实,而不是假设请求只到一次。超时代表结果未知,调用方不能未经确认就把它解释成失败。
先定义系统契约
这项能力横跨客户端、网络与服务端,局部最优很容易制造全局故障。工程决策需要同时约束两端行为、持久化事实和运维预算。
- 首次请求占用键并持久化 pending;事务提交结果后保存状态码和响应摘要,重复键若 payload 不同返回冲突而非复用。
- 为状态写出唯一所有者、版本号和终态;任何回调只能修改自己创建的版本。
- 兼容性通过显式能力协商决定,旧客户端得到可解释的降级路径,而不是进入半可用状态。
“重试不是再发一次:幂等契约要覆盖写入与副作用”的交付标准是正常路径可用、异常路径收敛、资源有界,并且用户能理解系统为何采用当前状态。 这样得到的不是演示代码,而是能解释、能降级、能回滚的生产能力。
哪些情况会破坏契约
失败路径需要与成功路径共享同一套状态模型。只弹一个错误提示却不释放资源、不传播终态,下一次恢复就会继承脏状态。
- 业务写入成功但幂等记录稍后写入会留下重复窗口;缓存 200 响应却不缓存确定性 4xx 会让客户端反复执行校验副作用。
- 只修正界面状态而没有清理底层资源,下一次操作继承队列、锁或过期凭据并再次失败。
- 降级路径从未压测,主路径故障时所有流量同时进入更慢、更昂贵的后备方案。
如何逐条验证契约
测试不能以“最后成功了”结束。还要测量经历了多少次副作用、等待多久、是否泄露内容,以及失败后再次执行能否从干净基线开始。
- 在数据库提交前、提交后响应前和副作用发送前分别断开连接,使用同一键重试并核对业务行、通知与配额各只有一次。
- 连续执行一百次开始、失败、重试和取消,检查句柄、监听器、队列及临时数据回到基线。
- 发布前记录成功率、p50/p95/p99 延迟、错误分类和资源高水位,并为回退条件设定明确阈值。
完成并不等于主路径跑通,而是每个终态都能对账、每个自动动作都受用户意图约束、每个成本都有明确上限。