用Redis+Lua脚本实现原子性库存扣减是最常用有效方案,核心是将查库存、判断是否足够、扣减三步封装为单个Lua脚本在Redis服务端原子执行,避免超卖;脚本返回1表示成功,0表示库存不足,-1表示商品不存在。

直接用 Redis + Lua 脚本做原子性库存扣减,是最常用且有效的方案。核心在于把“查库存、判是否足够、扣减”这三步压进一个 Lua 脚本里执行,避免网络往返和并发竞争。
为什么必须用 Lua 脚本?
Redis 单线程执行 Lua 脚本,整个脚本是原子的。如果用客户端先 get 再 decr,中间可能被其他请求插队,导致超卖。Lua 把逻辑锁死在服务端,不依赖客户端状态,也不受网络延迟影响。
- Redis 命令本身(如 INCR、DECR)是原子的,但多个命令组合不是
- Lua 脚本在 Redis 服务端一次性运行完,中间不会被打断
- 脚本返回结果可明确区分:1=扣成功,0=库存不足,-1=商品不存在
库存扣减 Lua 脚本怎么写?
一个典型脚本示例:
-- KEYS[1] 是商品ID,ARGV[1] 是要扣减的数量local stockKey = 'seckill:stock:' .. KEYS[1]
local stock = redis.call('GET', stockKey)
if not stock then
return -1
end
if tonumber(stock) return 0
end
redis.call('DECRBY', stockKey, ARGV[1])
return 1
注意:实际部署前需用 SCRIPT LOAD 预加载脚本获取 SHA1,后续用 EVALSHA 调用,减少网络传输开销。
立即学习“Java免费学习笔记(深入)”;
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
Java 端怎么安全调用?
推荐用 Lettuce 或 Jedis 封装调用,重点处理三件事:
- 预加载脚本并缓存 SHA1 值(避免每次 SEND SCRIPT)
- 设置合理的超时时间(如 100ms),防止 Redis 阻塞拖垮应用
- 对返回值做明确分支处理:成功则走下单流程;失败则快速拒绝,不重试
别在 Java 层做“扣减失败就重试”的逻辑——这会放大热点,反而加剧竞争。
配套优化不能少
Lua 解决了原子性,但高并发下还要防穿透、控流量、保兜底:
- 前置加本地缓存(如 Caffeine)过滤明显无效请求(比如秒杀已结束)
- 用 Redis 的 SETNX 或分布式锁控制“库存初始化”或“库存同步回写”这类低频但关键操作
- 数据库最终一致性靠异步任务补偿:扣减成功后发 MQ,由消费者落库并校验,发现不一致可告警+人工介入
不复杂但容易忽略。


















