库存超卖场景

以下是针对「高并发库存抢购」问题的技术方案总结,结合常见场景和解决方案,按优先级排序讲解:


一、核心问题

高并发场景下,库存扣减易出现:

  • 超卖:多个线程同时读取库存并扣减,导致库存为负。
  • 锁竞争:悲观锁(如 synchronized)在高并发下性能低下。
  • 热点数据:单个库存 key 成为性能瓶颈。

二、解决方案与技术选型

1. 错误做法:锁竞争(26.1)
  • 问题:使用 JVM 级别锁(如 synchronized)或数据库行锁,高并发下线程阻塞严重。
  • 后果:吞吐量骤降,甚至雪崩。
  • 改进方向:避免全局锁,转向分布式锁或无锁方案。
2. 原子操作(26.2)
  • 技术:Redis DECR 或 INCRBY 命令。
  • 适用场景:单 key 库存,如 stock:prod_1
  • 问题:仅支持单个 key,无法应对多 key 场景(如库存拆分)。
  • 优化:配合 Lua 脚本保证原子性(见 27.7)。
3. 令牌库存(26.3)
  • 技术:预先生成 N 个令牌(如 Redis Set),每次扣减取走一个令牌。
  • 适用场景:秒杀活动,库存量已知且固定。
  • 问题:令牌耗尽后无法扩容,灵活性差。
4. 多库存秒杀(26.4)
  • 技术:库存拆分 + 随机 key 选择。
  • 实现

java

复制

  // 库存拆分为 10 个 key:stock:prod_1:0 ~ stock:prod_1:9
  for (int i = 0; i < 10; i++) {
      String key = "stock:prod_1:" + random.nextInt(10);
      long stock = redis.decr(key); // 原子扣减
      if (stock >= 0) return true; // 成功
  }
  • 优势:分散热点,减少锁竞争。
  • 问题:最后少量库存需多次尝试,可结合 Lua 脚本优化(见 27.7)。
5. 自旋互斥超时锁(26.5)
  • 技术:Redis 分布式锁(如 SETNX) + 自旋重试。
  • 适用场景:需要强一致性的场景。
  • 问题:锁过期风险(如 GC 导致锁提前释放),高并发下性能下降。
  • 优化:使用 Redlock 算法或 Redisson 的高级锁。
6. CAS 乐观锁(26.6)
  • 技术:基于版本号的 Compare-And-Swap(如 Redis WATCH + MULTI)。
  • 适用场景:冲突概率低的场景。
  • 问题:高并发下冲突频繁,导致大量重试。
  • 优化:结合 Lua 脚本减少网络往返(见 27.7)。
7. Redis Lua 方式实现 Redis 锁(26.7)
  • 技术:Redis 内置 Lua 脚本,保证操作原子性。
  • 示例

lua

复制

  local stock = redis.call("GET", KEYS[1]);
  if tonumber(stock) > 0 then
      redis.call("DECR", KEYS[1]);
      return 1; // 成功
  else
      return 0; // 失败
  end

引用

  • 优势:避免网络延迟,原子性更强。
  • 问题:Lua 脚本不支持事务嵌套,复杂场景需谨慎。

三、技术演进路线

  1. 基础阶段:原子操作(如 DECR)解决单 key 问题。
  2. 进阶阶段:Lua 脚本封装复杂逻辑,减少网络交互。
  3. 高阶阶段:库存拆分 + 分布式锁(如 Redisson)应对超高并发。

四、面试话术

在高并发库存抢购场景中,我们通常会采取分层解决方案:

  1. 避免锁竞争:优先使用原子操作(如 Redis DECR)或 Lua 脚本,减少锁的使用。

  2. 分散热点:通过库存拆分(如多 key 存储),将请求分散到不同 Redis 节点。

  3. 兜底方案:结合分布式锁(如 Redisson)处理极端场景,但需注意锁的超时和续期问题。

  4. 最终一致性:使用消息队列异步更新数据库,保证最终一致性。

关键原则是:用空间换时间——通过增加 key 数量分散压力,而非依赖单一锁

在高并发场景下,多个用户同时抢购同一商品库存,如果没有合理的并发控制机制,容易出现“超卖”(卖出比实际库存多的商品)或“少买”(库存未被充分利用)等问题。这是因为多个线程同时读取库存、扣减库存,中间缺乏原子性控制,导致数据不一致。


二、常见错误做法

  • 先读再写:如先 get 库存,再 set 扣减,中间没有加锁或原子控制,多个线程可能同时读到相同值,导致库存扣减错误。
  • 数据库行锁:使用数据库行锁管理库存,虽然能保证一致性,但性能差,不适合高并发场景。
  • 简单自旋锁:虽然能保证互斥,但大量线程循环抢锁会极大消耗 CPU 和 Redis 资源,导致系统性能下降。

三、推荐解决方案

1. 原子操作(如 Redis decr/incr

使用 Redis 的原子指令(如 decr)直接扣减库存,通过返回值判断是否成功。例如:

bash

复制

redis> decr prod_stock
  • 优点:轻量、高效,Redis 单线程模型天然保证原子性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值