必须用lua脚本在redis端原子执行库存扣减:取值→判断→扣减→返回结果,java仅传参、执行、判返回值;脚本只做三件事——读当前值、比较是否足够、够则扣减否则拒绝,禁止日志、查用户、发消息等非原子操作。

直接在 Redis 服务端用 Lua 脚本完成“读取→判断→扣减→返回结果”整套逻辑,Java 不参与中间状态判断,才能真正避免高并发下的超卖或负库存。关键不是写得多 fancy,而是把原子边界划清楚:所有 Redis 操作必须在脚本内闭环,外部 Java 只负责传参、执行、判结果。
脚本要短而聚焦,只做三件事
一个库存扣减脚本不需要日志、不查用户、不发消息——那些是 Java 层的事。Lua 脚本只管:取当前值 → 比较是否够扣 → 够就扣、不够就拒绝。例如:
-
KEYS[1] 是库存 key(如
"item:1001:stock") -
ARGV[1] 是要扣的数量(字符串,如
"1") - 用
redis.call("GET", KEYS[1])读值,tonumber()转数字 - 用
redis.call("DECRBY", KEYS[1], ARGV[1])扣减,不手动算新值(避免 race condition) - 返回明确值:
-1表示失败,否则返回扣减后的剩余值
EVAL 调用要规范传参,别拼字符串
Java 端调用时,不要把脚本硬编码在字符串里反复发送。用 Lettuce 推荐方式:
- 提前用
SCRIPT LOAD加载脚本,拿到 SHA1 值(比如"a1b2c3...") - 运行时用
evalSha(),传入 SHA1 + KEYS 列表 + ARGV 列表 - KEYS 必须是真实 Redis key 名,不能写成
"item:" + id + ":stock"这种拼接逻辑 —— 拼接放在 Java,不是 Lua - ARGV 全部转字符串传,Lua 里再
tonumber(),避免类型错乱
结果处理要靠返回值,不靠异常
脚本里别用 error() 报错,Java 层也别 catch RedisException 来判断业务失败。统一约定返回值语义:
- 返回
-1→ 库存不足,Java 直接返回“库存不足”或抛业务异常 - 返回 ≥ 0 的整数 → 扣减成功,这个数就是新库存,可打日志或用于下游校验
- 返回
nil或其他非数字 → 说明脚本语法错或 key 类型不对,属于部署/配置问题,需告警而非重试
别让 Lua 做它不该做的事
脚本里禁止出现:
- HTTP 请求、文件读写、时间戳生成(
os.time())、随机数(math.random())——这些不原子,且可能阻塞 Redis 单线程 - 循环遍历大量 key(如
redis.call("KEYS", "*"))——会拖慢整个 Redis - 硬编码 key 名(如
redis.call("GET", "item:1001:stock"))——违反 KEYS/ARGV 传参规范,影响集群路由
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











