从原理到实战的5大核心策略
目录导读
- 超卖现象的本质与危害
- 防超卖的三大基础理论
- 分布式锁方案深度解析
- 数据库乐观锁与库存拆分
- 消息队列削峰填谷实战
- 最终一致性与兜底策略
- 常见问题Q&A
超卖现象的本质与危害
核心问题:当系统并发请求超过库存实际剩余数量时,多个线程同时扣减库存,导致最终卖出商品数量大于库存总量。
典型案例:某电商平台10万用户抢购100件商品,若未做限制,可能出现200人“成功下单”的惨剧。
直接后果:商誉损失+法律风险+赔付用户(如京东、天猫的“价保赔款”条款)。

防超卖底层逻辑:凡是“读-判断-写”三步操作非原子性,均可能产生超卖。
// 错误代码示例
if(库存 > 0) { // 读取
扣减库存(); // 写入
}
在并发场景下,两个线程同时通过if判断,均扣减成功,即超卖。
防超卖的三大基础理论
| 理论 | 核心要求 | 典型技术 |
|---|---|---|
| 原子性 | 扣库存操作不可分割 | Redis Lua脚本、数据库行锁 |
| 一致性 | 库存扣减与订单状态强一致 | 分布式事务(TCC/Saga) |
| 隔离性 | 并发互不干扰 | 乐观锁版本号、悲观锁SQL |
搜索引擎优化提示:Google明确将“原子性操作”列为高并发库存方案的核心关键词,百度搜索中,“Redis Lua脚本防超卖”的搜索指数年增长42%。
分布式锁方案深度解析
1 Redis Lua脚本(行业最高频方案)
-- 伪代码:原子性扣库存
local stock = redis.call('get', KEYS[1])
if stock > 0 then
redis.call('decrby', KEYS[1], ARGV[1])
return 1
else
return 0
end
优势:单机QPS可达10万+,且无需网络往返。
风险:Redis单点故障时库存数据丢失,需配合MySQL持久化。
2 分布式锁 + 本地缓存(大厂常用)
- 第一级:本地内存判断库存(如Guava Cache),过滤90%无效请求。
- 第二级:Redis Lua脚本真正扣库存,确保原子性。
- 第三级:MySQL作为最终数据源(订单落地后回写库存表)。
案例:小米商城秒杀设计中,本地缓存拦截9万次无效请求,Redis处理剩余1万次。
数据库乐观锁与库存拆分
1 乐观锁版本号
UPDATE stock SET count = count - 1, version = version + 1 WHERE product_id = 123 AND version = :current_version AND count > 0;
要点:
- 版本号更新必须包含
count > 0条件 - 失败重试最多3次,否则返回“库存不足”
性能瓶颈:单行库存行锁冲突,QPS上限约3000(MySQL 8.0 InnoDB)。
2 库存拆分(分库分表)
将100件库存拆分为10个分片,每个分片10件。
# 配置示例 shards: - id: 1-10 - id: 11-20 # ... 共10个分片
扩容技巧:按用户ID哈希分片,同一用户请求始终路由到固定分片。
消息队列削峰填谷实战
流程:
- 前端限流后,请求先进入MQ队列(如RabbitMQ)。
- 消费者批量拉取消息(如100条/批次),使用Redis原子脚本批量扣库存。
- 扣减成功的消息继续下单,失败的直接返回“已售罄”。
关键参数:
- 队列长度:建议不超过库存总量的10倍(避免无效堆积)
- 消费速率:通过
ACK确认实现“至少一次消费”
避坑指南:MQ可能出现消息重复(如消费者宕机重启),需幂等设计——为每个请求生成唯一ID,扣库存前先查去重表。
最终一致性与兜底策略
1 异步对账
每5分钟执行一次SQL:
SELECT product_id, SUM(sold_count) - total_stock AS diff FROM orders WHERE status = 'paid' GROUP BY product_id HAVING diff > 0;
发现超卖后立即熔断该商品,并触发退款流程。
2 TCC补偿模式
- Try:冻结库存资源(如Redis标记“预占用”)
- Confirm:订单状态变为已支付,真正扣减
- Cancel:库存冻结30分钟未支付,自动解冻并释放
适用场景:抢购+支付有时间窗(如15分钟未支付自动取消)。
常见问题Q&A
Q1:为什么不用纯MySQL行锁?
A:MySQL单机并发上限约3000 QPS,秒杀场景常达10万+ QPS,行锁会导致数据库连接瞬间耗尽,Redis作为前置缓存是行业共识。
Q2:Redis挂了怎么办?
A:方案1:Redis集群+持久化RDB/AOF;方案2:降级为MySQL直接扣库存(牺牲性能,但保证数据准确),建议采用方案1。
Q3:如何避免“黄牛”刷单?
A:前端添加滑块验证码+IP限流(每IP每秒最多1次)+设备指纹识别(如中国支付清算协会的设备指纹服务)。
Q4:商品库存是离散的(不同规格不同库存量)怎么处理?
A:SKU级别的库存隔离,每个SKU拥有独立Redis Key,如stock:shirt:red:L和stock:shirt:blue:S分别存储。
Q5:流量突增超过Redis集群极限怎么办?
A:策略1:提前计算扩容容量,加入网关层单机限流(如Sentinel);策略2:客户端预校验库存,使用客户端本地缓存(如Cookie中的库存快照)。
秒杀架构防超卖的核心不是选择单一技术,而是构建分层拦截、原子操作、最终校验的防御体系,从本地缓存、Redis原子脚本到数据库乐观锁,每一层都过滤掉一部分风险,既保性能又保准确,建议中小型项目优先采用“Redis Lua脚本+MQ削峰”组合,大型系统再引入分片+TCC模式。