接口幂等性设计实战
❓ 面试官:什么是接口的幂等性?你们在项目中是怎么保证支付接口/下单接口的幂等性的?【中级】
频率:🔥🔥🔥🔥🔥
💡 一句话总结(先抛结论): 幂等性是指:任意多次执行所产生的影响与一次执行的影响相同。在复杂的网络环境中,为了防止用户重复点击、或者因为网络超时导致的客户端框架自动重试(比如 Feign/Ribbon 自动重试),必须在核心的“写”接口(如下单、支付、扣款)做幂等性保护,防止重复扣钱。
📝 实战主流解决方案(分场景回答,展现技术广度):
方案一:数据库唯一索引(最简单、最托底)
- 适用场景:Insert 新增操作。
- 做法:比如给流水表建一个
biz_seq_no(业务流水号)的唯一索引。第一次请求进来插进去了;如果网络抖动发了第二次请求,同样的流水号去 Insert 会直接报DuplicateKeyException。我们在代码里 catch 住这个异常,不抛出错误,而是直接返回“处理成功”。
方案二:Redis 的 Token 防重令牌机制(最通用、面试必答)
- 适用场景:前端重复提交、复杂业务的 Insert/Update 操作。
- 完整流程:
- 获取 Token:用户进入“提交订单页”时,前端先向后端请求一个全局唯一的
Token(比如用 UUID)。后端把这个 Token 存进 Redis,并设置个 10 分钟过期时间。 - 提交业务:用户点击“提交订单”,前端把刚才拿到的 Token 放在 Request Header 里面传给后端。
- 后端校验并删除 Token:后端收到请求,第一件事就是去 Redis 里拿这个 Token。利用 Redis 的 Lua 脚本或者
del命令(非常关键:必须保证查和删是原子操作) 尝试删除这个 Token。- 如果删除成功,说明这是第一次请求,放行去执行后续下单的业务逻辑。
- 如果删除失败(Redis 里找不到这 Token),说明它刚刚已经被前一个请求删过了,这是一个重复请求!直接拦截并抛出异常“请勿重复提交”。
- 获取 Token:用户进入“提交订单页”时,前端先向后端请求一个全局唯一的
方案三:乐观锁机制(版本号)
- 适用场景:Update 更新操作。
- 做法:数据库表里加一个
version字段。每次查数据的时候把 version 查出来,更新的时候带上:update account set balance = balance - 100, version = version + 1 where id = 1 and version = 1;如果同时来了两个扣款请求,只有一个能匹配上version=1更新成功,另一个会因为找不到受影响的行而更新失败,从而保证了幂等。
方案四:状态机判断
- 适用场景:订单状态流转。
- 做法:订单是有状态的(待支付 -> 已支付 -> 发货中)。如果收到一个支付成功的回调请求,先去数据库查状态。如果状态已经是“已支付”,那这个回调就是个重复消息,直接 return success 忽略即可。代码里:
update order set status = '已支付' where order_id = xxx and status = '待支付';(也是一种乐观锁思想)。