1171 字
约 3 分钟
7
S07-J05 · 接口幂等:六种方案与选型

适用读者:被"用户点了两次,扣了两次钱"教育过的人。
前置:S07-B06。
覆盖旧文:2372(671) 1559(56) = 727 点。
覆盖扩展:E-S07-017(六种方案对比)。
上一篇:S07-J04 · 定时任务与异步:@Scheduled / 线程池 / 重试
下一篇:S07-A01 · 安全框架选型。


1. 一句话结论

幂等不是"加个锁",而是"让同一个请求执行多次的结果与一次相同"。六种方案的区别只在"用什么做去重键"和"在哪个环节拦"。

方案 去重键 拦在哪
唯一索引 业务键(订单号) 数据库(最终兜底)
去重表 / token 预发 token 应用层
状态机 当前状态 应用层(状态不符则拒)
乐观锁(version) 版本号 数据库
分布式锁 业务键 Redis/ZK
幂等键 + 缓存 请求 ID Redis

2. 六个方案逐个说(E-S07-017)

2.1 唯一索引(最可靠)

alter table t_order add unique key uk_order_no (order_no);
优点 缺点
数据库级保证,代码写错也拦住 需设计业务唯一键
无额外组件 冲突要处理异常

判据:"有天然业务唯一键吗?" 有 → 首选唯一索引。

2.2 去重表 / 预发 token

① 前端先取 token → ② 提交时带 token → ③ 服务端 insert token(唯一索引)成功才处理

适合"表单重复提交",能挡住"用户连点"。

2.3 状态机

待支付 → 已支付:只有"待支付"能执行支付

判据:"这个操作有明确的前置状态吗?" 有 → 状态机是最自然的幂等。

2.4 乐观锁

update t set status = 'PAID', version = version + 1 where id = ? and version = ?

影响行数 0 → 说明已被改过 → 视为重复。

2.5 分布式锁

SET key NX PX 3000   → 抢到才执行

注意:锁只能缩短竞争窗口,不能替代数据库约束(进程崩溃/超时后锁失效)。

2.6 幂等键 + Redis

SET idem:{requestId} 1 NX EX 86400   → 成功则处理,失败则直接返回上次结果

适合"跨多个步骤的请求",需要存上次结果(否则第二次只能返回"处理中")。


3. 选型决策树

有天然业务唯一键(订单号/流水号)?
├── 有 → 唯一索引(+ 状态机)
└── 没有 → 是表单重复提交?
        ├── 是 → token / 去重表
        └── 否 → 是"多步骤长请求"?
                ├── 是 → 幂等键 + 结果缓存
                └── 否 → 乐观锁 / 状态机

4. 三个常见错误

错误 后果
只在应用层加锁 多实例/重启后失效
幂等键存在内存(ConcurrentHashMap) 重启丢失、多实例不共享
只判"请求是否重复",不判"业务是否已完成" 重复请求仍会重复执行

核心认知:幂等的最终防线永远是"数据库约束"——应用层的锁/缓存都是优化。


5. HTTP 语义补充(A-1559)

方法 幂等
GET / PUT / DELETE ✅(同一个 PUT 多次结果相同)
POST ❌(默认不幂等)

所以"下单"这类 POST 接口必须自己做幂等——协议不提供。


6. 五个误区

误区 真相
"加了分布式锁就幂等" 锁失效/重启后仍需数据库约束
"前端禁用按钮就够了" 网络重试/攻击绕过
"幂等就是防重复提交" 还包括"重复执行不产生副作用"
"POST 天然幂等" 不幂等
"幂等键存内存就行" 多实例/重启失效

7. 自检

  1. 六种方案各自用什么做去重键?
  2. 为什么说"最终防线是数据库约束"?
  3. 状态机幂等的适用前提?
  4. POST 为什么不幂等?怎么补?
  5. 幂等键放 Redis 时要额外存什么?

8. 下一篇

S07-A01 · 安全框架选型:Spring Security vs Shiro

幂等讲完,进架构师层:认证与授权该放在哪一层,两个框架怎么选。


本篇属于系列《S07 · Spring 框架》。旧文来源:A-2372/1559;扩展点 E-S07-017。

S07-J05 · 接口幂等:六种方案与选型
https://www.clxhxhhr.top/posts/5278/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。