📚 幂等性:定义与实现方案(面试核心)
1. 📖 幂等性定义
- 核心:同一操作执行一次或多次,结果完全一致(无副作用、数据无异常变更)。
- 场景:网络重试、消息重复消费、前端重复提交、分布式系统超时重试等。
- 示例:
- 正确:重复调用“查询用户信息”“支付成功后回调”,结果不变;
- 错误:重复调用“扣减余额”,导致余额多次减少。
2. 🔑 需保证幂等性的核心场景
- 接口层面:REST API(POST/PUT)、RPC 接口、消息队列消费;
- 业务层面:支付回调、订单创建、库存扣减、数据同步。
3. ✅ 幂等性实现方案(按优先级+实用性排序)
方案1:唯一标识符(Token)机制(推荐首选)
- 逻辑:请求前获取唯一 Token,请求时携带 Token,服务端验证 Token 有效性(未使用则执行操作,已使用则直接返回结果)。
- 实现步骤:
- 前端请求 Token:用户发起操作前,调用服务端“获取 Token”接口(Token 可存在 Redis,设置过期时间);
- 前端携带 Token 请求:提交订单/支付时,HTTP 头/参数携带 Token;
- 服务端校验:
- 若 Token 不存在/已使用 → 返回重复请求结果;
- 若 Token 有效 → 执行业务逻辑 → 标记 Token 为“已使用” → 返回结果。
- 适用场景:前端重复提交(如订单提交、表单提交)、支付回调。
方案2:唯一业务主键去重(最常用)
- 逻辑:利用业务唯一标识(如订单号、流水号),确保重复请求仅执行一次。
- 实现方式:
- 数据库唯一约束:订单号作为主键/唯一索引,重复插入时触发主键冲突,直接返回成功(视为已执行);
- 状态机校验:订单状态从“待支付”→“已支付”,重复回调时判断状态,已完成则直接返回;
- 缓存记录:执行前查询 Redis(key=订单号,value=状态),存在则返回,不存在则执行并写入缓存。
- 适用场景:订单创建、支付回调、库存扣减(用商品ID+用户ID作为唯一键)。
方案3:幂等性查询参数(GET 接口天然幂等)
- 逻辑:查询类接口(GET)仅返回数据,不修改状态,天然满足幂等性;
- 注意:避免用 GET 接口执行写操作(如
GET /order/create?orderId=123),违反 REST 规范且不幂等。
方案4:乐观锁机制(更新操作幂等)
- 逻辑:通过版本号/时间戳控制更新,仅当版本号匹配时执行更新,重复请求会因版本号不匹配失败。
- 实现步骤:
- 表新增版本号字段:
version INT DEFAULT 1;
- 更新 SQL:
UPDATE order SET status=2, version=version+1 WHERE order_id=? AND version=?;
- 结果判断:若影响行数=0 → 重复更新,直接返回成功;若影响行数=1 → 更新成功。
- 适用场景:库存扣减、订单状态更新、用户余额修改。
方案5:悲观锁机制(强一致性场景)
- 逻辑:执行操作前锁定资源(行锁/表锁),防止并发重复执行,重复请求会阻塞直到锁释放,再判断状态。
- 实现:
SELECT * FROM order WHERE order_id=? FOR UPDATE(行锁),锁定后校验状态,已执行则返回。
- 注意:慎用表锁,避免性能瓶颈;适用于低并发、强一致性场景。
方案6:基于状态机的幂等(业务逻辑层面)
- 逻辑:业务数据存在明确状态流转(如订单:待支付→已支付→已发货),重复请求时若状态已流转到目标状态/后续状态,则直接返回成功。
- 示例:支付回调重复触发时,若订单状态已为“已支付”,则直接返回“回调成功”,不执行后续逻辑。
4. 🚀 生产环境最佳实践组合
| 业务场景 |
推荐方案组合 |
优势 |
| 前端重复提交(订单/表单) |
Token 机制 + 唯一订单号约束 |
双重保障,防止前端/后端重复请求 |
| 支付回调(第三方重复通知) |
唯一流水号去重 + 状态机校验 |
兼容网络重试,避免重复入账 |
| 库存扣减/余额更新 |
乐观锁 + 唯一业务键缓存 |
高并发支持,防止超卖/多扣 |
| 数据同步(MySQL→ES) |
唯一主键去重 + 版本号校验 |
避免重复同步,保障数据一致性 |
5. ⚠️ 避坑指南
- 避免过度设计:查询接口无需额外处理,写操作优先用“唯一主键+乐观锁”;
- Token 需全局唯一:用雪花算法/UUID 生成,避免重复;
- 缓存去重需设过期时间:防止 Redis 内存溢出(如订单 Token 过期时间=1小时);
- 异常处理:重复请求需返回与正常请求一致的响应格式(如同样返回
{"code":200,"msg":"success"}),避免前端处理异常。