文章目录
- 不同Lua返回值对应泛型对照表
- seckill.lua
- service实现类
- 代码逐段拆解
- 参数签名
- 返回值 Long result
- 🚨 你的脚本集群坑点(重点)
- 改写成支持集群的正确版本(把key通过Java传入KEYS数组)
不同Lua返回值对应泛型对照表
| Lua返回示例 | Java泛型选什么 |
|---|---|
return 0/1/2数字 | Long |
return "ok"字符串 | String |
return {1,2,3}数组 | List<Object> |
return nil | Object |
seckill.lua
-- 0 成功-- 1 库存不足-- 2 用户重复下单localvouvherId=ARGV[1]localuserId=ARGV[2]localstockKey='hmdp:seckill:stock:'..vouvherIdlocalorderKey='hmdp:seckill:order:'..userId-- 判断库存iftonumber(redis.call('get',stockKey))<=0thenreturn1end-- 判断用户是否已经下单ifredis.call('sismember',orderKey,userId)==1thenreturn2end-- 扣库存redis.call('incrby',stockKey,-1)redis.call('sadd',orderKey,userId)return0service实现类
- Redis 协议里,
lua脚本返回的整数是64 位有符号整数,对应 Java 的long;即使数值很小 0、1、2,底层依然是 64bit,所以 Spring Data Redis 脚本 API 统一使用 Long 接收数字结果。
@ServicepublicclassVoucherOrderServiceImplextendsServiceImpl<VoucherOrderMapper,VoucherOrder>implementsIVoucherOrderService{privatestaticfinalDefaultRedisScript<Long>SECKILL_SCRIPT;static{SECKILL_SCRIPT=newDefaultRedisScript<>();SECKILL_SCRIPT.setLocation(newClassPathResource("seckillJvm.lua"));SECKILL_SCRIPT.setResultType(Long.class);}@OverridepublicResultseckillVoucherAsync(LongvoucherId){LonguserId=UserHolder.getUser().getId();"执行 lua 脚本"Longresult=stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString());intr=result.intValue();if(r!=0){// 1 库存不足;2 用户重复下单returnResult.fail(r==1?"库存不足":"不能重复下单");}}}代码逐段拆解
Longresult=stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString());参数签名
<T>Texecute(RedisScript<T>script,List<String>keys,Object...args)第一个参数:SECKILL_SCRIPT
就是我们定义的DefaultRedisScript<Long>,里面加载了你写的lua脚本文件,指定返回类型是Long。第二个参数:
Collections.emptyList()→ KEYS[]数组(即:Redis的key名,就是你要操作的 Redis 的键)Redis 官方要求:所有要读写的 Redis key,必须通过 KEYS 数组传入,不要在 Lua 脚本内部拼接 key 字符串。因为,Redis集群模式下,Redis 会根据 KEY 做 hash 槽路由;如果你脚本里硬编码拼接 key,
集群环境会报错 CROSSSLOT Keys in request don't hash to the same slot。
📌 对应lua里KEYS[1]、KEYS[2]
你的 lua 脚本完全没有使用 KEYS 数组,key都是在lua内部字符串拼接:localstockKey='hmdp:seckill:stock:'..vouvherIdlocalorderKey='hmdp:seckill:order:'..userId所以这里 keys 传空集合
emptyList()。⚠️ Redis官方最佳实践:尽量把key放到KEYS[]参数传入,不要lua内部拼接字符串key,是为了兼容Redis集群槽位。
集群模式下,lua脚本操作的key必须全部来自KEYS数组,自己拼接会报错CROSSSLOT Keys in request don't hash to the same slot。后面可变参数:
voucherId.toString(), userId.toString()→ ARGV[]
对应lua脚本:localvouvherId=ARGV[1]localuserId=ARGV[2]voucherId.toString()→ARGV[1]userId.toString()→ARGV[2]
ARGV 放普通参数 (不是 redis key),没有集群槽位限制,可以随便传。
返回值 Long result
lua脚本return 0 / 1 / 2,底层协议返回64位整数,所以Java接收为Long。
"使用的时候 转换为 int"if(result==0){// 成功}elseif(result==1){// 库存不足}elseif(result==2){// 重复下单}注意:
result 不会是 null,脚本一定有 return。
🚨 你的脚本集群坑点(重点)
现在写法:key在lua内部拼接
localstockKey='hmdp:seckill:stock:'..vouvherIdlocalorderKey='hmdp:seckill:order:'..userId- 单机Redis:正常跑没问题
- Redis集群环境:直接报错跨槽
改写成支持集群的正确版本(把key通过Java传入KEYS数组)
Java调用:
StringstockKey="hmdp:seckill:stock:"+voucherId;StringorderKey="hmdp:seckill:order:"+userId;Longresult=stringRedisTemplate.execute(SECKILL_SCRIPT,List.of(stockKey,orderKey),// KEYS[1] stockKey , KEYS[2] orderKeyvoucherId.toString(),userId.toString());lua脚本修改:
-- KEYS[1] stockKey hmdp:seckill:stock:xxx-- KEYS[2] orderKey hmdp:seckill:order:xxx-- ARGV[1] voucherId-- ARGV[2] userIdlocalstockKey=KEYS[1]localorderKey=KEYS[2]iftonumber(redis.call('get',stockKey))<=0thenreturn1endifredis.call('sismember',orderKey,ARGV[2])==1thenreturn2endredis.call('incrby',stockKey,-1)redis.call('sadd',orderKey,ARGV[2])return0集群要求:脚本操作的所有 redis key,
必须全部来自KEYS[]参数,不能在lua字符串拼接生成key。