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. 自检
- 六种方案各自用什么做去重键?
- 为什么说"最终防线是数据库约束"?
- 状态机幂等的适用前提?
- POST 为什么不幂等?怎么补?
- 幂等键放 Redis 时要额外存什么?
8. 下一篇
S07-A01 · 安全框架选型:Spring Security vs Shiro
幂等讲完,进架构师层:认证与授权该放在哪一层,两个框架怎么选。
本篇属于系列《S07 · Spring 框架》。旧文来源:A-2372/1559;扩展点 E-S07-017。
S07-J05 · 接口幂等:六种方案与选型
https://www.clxhxhhr.top/posts/5278/ 评论
0 条
还没有评论,先写一条吧。