原子库存扣减必须由Redis Lua脚本完成,Java层if仅处理返回结果;脚本需原子执行检查、扣减与补偿入队,Java通过eval调用并依据返回值分支处理,补偿应异步解耦。

在 Java 中通过 if 判定实现基于 Redis 脚本的原子库存扣减与补偿,核心不是靠 Java 层的 if 逻辑保证原子性,而是将“判断 + 扣减 + 补偿触发”全部封装进 Lua 脚本,在 Redis 单线程中一次性执行。Java 层只负责调用、解析返回值并做后续业务处理(如发消息、记录日志)。下面分关键点说明:
为什么不能依赖 Java 层的 if
Redis 是单线程执行命令,但 Java 客户端(如 Jedis/Lettuce)与 Redis 的通信存在网络延迟和多线程并发风险。若先 GET 库存 → Java 判断是否充足 → 再 DECR,中间可能被其他客户端修改,导致超卖。真正的原子性必须由 Lua 脚本在服务端完成。
Lua 脚本需包含的原子逻辑
一个典型库存扣减脚本应同时完成:检查当前库存、扣减、返回结果,并在失败时可选触发补偿信号(如返回特定错误码或写入补偿队列 key)。示例脚本如下:
-- KEYS[1]: 库存 key(如 "stock:1001")
-- ARGV[1]: 扣减数量(如 "1")
-- ARGV[2]: 补偿标记开关(如 "true" 表示启用补偿)
local stock = tonumber(redis.call('GET', KEYS[1]))
if not stock or stock < tonumber(ARGV[1]) then
-- 库存不足:返回 -1 表示失败;也可用 redis.call('LPUSH', 'compensate:queue', ...) 主动入补偿队列
if ARGV[2] == 'true' then
redis.call('LPUSH', 'compensate:stock:' .. KEYS[1], ARGV[1])
end
return -1
end
redis.call('DECRBY', KEYS[1], ARGV[1])
return stock - tonumber(ARGV[1]) -- 返回扣减后的剩余库存
Java 调用与结果判定(配合 if)
Java 层使用 eval 执行脚本,根据返回值做分支处理。注意:这里的 if 不破坏原子性,只是响应原子操作的结果。
立即学习“Java免费学习笔记(深入)”;
- 返回
-1→ 库存不足,可触发降级(如提示用户、异步重试、走本地缓存兜底) - 返回 ≥ 0 数字 → 扣减成功,继续订单创建等流程
- 捕获
JedisConnectionException等网络异常 → 记录 error 日志,进入补偿任务扫描该 key 对应的compensate:stock:*队列
补偿流程的轻量设计建议
不推荐在 Lua 中直接调用 HTTP 或复杂 DB 操作。补偿应解耦为异步任务:
- 脚本仅写入补偿队列(如
LPUSH compensate:stock:1001 1) - 独立的定时任务或监听器(如 Spring Scheduler +
BRPOP)定期拉取补偿队列 - 补偿处理器检查当前真实库存(DB 或最终一致缓存),决定是回滚预占、通知运营还是告警


















