库存系统如何避免超卖和少卖?

Estimated reading: 1 minute 12 views

一、核心问题分析

问题类型原因后果
超卖并发请求下多个线程同时判定库存充足,导致扣减总和超过实际库存订单无法履约,用户投诉
少卖已扣减库存未释放(如订单未支付、系统异常回滚失败)商品滞销,营收损失

二、技术方案设计

1. 数据库层:强一致性控制

  • 方案一:乐观锁(版本号控制)
  UPDATE sku_stock 
  SET stock = stock - #{num}, 
      version = version + 1 
  WHERE sku_id = #{skuId} 
    AND version = #{oldVersion} 
    AND stock >= #{num};


优点:无锁竞争,适合中等并发场景。
缺点:高并发下大量请求失败需重试。

  • 方案二:悲观锁(SELECT … FOR UPDATE)
  BEGIN;
  SELECT stock FROM sku_stock WHERE sku_id = #{skuId} FOR UPDATE;
  -- 业务逻辑校验
  UPDATE sku_stock SET stock = stock - #{num} WHERE sku_id = #{skuId};
  COMMIT;


优点:强一致性保证。
缺点:并发性能差,可能引发死锁。

  • 方案三:直接库存约束
  UPDATE sku_stock 
  SET stock = stock - #{num} 
  WHERE sku_id = #{skuId} 
    AND stock >= #{num};  -- 核心约束条件


优点:简单高效,依赖数据库原子性。
缺点:需处理更新结果为0的失败情况。

2. 缓存层:高性能扣减

  • Redis+Lua原子操作
  -- KEYS[1]:库存key, ARGV[1]:扣减数量
  local stock = tonumber(redis.call('GET', KEYS[1]))
  if stock >= tonumber(ARGV[1]) then
      redis.call('DECRBY', KEYS[1], ARGV[1])
      return 1  -- 成功
  else
      return 0  -- 失败
  end


优点:支持万级TPS,避免数据库压力。
补充:需异步同步Redis与数据库数据。

3. 业务层:预占库存机制

  • 流程
  +---------------------+
  |     用户下单          |
  +----------+----------+
             | 预占库存
  +----------v----------+
  | 生成预占订单(待支付)   |
  +----------+----------+
             | 支付超时/失败
  +----------v----------+       +------------------+
  | 定时任务释放预占库存  +-------> 恢复Redis+DB库存 |
  +---------------------+       +------------------+
  • 关键点
  • 预占库存有效期(如30分钟)。
  • 支付成功后执行实际库存扣减。
  • 使用延迟队列(RocketMQ延迟消息)触发释放。

4. 分布式环境下的一致性保障

  • 最终一致性方案
  1. 扣减Redis库存成功
  2. 发送MQ消息(保证本地事务)
  3. 消费者更新数据库库存
  4. 定时对账修复差异
  • 强一致性方案(TCC模式)
  Try阶段:
    - 冻结库存(stock_frozen字段+1)
  Confirm阶段:
    - 真实扣减库存(stock -= num, stock_frozen -= num)
  Cancel阶段:
    - 释放冻结库存(stock_frozen -= num)

5. 少卖问题的专项处理

  • 库存释放策略
  • 未支付订单:定时任务扫描超时订单,调用库存回补接口。
  • 订单取消:用户主动取消时立即触发库存恢复。
  • 补偿机制(幂等库存回补接口)
  @Transactional
  public void restoreStock(Long skuId, Integer num) {
      skuStockDao.updateStock(skuId, num); // 累加库存
      orderDao.markStockRestored(orderId); // 标记避免重复回补
  }

三、高并发优化策略

  1. 热点库存分片
   // 对skuId取模分片到不同Redis节点
   int shard = skuId % 16;
   String redisKey = "stock:shard_" + shard + ":" + skuId;
  1. 本地缓存+批量合并
   服务内存维护ConcurrentHashMap:
   - Key: skuId
   - Value: 待扣减数量(累计合并请求)
   定时每100ms批量提交到Redis
  1. 令牌桶限流
   // 每个SKU分配独立令牌桶
   RateLimiter rateLimiter = RateLimiter.create(1000); // 每秒1000次
   if (rateLimiter.tryAcquire()) {
       // 允许执行库存操作
   }

四、容灾与监控

  1. 库存对账系统
   每日凌晨对比:
   Redis库存总量 + 预占库存 = 数据库库存总量
   若不相等,触发告警并自动修复
  1. 监控指标
  • 实时看板:库存变更QPS、预占库存释放率、库存对账差异数。
  • 报警规则:库存扣减失败率 > 1%、库存回补延迟 > 5分钟。
  1. 熔断降级
   当数据库响应时间 > 500ms时:
   1. 切换库存计算到Redis-only模式
   2. 记录日志后异步补偿

五、方案选型建议

场景推荐方案优点
普通电商(千级TPS)数据库乐观锁 + 预占机制实现简单,数据强一致
秒杀系统(万级TPS)Redis分片 + 异步对账高性能,可扩展
分布式复杂业务TCC模式 + 本地缓存合并高一致,支持柔性事务

核心总结

  • 超卖防护:通过数据库/缓存原子操作、预占库存机制确保扣减不超量。
  • 少卖解决:完善库存释放策略(定时任务、异步补偿)和幂等回补接口。
  • 高性能:缓存分片、批量合并请求、令牌桶限流应对高并发。
  • 高可靠:库存对账系统兜底数据一致性,熔断降级保障系统可用性。

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注