Prompts-MCP 686 skills · ai / design / design-pattern / framework / fundamentals / habit / lang / tech-selection /mcp/sse
fundamentals fundamentals/distributed-theory/idempotent-design.md high

distributed-idempotent-design

幂等设计 — 同一请求/消息重复执行结果一致,手段按场景选:唯一索引/去重表(插入类)、Token 令牌(防重复提交)、状态机(流转类)、乐观锁(更新类),接口幂等 + 消息幂等。Use when 接口可能被重试 / MQ 至少投递一次会重复 / 设计防重复提交时。

幂等idempotent防重复提交去重表唯一索引状态机乐观锁
paths
  • *.java

分布式理论 · 幂等设计

本条只管「怎么保证重复执行只生效一次」。它是最终一致事务的前提,方案选型见 transaction-solutions.md;乐观锁的 ID/version 落地不在这。

为什么要幂等

网络重试、MQ「至少投递一次」、用户重复点击——同一操作会重复到达。幂等 = 重复执行 N 次与执行 1 次效果相同

手段按场景选

操作类型 手段 怎么做
插入(创建订单/支付单) 唯一索引 / 去重表 业务唯一键建唯一约束,重复插入直接被 DB 挡
防重复提交(表单/下单) Token 令牌 进页面发 token,提交校验并删除,重复提交 token 已失效
状态流转(待支付→已支付) 状态机 只允许合法前置态流转,重复请求落在错误前置态被拒
更新(扣库存/改余额) 乐观锁 update ... set v=v+1 where id=? and v=?,影响行数为 0 即重复
通用消息消费 去重表 + 唯一 msgId 消费前查/插 msgId,已存在则跳过

正例

// ✅ 唯一键去重:靠 DB 唯一索引兜底,捕获重复键即视为幂等命中
try {
    orderMapper.insert(order);            // uk: (user_id, biz_no)
} catch (DuplicateKeyException e) {
    return queryExisting(order.getBizNo()); // 重复请求返回已有结果,不报错
}
// ✅ 乐观锁更新,影响行数=0 说明已被处理过(重复)
int rows = accountMapper.deduct(id, amount, version);
if (rows == 0) { /* 已处理或并发,按业务幂等返回 */ }

反例

❌ 只在应用层 if (exists) 判重不加 DB 唯一约束:并发下两条同时过检查双插入
❌ 消费 MQ 不做去重:至少投递一次 → 同条消息消费两次 → 重复扣款
❌ 用「查到再插」两步且无唯一索引兜底:典型 check-then-act 竞态
❌ 把幂等键设成业务无关的随机值:重试时键变了,根本判不出是同一请求

自检

  • [ ] 幂等键是业务唯一的(订单号/msgId),重试时保持不变?
  • [ ] 插入类有 DB 唯一索引兜底,不只靠应用层判断?
  • [ ] 更新类用乐观锁/状态机,靠影响行数/前置态判重复?
  • [ ] MQ 消费端做了去重(至少投递一次必然重复)?

相关