如何解决高并发下的库存抢购超卖少买问题?(持续更新中)

一键三连,让算法推荐更多类似干货 ~  
你最近面试被问到的最难的题是什么?         评论区交流,我来解答
突然发现自己欠了一屁股 技术债 ?               别薅头发了  ——   关注本专栏,带你清债~

一、口诀:

库存防超卖,和版本带;
Redis 先扣减,异步同步快;
限流加队列,拆分监控在。

二、答案:

锁、缓存、流量削峰、监控

1. 数据库层:保证库存操作的原子性

  • 悲观锁:使用SELECT ... FOR UPDATE锁定库存记录,确保同一时间只有一个请求能修改库存,避免并发冲突。
  • 例如:
    SELECT inventory FROM goods WHERE id = 1 FOR UPDATE;  
    UPDATE goods SET inventory = inventory - 1 WHERE id = 1 AND inventory > 0;  
    
  • 乐观锁通过版本号或库存条件控制,例如在更新时校验库存是否足够,避免超卖:
    UPDATE goods SET inventory = inventory - 1, version = version + 1 WHERE id = 1 AND inventory > 0 AND version = 1;  
    

锁的选择核心是匹配冲突频率:
冲突多(秒杀)→ 悲观锁(避免重试浪费资源)。
冲突少(优惠券)→ 乐观锁(提升吞吐量)。

  • 字段约束将库存字段设为无符号整数,当库存为 0 时再减 1 会直接报错,从数据库层面阻止超卖。

2. 缓存层:减少数据库压力,快速响应

  • Redis 预存库存将库存提前加载到 Redis,抢购时先在 Redis 中扣减库存(如使用DECR命令),成功后再异步同步到数据库。通过 Lua 脚本保证扣减的原子性,避免并发问题:

    lua

    -- 若库存>0则扣减,返回1;否则返回0  
    if redis.call('get', KEYS[1]) > 0 then  
      return redis.call('decr', KEYS[1])  
    else  
      return 0  
    end  
    
  • 库存拆分将热点商品的库存拆分为多个子库存(如 10 万库存拆成 20 份),随机轮询扣减子库存,分散 Redis 单分片压力,避免单点瓶颈。

3. 流量控制:避免超预期请求冲击

  • 限流通过令牌桶算法限制每秒请求数(如 10 万 QPS),超出阈值的请求直接返回 “活动拥挤”,避免系统过载。
  • 异步处理使用消息队列(如 RocketMQ)接收抢购请求,消费者异步处理库存扣减和订单创建,削峰填谷,避免瞬间压力压垮数据库。

4. 兜底措施:应对极端情况

  • 库存预热与监控活动前预热库存到 Redis,实时监控库存余量和接口响应时间,异常时触发报警。
  • 库存校验订单创建后定期核对 Redis 与数据库的库存一致性,发现偏差时通过补偿机制修正(如回滚异常订单释放库存)。

三、STAR 法则回答

  • 情境(S)电商秒杀活动中,某商品库存 1000 件,预计峰值 QPS 达 10 万,需避免超卖或少买,确保数据准确。
  • 任务(T)设计方案解决高并发下的库存一致性问题,保证抢购过程中库存不超发、不遗漏。
  • 行动(A)
    1. 数据库层:使用乐观锁(版本号)控制库存更新,确保只有库存 > 0 时才能扣减;
    2. 缓存层:将库存预存到 Redis,用 Lua 脚本原子性扣减,热点库存拆分为 10 个子库存分散压力;
    3. 流量控制:通过令牌桶限流每秒 10 万请求,用 RocketMQ 异步处理订单,避免直接冲击数据库;
    4. 兜底措施:实时监控库存数据,定时核对 Redis 与数据库一致性,异常时自动补偿。
  • 结果(R)活动期间成功处理 12 万 QPS,库存准确扣减至 0,无超卖少买,响应时间 < 200ms,活动顺利完成。

    四、生活例子

    类比 “热门餐厅抢座位”:

  • 数据库锁就像 “服务员一次只接待一位顾客登记座位”,避免多人同时抢同一座位;
  • Redis 缓存好比 “门口电子屏实时显示余位”,顾客先看屏再进店,减少对服务员的询问;
  • 库存拆分类似 “将 10 个座位分成多个区域,每个区域单独叫号”,避免拥挤;
  • 限流如同 “餐厅规定每分钟最多放 5 人进店”,防止内部混乱。

(点个赞,算法会给你推荐更多类似干货 ~        持续更新中,点关注,看下篇文章)

鉴于面试官喜欢逮住一个方向连环问,那我们再阐述一下周边问题:

(二)、悲观锁、乐观锁的适用场景是什么?

口诀:

高冲突,悲观锁,锁定资源防出错;
低冲突,乐观锁无锁重试效率多。

1. 悲观锁:秒杀场景的 “铁将军”
  • 适用场景:秒杀、热门商品抢购等高冲突场景(如 10 万 QPS 争抢 100 件库存)。
  • 核心逻辑:通过SELECT ... FOR UPDATE直接锁定库存记录,同一时间只允许一个请求操作,从根源避免并发冲突。
  • 为何适配:秒杀中冲突极频繁,悲观锁 “先锁后操作” 能杜绝超卖,牺牲部分吞吐量换数据安全。
2. 乐观锁:优惠券场景的 “轻骑兵”
  • 适用场景:优惠券发放、普通商品库存扣减等中低冲突场景(如 10 万张券分散领取)。
  • 核心逻辑:通过版本号(WHERE version = x)或库存条件(WHERE inventory > 0)实现无锁化更新,冲突时重试。
  • 为何适配:冲突少,乐观锁 “先操作后校验” 能提升吞吐量,重试成本低。
3. 反向使用的坑
  • 秒杀用乐观锁:高频冲突导致大量重试失败,数据库压力飙升,可能引发超卖。
  • 优惠券用悲观锁:低频冲突下锁等待拖慢响应,吞吐量骤降,影响用户体验。

一键三连,让算法推荐更多类似干货 ~             持续更新中~ 
你最近面试被问到的最难的题是什么?            评论区交流,我来解答)
突然发现自己欠了一屁股 技术债 ?                  别薅头发了  ——   关注本专栏,带你清债~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值