跳到主要内容

库存系统设计

问题

如何设计一个防超卖的库存系统?

答案

库存扣减三层架构

Redis 预扣库存(Lua 原子操作)

Redis Lua 保证原子性
String lua = """
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock == nil or stock < tonumber(ARGV[1]) then
return -1
end
redis.call('DECRBY', KEYS[1], ARGV[1])
return stock - tonumber(ARGV[1])
""";

public boolean deductStock(String skuId, int quantity) {
Long remaining = redisTemplate.execute(
new DefaultRedisScript<>(lua, Long.class),
List.of("stock:" + skuId),
String.valueOf(quantity)
);
return remaining != null && remaining >= 0;
}

数据库扣减(乐观锁兜底)

乐观锁防超卖
UPDATE inventory 
SET stock = stock - #{quantity}, version = version + 1
WHERE sku_id = #{skuId} AND stock >= #{quantity} AND version = #{version}

库存回补

订单取消/支付超时回补库存
public void rollbackStock(String skuId, int quantity) {
// 1. 回补 Redis
redisTemplate.opsForValue().increment("stock:" + skuId, quantity);
// 2. 回补数据库
inventoryMapper.increaseStock(skuId, quantity);
}

库存预热

大促前将库存加载到 Redis
public void warmUpStock(List<String> skuIds) {
for (String skuId : skuIds) {
int stock = inventoryMapper.getStock(skuId);
redisTemplate.opsForValue().set("stock:" + skuId, String.valueOf(stock));
}
}

常见面试问题

Q1: Redis 和数据库库存不一致怎么办?

答案

  • 定时对账:比较 Redis 和 DB 的库存差异
  • Redis 作为预扣快速判断,DB 是最终真实数据
  • MQ 消费失败时有重试机制 + 对账补偿

Q2: 分布式场景下如何防超卖?

答案

三道防线:

  1. Redis Lua 原子扣减(最快,过滤 99% 请求)
  2. MQ 串行化消费(避免并发)
  3. DB 乐观锁/悲观锁(最终兜底)

Q3: 预扣后未支付怎么办?

答案

设置订单超时(如 30 分钟),超时未支付通过延迟消息触发库存回补。详见 订单系统设计

相关链接