多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

java 中使用 lua 脚本

java 中使用 lua 脚本 文章目录不同Lua返回值对应泛型对照表seckill.luaservice实现类代码逐段拆解参数签名返回值 Long result 你的脚本集群坑点重点改写成支持集群的正确版本把key通过Java传入KEYS数组不同Lua返回值对应泛型对照表Lua返回示例Java泛型选什么return 0/1/2数字Longreturn ok字符串Stringreturn {1,2,3}数组ListObjectreturn nilObjectseckill.lua-- 0 成功-- 1 库存不足-- 2 用户重复下单localvouvherIdARGV[1]localuserIdARGV[2]localstockKeyhmdp:seckill:stock:..vouvherIdlocalorderKeyhmdp: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 接收数字结果。ServicepublicclassVoucherOrderServiceImplextendsServiceImplVoucherOrderMapper,VoucherOrderimplementsIVoucherOrderService{privatestaticfinalDefaultRedisScriptLongSECKILL_SCRIPT;static{SECKILL_SCRIPTnewDefaultRedisScript();SECKILL_SCRIPT.setLocation(newClassPathResource(seckillJvm.lua));SECKILL_SCRIPT.setResultType(Long.class);}OverridepublicResultseckillVoucherAsync(LongvoucherId){LonguserIdUserHolder.getUser().getId();执行 lua 脚本LongresultstringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString());intrresult.intValue();if(r!0){// 1 库存不足2 用户重复下单returnResult.fail(r1?库存不足:不能重复下单);}}}代码逐段拆解LongresultstringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString());参数签名TTexecute(RedisScriptTscript,ListStringkeys,Object...args)第一个参数SECKILL_SCRIPT就是我们定义的DefaultRedisScriptLong里面加载了你写的lua脚本文件指定返回类型是Long。第二个参数Collections.emptyList()→ KEYS[]数组即Redis的key名就是你要操作的 Redis 的键Redis 官方要求所有要读写的 Redis key必须通过 KEYS 数组传入不要在 Lua 脚本内部拼接 key 字符串。因为Redis集群模式下Redis 会根据 KEY 做 hash 槽路由如果你脚本里硬编码拼接 key集群环境会报错 CROSSSLOT Keys in request dont hash to the same slot。 对应lua里KEYS[1]、KEYS[2]你的 lua 脚本完全没有使用 KEYS 数组key都是在lua内部字符串拼接localstockKeyhmdp:seckill:stock:..vouvherIdlocalorderKeyhmdp:seckill:order:..userId所以这里 keys 传空集合emptyList()。⚠️ Redis官方最佳实践尽量把key放到KEYS[]参数传入不要lua内部拼接字符串key是为了兼容Redis集群槽位。集群模式下lua脚本操作的key必须全部来自KEYS数组自己拼接会报错CROSSSLOT Keys in request dont hash to the same slot。后面可变参数voucherId.toString(), userId.toString()→ ARGV[]对应lua脚本localvouvherIdARGV[1]localuserIdARGV[2]voucherId.toString()→ARGV[1]userId.toString()→ARGV[2]ARGV 放普通参数 (不是 redis key)没有集群槽位限制可以随便传。返回值 Long resultlua脚本return 0 / 1 / 2底层协议返回64位整数所以Java接收为Long。使用的时候 转换为 intif(result0){// 成功}elseif(result1){// 库存不足}elseif(result2){// 重复下单}注意result 不会是 null脚本一定有 return。 你的脚本集群坑点重点现在写法key在lua内部拼接localstockKeyhmdp:seckill:stock:..vouvherIdlocalorderKeyhmdp:seckill:order:..userId单机Redis正常跑没问题Redis集群环境直接报错跨槽改写成支持集群的正确版本把key通过Java传入KEYS数组Java调用StringstockKeyhmdp:seckill:stock:voucherId;StringorderKeyhmdp:seckill:order:userId;LongresultstringRedisTemplate.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] userIdlocalstockKeyKEYS[1]localorderKeyKEYS[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。
返回列表