怎么保证用户重复点击支付按钮订单不会重复扣款

幂等性的五种方案:
什么是幂等性,打一个比方,用户网络卡了,连续点了三次支付,后台收到三条请求,但你只要扣一次钱,保证这个结果就是幂等性。
(1)数据库索引;这个简答粗暴,直接在订单号上建一条唯一索引,插入重复订单号时数据库,冲突异常,你捕获以后直接返回订单已存在。
优点就是稳,缺点是只能防插入重复,更新操作,他根本管不了,适合订单创建,用户注册这类纯插入的动作;
(2)token机制,用户打开支付页面,后端生成一个唯一的token存到redis,5分钟后过期,提交请求,必须带着这个token,后端拿到以后,先查再删,查不到说明处理过了关键点,查询和删除,必须原子化,所以你得用Lua脚本,一把搞定,否则高并发照样会出问题。
(3)分布式锁,用redis锁的时候,可以用订单号或者用户id,处理前先加锁,失败就返回操作进行中,锁必须带唯一标识解释时,释放的时候判断,自己是不是加了锁,用Lua脚本删;
(4)状态机校验机制,订单不都有状态的流转,待支付、支付中、已支付,每次更新状态前,先判断当前状态允不允许你往目标状态转已经是已支付的状态,再来支付请求,直接拒绝;
(5)消息队列,加表去重,这也是异步场景下的最终方案,支付请求扔到消息队列,消费端在去重表插入一条记录,插入成功就直接执行业务,失败就说明,你处理过了,但是它引入消息队列,复杂度也上去了,实时性也不如同步方案。

AI总结:

一、什么是幂等性

幂等性是分布式系统的核心设计原则:同一个请求执行一次和执行 N 次,产生的业务效果、返回结果完全一致,不会产生额外副作用。
典型场景:用户网络卡顿重复点击支付按钮,后端收到三次相同的支付请求,最终只能扣一次钱、生成一笔流水,这就是幂等性的体现。

行业内主流的幂等实现方案共五种,从简单到复杂、从同步到异步分别如下:


方案一:数据库唯一索引(主键幂等)

实现原理

利用数据库唯一索引的唯一性约束做幂等校验:选择业务唯一标识(比如订单号、用户 ID + 业务类型)作为唯一键,建立唯一索引。重复请求插入时会触发唯一键冲突异常,捕获异常后直接返回「操作已执行」,视为幂等命中。

优缺点与适用场景
  • 优点:实现简单,数据库层面强校验,结果可靠,无额外中间件依赖。
  • 缺点:仅支持新增类操作的幂等,无法处理更新类操作;高并发下数据库写入压力大;通过异常捕获做业务控制,代码优雅度较低。
  • 适用场景:订单创建、用户注册等纯插入、幂等键明确的业务场景。

方案二:去重令牌(Token)机制

实现原理

前置发放令牌 + 后端原子校验,是前端接口防重的标准方案:

  1. 用户进入操作页面(如支付页)时,后端生成一个全局唯一的 Token,存入 Redis 并设置过期时间(如 5 分钟),返回给前端;
  2. 用户提交请求时必须携带该 Token;
  3. 后端收到请求后,通过Lua 脚本原子执行「校验 Token 是否存在→存在则删除并放行→不存在则拒绝」。

关键:查询和删除必须原子化,否则高并发下两个请求同时查到 Token 存在,都会执行业务,直接导致幂等失效。

优缺点与适用场景
  • 优点:前置去重,拦截在业务层之前;性能高,Redis 操作轻量。
  • 缺点:需要额外的 Token 生成交互;仅适合前端提交类场景,无法适配系统间的内部调用。
  • 适用场景:前端表单提交、按钮重复点击防护,是 Web 接口防重的最常用方案。

方案三:分布式锁幂等

实现原理

基于 Redis/ZooKeeper 实现分布式锁,以业务唯一标识(如订单号)作为锁 Key,请求处理前先尝试加锁:

  • 加锁成功:执行业务逻辑,执行完成后释放锁;
  • 加锁失败:直接返回「操作处理中,请稍后重试」。

关键细节:锁的 Value 必须存入当前请求的唯一标识(如请求 ID),释放锁时先校验 Value 匹配,再执行删除,全程用 Lua 脚本保证原子性,避免误删其他请求的锁。

优缺点与适用场景
  • 优点:通用性强,新增、更新类操作都适用;强串行化,从根源避免并发冲突。
  • 缺点:加锁释放有性能开销;存在死锁风险(必须设置过期时间兜底)。
  • 适用场景:业务流程长、并发冲突概率高的复杂操作,比如支付核销、库存扣减。

方案四:状态机校验(乐观锁思路)

实现原理

基于业务状态的流转不可逆性做幂等控制:给业务对象设计明确的状态流转链路(比如订单:待支付 → 支付中 → 已支付 → 已完成),每次更新状态前,先校验当前状态是否允许流转到目标状态。
例如:已经是「已支付」状态的订单,再次收到支付请求时直接拒绝,因为状态不允许逆向流转。
通常会配合版本号字段做乐观锁,进一步避免并发更新冲突。

优缺点与适用场景
  • 优点:基于业务本身的状态逻辑,无额外中间件;性能好,属于乐观校验。
  • 缺点:只适合有明确状态流转的业务,通用性差。
  • 适用场景:订单状态流转、单据审核等有清晰状态机的业务场景。

方案五:消息队列 + 幂等去重表

实现原理

异步场景下的标准幂等方案:

  1. 上游将请求投递到消息队列,通过 MQ 的削峰能力承接高并发;
  2. 消费端收到消息后,先在幂等去重表(数据库唯一索引或 Redis 去重键)中插入业务唯一标识;
  3. 插入成功则执行业务逻辑;插入失败说明消息已被处理,直接 ACK 丢弃。
优缺点与适用场景
  • 优点:异步解耦,可承接高并发流量;消费端可控,适合分布式系统的异步回调场景。
  • 缺点:引入消息队列,系统复杂度上升;存在延迟,实时性弱于同步方案。
  • 适用场景:支付回调、消息通知、异步任务处理等高并发异步场景。

选型总结

  • 简单插入场景:优先用数据库唯一索引
  • 前端防重复提交:优先用 Token 令牌机制
  • 复杂并发操作:优先用分布式锁
  • 状态流转类业务:优先用状态机校验
  • 异步高并发场景:优先用消息队列 + 去重表
posted @ 2026-09-15 16:47  堭鍙銤  阅读(12)  评论(0)    收藏  举报