【生产案例】
问题:高并发下如何防止商品超卖? https://www.cnblogs.com/12lisu/p/18908447
方案1:数据库乐观锁
优点:实现简单、无需额外中间件
缺点: 可能出现大量更新失败、高并发时DB压力大
方案2:Redis原子操作
Redis原子操作的核心原理是使用:Redis + Lua脚本。
扣减成功后写入消息队列;扣减失败返回秒杀失败
性能对比:
- 单节点QPS:数据库方案500 vs Redis方案8万
- 响应时间:<1ms vs 50ms+
// Lua脚本保证原子性 String lua = "if redis.call('get', KEYS >= ARGV[1] then " + "return redis.call('decrby', KEYS[1], ARGV " + "else return -1 end"; public boolean preDeduct(String itemId, int count) { RedisScript<Long> script = new DefaultRedisScript<>(lua, Long.class); Long result = redisTemplate.execute(script, Collections.singletonList(itemId), count); return result != null && result >= 0; }
方案3: 分布式锁
最常用的分布式锁的方案是Redisson
注意事项
- 1.锁粒度要细化到商品级别
- 2.必须设置等待时间和自动释放
- 3.配合异步队列使用效果更佳
方案4:消息队列削峰
可以使用 RocketMQ的事务消息

技术指标:
- 削峰能力:10万QPS → 2万TPS
- 订单处理延迟:<1秒(正常时段)
// RocketMQ事务消息示例 TransactionMQProducer producer = new TransactionMQProducer("stock_group"); producer.setExecutor(new TransactionListener() { @Override public LocalTransactionState executeLocalTransaction(Message msg) { // 扣减数据库库存 return LocalTransactionState.COMMIT_MESSAGE; } });
方案5:预扣库存
预扣库存是防止商品超卖的终极方案。用户提交订单时,做的是reids中库存预扣,只有当实际支付完成后,才会做数据库层的库存扣减。
// Guava RateLimiter限流 RateLimiter limiter = RateLimiter.create(1000); // 每秒1000个令牌 public boolean preDeduct(Long itemId) { if (!limiter.tryAcquire()) return false; // 写入预扣库存表 preStockDao.insert(itemId, userId); return true; }

性能数据:
- 百万级并发支撑能力
- 库存准确率99.999%
- 订单处理耗时200ms内
其实在很多大厂中,一般会将防止商品超卖的多种方案组合使用。
架构图如下:

通过组合使用:
- Redis做第一道防线(承受80%流量)
- 分布式锁控制核心业务逻辑
- 预扣库存+消息队列保证最终一致性
实战经验:某电商在2023年双11中:
- Redis集群承载98%请求
- 分布式锁拦截异常流量
- 预扣库存保证最终准确性
系统平稳支撑了每秒12万次秒杀请求,0超卖事故发生!
浙公网安备 33010602011771号