秒杀
秒杀是库存扣减的一个极端变体,商品有限,但同一瞬间涌进来几十万请求。它的核心矛盾不是「怎么扣库存」,而是「怎么让几十万请求在到达数据库之前被层层拦截」。库存扣减只解决最后一步,前面的削峰才是秒杀的真正难点。
1. 秒杀难在哪
秒杀和平常下单的区别,不在业务逻辑,在流量形态。平常的请求是均匀的,秒杀的请求是在几秒内瞬间涌入,峰值可能是平常的几百上千倍。
数据库扛不住这个峰值。即使库存扣减逻辑写得再对,几十万请求同时打数据库,连接池先被占满,数据库直接不可用。所以秒杀要解决的第一件事,不是正确扣库存,而是让数据库只承受它能承受的那一小撮请求。
理解这一点,就能理解秒杀方案为什么是层层拦截的结构。每一层拦掉一批请求,到达数据库的请求量衰减到可控范围。
2. 层层拦截,把洪峰削平
第一层在前端。秒杀开始前按钮置灰,开始后点击一次就禁用,防住无意义的重复点击。
第二层在网关或限流层。对秒杀接口单独限流,超过阈值的请求直接拒绝,返回「活动太火爆」。限流的实现见 限流器。
第三层是 Redis 预扣库存。请求过了限流,在 Redis 里用 Lua 脚本把「判断库存够不够」和「扣减」合成一次原子操作,扣到不足就直接拒绝,根本不进数据库。这层拦住的是绝大部分「库存已空」的请求。
-- 判断和扣减在同一个脚本里原子完成,中间插不进别的请求
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock == nil or stock <= 0 then
return -1 -- 库存不足,直接拒绝
end
redis.call('DECR', KEYS[1])
return 1用 Lua 而不是先 DECRBY 再回滚,因为后者在「扣减」和「回滚」之间留了一个窗口,服务若在这中间崩溃,库存就永久少了一份。Lua 要么判断和扣减一起成功,要么一起不执行,没有中间态。防重复抢购和完整的 Java 版实现见 库存扣减并发控制 的 Redis 预扣一节。
第四层才是数据库。经过前面三层,真正到达数据库的请求,数量已经和库存量一个量级,数据库轻松承接。
四层不是并列的冗余,拦的对象各不相同。前端挡误触,限流挡的是请求量,Redis 挡的是库存量,数据库是兜底。限流把请求压到「后面扛得住」的量级,Redis 再把「库存已空」的那批挡在数据库外,顺序不能互换。反过来把 Redis 预扣放在限流前面,几十万请求先打进 Redis,缓存层先被打穿,削峰就失效了。
3. Redis 预扣之后,异步落库
Redis 里扣减成功,不代表下单完成。下单要写订单表、扣库存表,这些还得落数据库。但落库不要求同步,扣减成功后发一条消息,由消费者异步完成订单创建和库存落库。
// Lua 返回 1 表示预扣成功,发消息交给消费者异步落库
if (result == 1) {
mq.send("flash-sale.order", new OrderMessage(itemId, userId));
}这引入了一个新问题,Redis 扣了库存,但异步落库还没完成,中间崩溃了怎么办。库存已经少了一份,数据库里还没记录。这就是 Redis 预扣的双份数据一致性问题,具体解法见 库存扣减并发控制 里的 Redis 预扣一节。
秒杀场景对这个问题比普通库存更宽容,因为秒杀的商品本来就卖光了,Redis 里预扣的库存多扣少扣几件,影响的只是极少数用户的购买结果,不是资金安全。多扣的库存本来就是会被抢走的那份,对账补不补偿,改变的只是谁抢到、谁没抢到,不产生资损。所以秒杀里 Redis 预扣加异步落库是标准做法,补偿机制做到「最终对账」这一层就够了。
4. 秒杀和普通库存扣减的区别
普通库存扣减,用数据库的悲观锁或乐观锁就够,因为并发没那么极端。秒杀不能用数据库锁,几十万请求在数据库行锁上排队,锁等待时间比业务本身还长。
所以秒杀几乎必然走 Redis 预扣这条路线,用 Redis 的原子命令替代数据库行锁。Redis 6.0 之后虽然引入 IO 多线程,命令执行仍是单线程串行,DECRBY 的原子性正来自这里。这个选择不是「更优」,是「没有别的路」。数据库的锁在秒杀的量级下必崩。
同时秒杀对一致性的要求比普通库存低。普通库存多扣一件是事故,秒杀多扣几件几乎无感,因为它卖的就是「抢不到」这个结果。这个差异决定了秒杀可以更大胆地用异步,用最终一致换吞吐。
5. 结论
秒杀的核心是削峰,不是扣库存。前端、限流、Redis 预扣、数据库四层,每层拦掉一批,让洪峰衰减到数据库能承受的量。库存扣减的正确性靠 Redis 原子操作保证,一致性靠异步落库加对账兜底。把「洪峰」和「库存」这两件事分开想,秒杀方案就清晰了。
6. 相关章节
库存扣减的三种方案对比见 库存扣减并发控制。限流的具体算法见 限流器。异步落库的消息可靠性见 消息系统高可用实战。这套架构的完整实现,Lua 原子扣减、MQ 异步、对账补偿,见 高并发设计 的经典秒杀架构一节。