高并发扣款指南:如何终结超花危机?

75 阅读2分钟

高并发扣款指南:如何终结超花危机?

背景

  1. 业务场景

    • 新活动刚开始,会有一段时间的高峰期。
    • 部分用户会利用脚本发起批量操作,导致系统面临以下挑战:
      1. 超卖风险:账户余额仅10元,但并发请求可能导致实际扣减超过10元(如两笔6元扣减后余额变为-2元)。
  2. 核心问题

    • 传统方案仅依赖数据库事务(如先查余额再扣减),无法应对高并发竞态条件。
  3. 业务目的

    • 零资损:100%规避超卖问题
    • 低延迟:单请求响应时间<50ms(满足上万QPS需求)
    • 高可用:系统可用性 ≥99.99%(7×24小时运行)

回答

这是一个典型的超卖问题。

  • 传统方案:

    1. 先查一下余额,判断下够不够。如果够就扣减;不够就不扣减。

      这种方式,最简单的,也最容易出错。

    2. 问题:出现多个并发请求时,就会出现查询的时候金额都是够的,然后,就更新成负数了。

image-20250401222246916

1、 分布式锁
  1. 所有的余额扣减的请求排队执行
    • 在查询用户余额之前,针对账户先加一把锁。只有抢到锁的线程才能进行余额的查询和扣减。
    • 缺陷:加锁会降低并发度

image-20250311112834090

2、 数据库乐观锁

只有剩余金额(balance)大于本次扣减金额(amount)的时候,SQL才能执行成功,否则SQL更新结果就是0条,无法更新成功。

image-20250311112959000

3、 Redis + lua(最常见的方案)
  • 最常见的方案,就是 Redis + lua 的方案来防超卖。

    1. Redis 是单线程,所以,命令的执行天然就是排队的。

    2. lua 脚本,可以保证多个命令以原子性的方式执行。

      我们可以,在一个 lua 脚本中完成余额的查询、判断以及扣减的一系列组合动作。

image-20250311113704417

思考

引入 redis + lua 的方案来解决超卖问题,会带来 Redis中的余额和数据库中的余额的一致性问题。

通常的解决方案: MQ的重试+旁路+对账

看看这个方案怎么实现?会有什么问题?