lpush+brpop不适用于秒杀因无法限等待时长、不保证fifo且库存校验滞后;应改用lua脚本前置校验库存再入队,再以原子脚本出队并扣减库存。

为什么不用 LPUSH + BRPOP 直接处理秒杀请求
直接用 LPUSH 入队、BRPOP 出队看似自然,但秒杀场景下会暴露两个硬伤:一是 BRPOP 无法限制最大等待时长(超时后需手动清理残留连接),二是多个消费者并发 BRPOP 时,Redis 不保证“先到先服务”——因为网络延迟和命令到达顺序不可控,可能让后发请求反而先被取走。
更关键的是,秒杀必须强校验库存余量,而 BRPOP 取出请求后才查库存,已造成无效排队穿透。真正要削的不是“请求流量”,而是“无效库存竞争”。
用 LRANGE + LTRIM 分批预检,把库存判断前置到入队环节
核心思路是:不让人无条件进队,而是让客户端在 LPUSH 前,先通过原子操作确认“此刻是否还有足够库存供我排队”。这需要借助 Redis 的 EVAL 执行 Lua 脚本实现多步原子性。
- 脚本先用
EXISTS检查库存 key 是否存在(防止首次秒杀未初始化) - 再用
GET读当前库存值,转为数字后判断是否 ≥ 待抢数量(比如每人限1件,就看是否 ≥1) - 若通过,才执行
LPUSH入队,并返回1;否则返回0 - 库存 key 建议设为
seckill:stock:goods_123,队列 key 为seckill:queue:goods_123
示例 Lua 脚本(传参:KEYS[1] 库存key,KEYS[2] 队列key,ARGV[1] 用户ID,ARGV[2] 请求数量):
if redis.call('EXISTS', KEYS[1]) == 0 then
return 0
end
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock
<h3>消费者如何安全地从 List 中取请求并扣减库存</h3>
<p>不能简单 <code>RPOP</code> 后再扣库存,否则仍存在超卖风险。必须把“取请求”和“扣库存”做成原子操作。推荐用 <code>WATCH</code> + <code>MULTI</code>,或更稳妥的 Lua 脚本:</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理"><img
src="https://img.php.cn/upload/skill/000/000/081/178960683454849.jpg" alt="Redis Skill - 高性能缓存管理" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理" class="overflowclass">Redis Skill - 高性能缓存管理</a>
<p class="overflowclass">Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。</p>
</div>
<a rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 脚本先
RPOP队列头,拿到用户ID - 再对库存 key 执行
DECRBY(减去对应数量),并用GET拿新值 - 如果新值 INCRBY 回滚,并返回失败;否则提交成功
注意:Lua 脚本中不能调用阻塞命令(如 BLPOP),所以消费者需轮询 LRANGE queue_key 0 0 判断是否有待处理项,有则触发上述脚本,无则休眠(避免空转)。
为什么 List 长度暴增时 LRANGE 会拖慢入队判断
当排队人数达数万,每次入队前都 LRANGE seckill:queue:goods_123 0 0 查首元素没问题,但若脚本里写了 LRANGE queue_key 0 -1 想统计长度,就会 O(N) 扫全量,直接打满 Redis CPU。
正确做法是:用 LLEN 获取长度(O(1)),再结合业务规则做阈值拦截(例如排队超5000人就返回“系统繁忙”)。同时,定期用后台任务执行 LTRIM queue_key 0 4999 截断过长队列(仅保留前5000个有效请求),避免历史积压干扰实时判断。
真正难的不是写对几条命令,而是想清楚哪一步该阻塞、哪一步该拒绝、哪一步必须原子——List 本身没状态,所有业务逻辑得靠你用命令组合和时机控制来补全。










