☰
java 中使用 lua 脚本
2026/10/9 13:59:37 网站建设 项目流程

文章目录

      • 不同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 nilObject

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)return0

service实现类

  • 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)
  1. 第一个参数:SECKILL_SCRIPT
    就是我们定义的DefaultRedisScript<Long>,里面加载了你写的lua脚本文件,指定返回类型是Long。

  2. 第二个参数: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。

  3. 后面可变参数: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。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询