分布式场景下接口幂等性保证方案
幂等性:指同一个请求执行一次和执行多次的结果完全一致,不会因为重复调用产生脏数据、重复扣款、重复下单等问题。
分布式系统中,网络超时、重试机制、前端重复提交、消息重复消费都会导致接口被重复调用,必须强制保证幂等性。
我会从核心原理、适用场景、实现方案、最佳实践四个维度,给你最完整、可落地的总结。
一、先明确:哪些接口必须做幂等?
非幂等接口:执行多次会改变数据状态
- 新增接口(insert)
- 扣款/扣库存/支付接口
- 提交订单接口
- 发送消息/推送接口
天然幂等接口:执行多次无影响
- 查询接口(select)
- 修改指定状态的接口(
set status=1) - 删除指定ID接口(
delete where id=1)
二、分布式幂等性 6 种主流实现方案(按推荐优先级排序)
1. 唯一索引 / 唯一约束(最简单、最推荐)
核心思想:利用数据库唯一索引天然防重复,重复插入会直接报错,业务捕获异常即可。
适用场景:
- 用户注册(手机号唯一)
- 订单创建(订单号唯一)
- 日志/流水表插入
实现步骤:
- 给业务唯一字段(订单号、手机号、业务编号)建唯一索引
- 插入时捕获
DuplicateKeyException - 捕获后直接返回成功,视为幂等处理
优点:实现最简单、无侵入、可靠性高
缺点:只适用于插入场景,不适合更新/扣款场景
2. 分布式锁(Redis / Zookeeper)
核心思想:重复请求进来时,只有一个能拿到锁执行,其他请求直接拦截。
适用场景:
- 高并发秒杀、库存扣减
- 支付、退款
- 所有需要强防重的写操作
实现步骤(Redis 最常用):
- 请求携带唯一标识(订单号、用户ID+业务号)
- 先尝试
SETNX加锁(设置过期时间) - 加锁成功 → 执行业务
- 加锁失败 → 直接返回“重复请求”/幂等成功
关键:必须用Redis 原子操作,防止锁超时、死锁。
优点:适用范围极广、性能好
缺点:需要引入 Redis,有锁超时风险
3. 前端 Token 机制(最通用、企业标准方案)
核心思想:先获取 Token,再用 Token 请求,Token 一次性有效。
适用场景:
- 表单提交(订单、支付)
- 前端防重复点击 + 后端防重复提交
完整流程:
- 进入页面时,前端请求后端生成全局唯一 Token(存在 Redis)
- 页面渲染时把 Token 隐藏带入表单
- 提交接口时,必须携带 Token
- 后端使用
Redis DEL原子操作删除 Token- 删除成功 → 放行执行业务
- 删除失败 → 说明重复请求,直接返回
为什么用 DEL?
- 原子操作,绝对保证只执行一次
- 比 GET + DELETE 更安全,不会出现并发问题
优点:覆盖全场景、无业务侵入、最安全
缺点:多一次请求获取 Token
4. 状态机幂等(业务状态流转控制)
核心思想:只有当前状态允许,才能流转到下一个状态,重复请求会被状态拦截。
适用场景:
- 订单流程(待支付 → 已支付 → 已发货)
- 审批流程
- 所有有明确状态流转的业务
示例 SQL:
UPDATEorderSETstatus=2WHEREid=?ANDstatus=1;- 状态=1(待支付)才能改成 2(已支付)
- 重复执行时,条件不满足,更新行数=0,直接返回
优点:业务天然幂等、无额外组件依赖
缺点:只适用于有状态流转的业务
5. 悲观锁 / 乐观锁
(1)悲观锁(数据库锁)
SELECT*FROMtableWHEREid=?FORUPDATE;- 同一数据同一时间只能一个请求操作
- 适用于低并发、强一致性场景
- 不推荐分布式高并发
(2)乐观锁(版本号机制)
UPDATEtableSETamount=amount-1,version=version+1WHEREid=?ANDversion=?;- 版本号匹配才执行
- 高并发性能好
- 适用于扣款、库存更新
6. 全局唯一请求号(上游传入)
核心思想:上游调用方传入全局唯一 requestId,后端存储并校验。
适用场景:
- 微服务之间调用
- 第三方回调(支付回调)
- MQ 消息消费
实现:
- 用 requestId 作为唯一键存入 Redis/DB
- 存在则直接返回,不存在则执行
三、方案选型速查表(直接照抄用)
| 方案 | 适用场景 | 优点 | 缺点 | 推荐指数 |
|---|---|---|---|---|
| 唯一索引 | 插入、防重复数据 | 简单、可靠 | 仅插入 | ⭐⭐⭐⭐⭐ |
| Token 机制 | 表单提交、订单、支付 | 通用、安全 | 多一次请求 | ⭐⭐⭐⭐⭐ |
| 分布式锁 | 高并发、扣款、秒杀 | 性能好 | 需 Redis | ⭐⭐⭐⭐ |
| 状态机 | 订单/流程状态流转 | 无侵入 | 业务限定 | ⭐⭐⭐⭐ |
| 乐观锁 | 更新、扣库存 | 高并发 | 需版本字段 | ⭐⭐⭐ |
| 悲观锁 | 低并发强一致 | 简单 | 性能差 | ⭐ |
四、企业级最佳实践(必看)
- 查询接口天然幂等,不用处理
- 插入优先用唯一索引
- 订单/支付/表单提交 必用 Token 机制
- 高并发扣减 用分布式锁 + 乐观锁
- 状态流转业务 用状态机控制
- 所有幂等控制必须是原子操作(Redis SET/DEL、数据库唯一索引/行锁)
- 重复请求不抛异常,直接返回成功(幂等语义)
五、伪代码示例(Token 方案,最常用)
// 1. 获取 Token 接口StringgetToken(){Stringtoken=UUID.randomUUID().toString();redis.set(token,token,5,TimeUnit.MINUTES);// 5分钟过期returntoken;}// 2. 业务提交接口(幂等核心)Resultsubmit(Stringtoken,Orderorder){// 原子删除:成功=第一次请求,失败=重复请求Booleansuccess=redis.delete(token);if(!success){returnResult.success("重复请求,已幂等处理");}// 正常执行业务orderService.save(order);returnResult.success("提交成功");}总结
- 幂等性 = 一次请求和多次请求结果一致
- 分布式下最通用方案:Token 机制
- 最简单方案:数据库唯一索引
- 高并发方案:Redis 分布式锁
- 所有实现必须基于原子操作,才能保证绝对安全
