幂等、去重与重试的工程化

06-工程实践与前沿 核心 约 20 分钟 #幂等#去重#重试预算#退避 更新 2026-10-02
当前状态:未学
本文基于模型知识整理(生成时未联网核对),关键结论建议对照经典文献复核。

一句话定义

幂等与去重把"消息可能重复到达"变成无害事实:请求携带唯一标识,服务端以唯一约束/去重表识别重复并返回原结果;重试则必须配指数退避、抖动与重试预算,否则会把自己与下游一起放大打死。

为什么重要

kp-006 说明了"未知结果只能重试";本条给出重试可以安全落地的完整工程清单。绝大多数"重复扣款""重复发券"事故,根因都是幂等设计缺位或去重窗口配置错误。这是分布式业务系统的必修防守课。

前置知识

kp-006(三态结果与重试)、kp-008(至少一次语义)。

核心概念

  • 幂等键(idempotency key):客户端为同一业务操作生成全局唯一键(如 UUID 或业务单号),随请求传递;服务端按键识别重复。
  • 去重表 / 唯一约束:insert into dedup(key, result) 以数据库唯一键兜底——重复请求插入冲突即返回已存结果。这是最可靠的去重实现(原子性由 DB 保证)。
  • 条件更新:update orders set state='已支付' where order_id=? and state='待支付'——用状态机让重复执行自动空转,是无需额外表的原生幂等写法。
  • 指数退避 + 抖动:第 n 次重试等待 base×2^n + random(jitter);jitter 打散同步重试尖峰。
  • 重试预算(retry budget):限制重试流量占比(如 ≤ 总流量的 10%),防止重试风暴(retry storm)在下游故障时雪上加霜。
  • 去重窗口:去重记录只保留有限时间(如 24h),窗口外的重复视为新请求——窗口长度必须 ≥ 上游最大重试跨度。

原理与机制

幂等的三个实现层次(按可靠性递增):

  1. 天然幂等操作:GET、条件更新(状态机)、upsert——优先把 API 设计成天然幂等,成本最低;
  2. 唯一键去重:数据库/Redis 唯一约束拦截重复请求并返回首次结果——跨系统重试的安全网;
  3. 业务对账兜底:异步对账任务扫描重复与不一致——任何幂等实现都有窗口缝隙(去重表与业务库不同库、Redis 与 DB 不同步),对账是最后防线。

为什么重试要加预算:下游故障时上游全部请求超时 → 全部触发重试 → 流量×N 倍涌入本已瘫痪的下游 → 恢复被推迟甚至彻底压死(正反馈放大)。重试预算把"允许重试的比例"与整体成功率挂钩:成功率越低,预算越紧,自动抑制放大。层级调用时每层的重试必须计入同一总预算(否则 3 层 × 3 次 = 27 倍放大)。

图示

Client ──pay(order=88, idemKey=K1)──► Server
Server: insert dedup(K1, result)  ← 唯一键
  首次: 成功入账, 返回 result
  重复: 插入冲突 → 查表返回原 result  (业务不重复执行)
重试策略: 100ms×2^n + rand(jitter), 最多 5 次, 预算 ≤10% 总流量

实例或案例

  • 支付回调:网关重复通知是常态,商户以"订单号+金额+状态"建唯一索引,重复通知幂等返回 success。
  • HTTP Idempotency-Key:Stripe 的标准做法——键保留 24h,重复请求返回首次结果并在响应头标注 idempotent-replayed: true。
  • 消息消费去重:RocketMQ 的 key 去重 + 消费端去重表双保险;Flink 的幂等 sink。

常见误区

  • 误区一:"接口加了幂等键就万事大吉"。键要绑定业务语义(同一操作同一键),客户端生成键的错误(每次重试换新键)会让幂等失效——键的生命周期属于业务操作而非 HTTP 请求。
  • 误区二:"去重表可以跟业务库分开部署"。两库无法同事务,窗口期内崩溃会出现"业务成功但去重没记录",重复仍会穿透;同库同事务才是强保证。
  • 误区三:"立即重试几次总没错"。无退避的立即重试在下游拥塞时火上浇油;重试必须退避 + 抖动 + 预算三件套。

与其他知识点的关系

  • kp-006:本条是其工程展开。
  • kp-008/024:至少一次语义与 outbox 链路的消费端配套。
  • kp-030:重试预算与熔断/限流共同构成流量防护体系。

自测题

  1. 为什么推荐"条件更新"式的天然幂等?

答:状态机条件让重复执行自动不生效,无需额外去重存储与两库一致性问题,实现成本与故障面最小。

  1. 去重窗口应设多长?

答:必须覆盖上游可能的最大重试跨度(含 MQ 重投、人工重放),通常取业务对账周期与上游重试上限的较大值。

  1. 重试预算解决什么问题?

答:防止故障时重试流量正反馈放大(retry storm);把重试比例与整体成功率挂钩,自动在下游恶化时收敛重试量。

延伸阅读

  • Stripe API 文档:Idempotency(工程范本)。
  • AWS Builders' Library: "Timeouts, retries, and backoff with jitter"。
  • Google SRE Book 第 22 章(级联失败与重试)。