导读:本期聚焦于大卫创作的《如何用Redis实现限时秒杀中的库存预减而不超卖》,敬请观看详情。限时秒杀系统最怕的就是库存被冲垮或者超卖。直接查数据库减库存,高并发下连接池瞬间打满。把库存预热进Redis,在请求进来时先对Redis做原子扣减,才是扛住脉冲流量的关键。但光用普通的get再set肯定不行,并发下会出现写覆盖。可以利用Redis的单线程特性配合Lua脚本,把判断库存和扣减做成原子操作,也可以在集群模式下用Hash Tag把同一商品锁在单个槽位。预减后异步落库能削峰,不过要留意消息丢失带来的数据不一致,可用对账任务兜底。

限时秒杀场景里,瞬时流量可能达到平时的几十倍甚至上百倍。如果每次抢购请求都直接去数据库里执行库存查询和扣减,数据库很容易因为连接数耗尽或者行锁竞争而雪崩。因此,把库存量提前加载到Redis中,让用户在抢购时先对Redis里的库存做预减,是业界非常成熟的削峰方案。预减的意思并不是最终成交,而是先在内存里占住名额,后续再异步同步到数据库完成真实下单。

如何用Redis实现限时秒杀中的库存预减而不超卖

为什么数据库直接扣减撑不住秒杀流量

在普通的电商下单链路中,库存一般存放在MySQL这样的关系型数据库里,通过一行记录表示某个商品的剩余数量。当用户点击购买,系统先select出库存,判断大于零后再update减一。这个逻辑在少量并发时没有问题,但秒杀开启的一秒内可能有十万级请求同时打到同一行数据上。

数据库为了保证事务一致,会对这行库存记录加行级锁。大量请求排队等锁,线程池和连接池很快被占满,导致整个数据库实例响应变慢,甚至影响其他正常业务。更严重的是,如果应用层没有做好并发控制,先读后写的逻辑还会产生超卖:两个请求都读到库存为1,都判断通过,然后都减成0,结果卖出了两件商品。所以必须把高并发的读写转移到内存层,用Redis承接第一波冲击。

Redis基于内存且本身是单线程处理命令,不存在多个命令交错执行的情况,非常适合做这种计数器的原子操作。把商品库存以字符串或者散列形式放在Redis里,抢购时优先操作它,只有预减成功的人才允许继续走数据库下单,这样落到数据库的压力就只剩下少量真实订单了。

用Lua脚本保证库存预减的原子性

很多人第一反应是用DECR命令直接对库存key减一,然后判断返回值是否小于零再回滚。但这种方式在分布式环境下有漏洞:DECR之后如果程序崩溃没来得及处理负数,或者多步判断之间存在网络往返,就可能被其他线程钻空子。正确做法是将判断和扣减打包成一个原子操作,而Redis执行Lua脚本时整个脚本会被当成一条命令,中间不会被其他命令插入。

下面这段Lua脚本演示了安全的库存预减逻辑。它先获取当前库存,如果不存在或者已经不足,就返回0表示失败;否则执行减一并返回剩余库存。因为脚本在Redis服务端一次性执行完,所以不可能出现超卖。

-- 秒杀库存预减Lua脚本
-- KEYS[1]为库存key,ARGV[1]为本次扣减数量
local stock = tonumber(redis.call('GET', KEYS[1]))
if not stock then
    return 0
end
local decr = tonumber(ARGV[1])
if stock < decr then
    return 0
end
local remain = redis.call('DECRBY', KEYS[1], decr)
return remain

在Java代码里可以通过RedisTemplate调用这个脚本。注意要先把脚本注册成DefaultRedisScript,并且用Long接收返回结果。如果返回大于等于零,说明预减成功,此时可以把用户ID和商品ID丢进消息队列,由消费者异步创建订单并扣数据库库存;如果返回0,直接告诉前端活动已结束。

这种方案的优点是逻辑简单、性能极高,单节点Redis轻松扛住几万QPS。缺点在于脚本里只能操作同一个key,如果业务需要同时扣多个库存(比如套餐含多件商品),就要用Hash Tag把相关key映射到同一槽位,或者拆成多个脚本分步执行并自己处理部分失败补偿。

预减成功后如何与数据库保持最终一致

Redis预减只是挡在前面的一道闸门,真正的库存资产仍以数据库为准。常见做法是预减成功后将订单消息发送到RabbitMQ或Kafka,后台消费者拿到消息后开启数据库事务,再次确认库存并生成订单。由于走到这一步的请求已经被Redis过滤过,量不大,数据库完全可以承受。

但这里有个隐患:如果消息队列丢消息,或者消费者处理到一半宕机,就会出现Redis减了但数据库没减,导致两边数据不一致。为此需要引入对账机制,比如每隔五分钟跑一个任务,用数据库的已售数量加上Redis剩余数量反推初始库存,发现偏差就报警并修正。另一种更稳妥的思路是使用Redis的延时双删或者binlog订阅(如Canal)来同步,不过复杂度会明显上升。

此外还要考虑库存预热和过期问题。活动开始前要把数据库库存批量写入Redis,并设一个比活动稍长的过期时间,防止Redis宕机后数据丢失造成永久超卖。如果Redis真的挂了,可以降级为直接数据库扣减并配合限流,虽然体验下降但不至于资损。整体来看,Redis预减配合Lua原子脚本和异步落库,是限时秒杀里最实用且容易落地的库存控制方案。

Redis库存预减秒杀修改时间:2026-08-18 12:06:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。