一、先搞懂核心概念1. 什么是接口幂等性同一请求重复发起N次业务最终结果和只请求1次完全一致不会产生脏数据、重复扣款、重复下单、重复消息消费等副作用。2. 产生重复请求的常见场景前端重复点击提交按钮、网络卡顿重复提交表单服务内部重试机制接口超时自动重试MQ消息重复投递、消费者重复消费网关/负载均衡转发异常造成重复调用3. 两类高危操作INSERT重复插入产生多条重复业务数据带计算的UPDATEset num num 1、余额扣减重复执行数据错乱简单静态更新set status1 where id1天然幂等无需额外处理。二、8种幂等实现方案按落地常用程度排序方案1唯一索引数据库兜底最推荐插入场景实现给业务唯一标识订单号、流水号、设备编码建立唯一索引/联合唯一索引。ALTERTABLEorderADDUNIQUEKEYuk_order_no(order_no);流程执行insert捕获唯一键冲突异常DuplicateKeyException捕获到异常直接返回成功保证幂等。优点数据库原生支持强一致性并发完全无问题低成本不用额外中间件。缺点仅适用于新增场景更新场景不适用大量冲突抛异常高并发下数据库压力略大。适用场景下单、支付流水、新增记录等INSERT业务。方案2防重表多业务隔离不想主表加唯一索引时用实现单独建一张防重记录表只存业务唯一ID加唯一索引防重表与业务表同一事务。表结构示例id, biz_no, create_timebiz_no唯一索引。流程事务内先插入防重表插入成功 → 执行业务操作插入冲突 → 回滚事务返回成功。优点不污染主业务表索引多业务共用一套防重逻辑缺点多一张表增加数据库IO仅支持同库事务跨库无法使用。适用场景主表不方便加唯一索引、多类型业务统一防重。方案3insert前先select简易方案并发有漏洞实现插入前根据唯一编码查询不存在再insert存在直接返回。Orderordermapper.selectByOrderNo(orderNo);if(ordernull){mapper.insert(order);}致命缺陷高并发下两个请求同时查询都为空会同时插入产生重复数据。优化必须搭配锁/唯一索引一起使用不能单独做幂等。适用场景低并发、内部管理后台、非核心业务。方案4状态机控制更新订单/流程类专用实现业务单据存在固定流转状态更新时带上原始状态作为条件只允许正向流转。例订单状态 1待支付 → 2已支付 → 3已完成-- 只有状态待支付时才更新为已支付updateordersetstatus2whereid100andstatus1;流程执行更新SQL判断受影响行数行数1正常更新行数0重复请求直接返回成功。优点无额外组件性能好天然防止逆向重复操作。缺点仅适用于有状态流转的单据订单、审批单通用场景不适用。适用场景支付回调、订单状态变更、流程审批接口。方案5乐观锁version版本号更新类并发幂等高并发首选实现表增加version版本字段更新时携带当前版本号版本匹配才更新。-- 查询selectid,amount,versionfromuserwhereid100;-- 更新updateusersetamountamount-100,versionversion1whereid100andversion1;更新行数0版本已变重复请求返回成功更新行数1正常执行业务。优点无数据库行锁并发性能远高于悲观锁不阻塞线程。缺点存在自旋重试极端高并发下大量更新失效。适用场景余额扣减、库存扣减、积分更新等计算型UPDATE。方案6悲观锁 for update强一致性低并发资金场景实现InnoDB行锁事务内先锁定该行再操作select*fromaccountwhereid100forupdate;同一行同一时间只允许一个事务持有锁其他请求阻塞等待避免重复扣减。注意查询条件必须是主键/唯一索引否则锁整张表性能灾难。优点数据绝对安全不会超扣、重复扣款。缺点并发量大时大量请求阻塞接口吞吐量暴跌。适用场景支付、转账、大额资金等对数据一致性要求极高、并发不高的场景。方案7Redis分布式锁分布式系统通用接口入口拦截主流实现Redisson推荐、Redis SETNX 过期时间避免死锁。流程用业务唯一号orderNo作为锁key抢占锁成功执行业务抢占失败直接返回成功实现幂等锁设置过期时间防止服务宕机死锁。优点分布式场景通用性能高于数据库锁可统一在网关/拦截器做全局防重。缺点依赖Redis存在缓存与数据库一致性问题锁超时需合理把控。适用场景分布式微服务、MQ消费、外部第三方接口回调。方案8Token令牌机制前端表单重复提交专用实现两次请求进入页面/打开表单前端先调用接口获取全局唯一防重Token存入Redis用户提交表单携带Token服务端先删除Redis中Token删除成功执行业务删除失败Token不存在判定重复提交直接返回。优点专门拦截前端重复点击和数据库解耦。缺点需要两次接口交互仅解决前端重复提交无法处理服务重试、MQ重复消息。适用场景页面表单提交、后台新增/编辑页面。三、方案分层选型面试加分总结前端层Token令牌拦截页面重复点击网关层Redis分布式锁全接口统一防重数据库新增唯一索引 / 防重表数据库更新状态流转状态机数据库更新数值计算乐观锁version高并发、悲观锁for update资金强一致分布式/MQ场景Redis分布式锁兜底。实际项目不会只用单一方案采用多层组合防护前端Token防重复点击 网关Redis分布式锁拦截重复调用 数据库唯一索引/乐观锁兜底层层保障接口幂等。
如何保证接口幂等性?
发布时间:2026/7/11 1:25:49
一、先搞懂核心概念1. 什么是接口幂等性同一请求重复发起N次业务最终结果和只请求1次完全一致不会产生脏数据、重复扣款、重复下单、重复消息消费等副作用。2. 产生重复请求的常见场景前端重复点击提交按钮、网络卡顿重复提交表单服务内部重试机制接口超时自动重试MQ消息重复投递、消费者重复消费网关/负载均衡转发异常造成重复调用3. 两类高危操作INSERT重复插入产生多条重复业务数据带计算的UPDATEset num num 1、余额扣减重复执行数据错乱简单静态更新set status1 where id1天然幂等无需额外处理。二、8种幂等实现方案按落地常用程度排序方案1唯一索引数据库兜底最推荐插入场景实现给业务唯一标识订单号、流水号、设备编码建立唯一索引/联合唯一索引。ALTERTABLEorderADDUNIQUEKEYuk_order_no(order_no);流程执行insert捕获唯一键冲突异常DuplicateKeyException捕获到异常直接返回成功保证幂等。优点数据库原生支持强一致性并发完全无问题低成本不用额外中间件。缺点仅适用于新增场景更新场景不适用大量冲突抛异常高并发下数据库压力略大。适用场景下单、支付流水、新增记录等INSERT业务。方案2防重表多业务隔离不想主表加唯一索引时用实现单独建一张防重记录表只存业务唯一ID加唯一索引防重表与业务表同一事务。表结构示例id, biz_no, create_timebiz_no唯一索引。流程事务内先插入防重表插入成功 → 执行业务操作插入冲突 → 回滚事务返回成功。优点不污染主业务表索引多业务共用一套防重逻辑缺点多一张表增加数据库IO仅支持同库事务跨库无法使用。适用场景主表不方便加唯一索引、多类型业务统一防重。方案3insert前先select简易方案并发有漏洞实现插入前根据唯一编码查询不存在再insert存在直接返回。Orderordermapper.selectByOrderNo(orderNo);if(ordernull){mapper.insert(order);}致命缺陷高并发下两个请求同时查询都为空会同时插入产生重复数据。优化必须搭配锁/唯一索引一起使用不能单独做幂等。适用场景低并发、内部管理后台、非核心业务。方案4状态机控制更新订单/流程类专用实现业务单据存在固定流转状态更新时带上原始状态作为条件只允许正向流转。例订单状态 1待支付 → 2已支付 → 3已完成-- 只有状态待支付时才更新为已支付updateordersetstatus2whereid100andstatus1;流程执行更新SQL判断受影响行数行数1正常更新行数0重复请求直接返回成功。优点无额外组件性能好天然防止逆向重复操作。缺点仅适用于有状态流转的单据订单、审批单通用场景不适用。适用场景支付回调、订单状态变更、流程审批接口。方案5乐观锁version版本号更新类并发幂等高并发首选实现表增加version版本字段更新时携带当前版本号版本匹配才更新。-- 查询selectid,amount,versionfromuserwhereid100;-- 更新updateusersetamountamount-100,versionversion1whereid100andversion1;更新行数0版本已变重复请求返回成功更新行数1正常执行业务。优点无数据库行锁并发性能远高于悲观锁不阻塞线程。缺点存在自旋重试极端高并发下大量更新失效。适用场景余额扣减、库存扣减、积分更新等计算型UPDATE。方案6悲观锁 for update强一致性低并发资金场景实现InnoDB行锁事务内先锁定该行再操作select*fromaccountwhereid100forupdate;同一行同一时间只允许一个事务持有锁其他请求阻塞等待避免重复扣减。注意查询条件必须是主键/唯一索引否则锁整张表性能灾难。优点数据绝对安全不会超扣、重复扣款。缺点并发量大时大量请求阻塞接口吞吐量暴跌。适用场景支付、转账、大额资金等对数据一致性要求极高、并发不高的场景。方案7Redis分布式锁分布式系统通用接口入口拦截主流实现Redisson推荐、Redis SETNX 过期时间避免死锁。流程用业务唯一号orderNo作为锁key抢占锁成功执行业务抢占失败直接返回成功实现幂等锁设置过期时间防止服务宕机死锁。优点分布式场景通用性能高于数据库锁可统一在网关/拦截器做全局防重。缺点依赖Redis存在缓存与数据库一致性问题锁超时需合理把控。适用场景分布式微服务、MQ消费、外部第三方接口回调。方案8Token令牌机制前端表单重复提交专用实现两次请求进入页面/打开表单前端先调用接口获取全局唯一防重Token存入Redis用户提交表单携带Token服务端先删除Redis中Token删除成功执行业务删除失败Token不存在判定重复提交直接返回。优点专门拦截前端重复点击和数据库解耦。缺点需要两次接口交互仅解决前端重复提交无法处理服务重试、MQ重复消息。适用场景页面表单提交、后台新增/编辑页面。三、方案分层选型面试加分总结前端层Token令牌拦截页面重复点击网关层Redis分布式锁全接口统一防重数据库新增唯一索引 / 防重表数据库更新状态流转状态机数据库更新数值计算乐观锁version高并发、悲观锁for update资金强一致分布式/MQ场景Redis分布式锁兜底。实际项目不会只用单一方案采用多层组合防护前端Token防重复点击 网关Redis分布式锁拦截重复调用 数据库唯一索引/乐观锁兜底层层保障接口幂等。