ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

后端幂等性设计实战:重复请求问题一次讲清楚

后端幂等性设计实战:重复请求问题一次讲清楚 用户点击“提交订单”后网络卡顿又点了一次结果生成了两笔订单支付回调因网络抖动重试了三次账户被扣了三笔钱MQ 消费者重复消费同一条消息积分发了两次。这些问题的根源只有一个接口没有保证幂等性。幂等性不是可选项而是分布式系统的生存底线。本文从实战角度把重复请求的来源和五种主流解决方案一次讲清楚。重复请求从哪来重复请求的来源远比想象中多用户误操作按钮无响应时反复点击或浏览器回退后重新提交。网络超时重试客户端或网关在超时后自动重发但服务端可能已经处理成功。MQ 重复消费消息队列为保证至少一次投递消费者可能收到重复消息。定时任务重复执行分布式环境下多个节点同时触发同一任务。第三方回调重试支付、物流等外部系统在未收到确认时反复回调。这些场景的共同点是请求可能被重复投递但业务只能执行一次。幂等性设计的目标就是让同一请求执行多次与执行一次的效果相同。方案一Token 机制——前端防重最经典的方案是“先申请令牌再携带令牌提交”。用户进入提交页时服务端生成一个全局唯一的 token 存入 Redis并返回给前端。提交时前端带上 token服务端用 Lua 脚本原子性地删除 token删除成功说明是首次请求放行删除失败说明 token 已不存在判定为重复请求直接拒绝。java复制下载// 申请 token String token UUID.randomUUID().toString(); redis.setex(idem:token: token, 300, 1); // 提交时校验 Long deleted redis.execute( new DefaultRedisScript( if redis.call(del, KEYS[1]) 1 then return 1 else return 0 end, Long.class ), Collections.singletonList(idem:token: token) ); if (deleted 0) { throw new BizException(请勿重复提交); }Token 机制的优点是通用性强、实现简单适合表单提交、订单创建等前端可控的场景。缺点是依赖前端配合且 token 只能使用一次不适合需要多次调用的接口。方案二数据库唯一索引——最可靠的兜底无论前端做了多少防重后端都必须有最终防线。数据库唯一索引是最简单也最可靠的方案为业务唯一键如订单号、流水号、用户 ID 业务类型建立唯一索引。重复插入时数据库会抛出DuplicateKeyException捕获后返回“已处理”即可。sql复制下载ALTER TABLE orders ADD UNIQUE KEY uk_order_no (order_no);唯一索引的可靠性极高不依赖任何中间件是分布式系统的最后一道防线。但它只能防插入不能防更新。对于更新操作需要结合状态机或乐观锁。方案三状态机——用业务状态防重很多重复请求本质上是状态流转的重复触发。比如订单从“待支付”到“已支付”只允许执行一次。设计状态机时把更新操作带上当前状态作为条件sql复制下载UPDATE orders SET status PAID WHERE order_no ? AND status PENDING;如果影响行数为 0说明订单状态已经不是“待支付”要么已被处理要么状态非法直接返回成功或抛出业务异常。状态机方案贴合业务语义天然幂等但要求每个业务操作都明确前置状态。方案四分布式锁——控制并发窗口当多个节点同时处理同一请求时可以用分布式锁把并发串行化。以 Redis 的SET NX PX为例java复制下载Boolean locked redis.opsForValue() .setIfAbsent(lock:order: orderNo, 1, 10, TimeUnit.SECONDS); if (Boolean.FALSE.equals(locked)) { throw new BizException(请求处理中请稍后); } try { // 执行业务逻辑 } finally { redis.delete(lock:order: orderNo); }分布式锁适合短时间内的并发防重比如防止同一用户同时发起两笔相同提现。但锁的粒度、超时时间、释放逻辑都需要仔细设计否则可能引发死锁或锁误删。生产环境建议使用 Redisson 等成熟框架。方案五去重表——异步场景的利器对于 MQ 消费、异步任务等场景可以建一张去重表记录已处理的消息 ID 或业务唯一键。每次消费前先插入去重表插入成功则处理插入失败则跳过。去重表与业务表在同一个数据库事务中保证原子性。sql复制下载INSERT INTO idempotent_record (biz_id, status, create_time) VALUES (?, PROCESSING, NOW());如果插入成功继续执行业务如果唯一键冲突直接确认消息。处理完成后更新状态为DONE。去重表方案适合高吞吐的异步场景但需要定期清理历史数据避免表无限膨胀。实战建议分层防御缺一不可幂等性设计没有银弹最佳实践是分层防御前端按钮置灰、Token 防重减少无效请求。网关对同一用户、同一接口做短时间限流。服务端唯一索引兜底状态机控制流转分布式锁控制并发。异步层去重表 幂等消费者保证消息不重复处理。此外所有幂等方案都要考虑失败重试和数据一致性。比如 Token 删除后业务执行失败需要允许重新申请唯一索引冲突后要返回明确的业务提示而不是 500 错误。总结幂等性是后端工程师的基本功也是面试中的高频考点。核心思想是用唯一标识、状态条件或去重记录确保同一请求只产生一次业务效果。Token 防前端重复提交唯一索引兜底状态机控制流转分布式锁应对并发去重表处理异步。根据业务场景组合使用才能构建真正可靠的幂等体系。记住不要相信任何客户端后端必须自己守住最后一道门。
返回列表