java 中使用 lua 脚本

文章目录

不同Lua返回值对应泛型对照表

Lua返回示例 Java泛型选什么
return 0/1/2 数字 Long
return "ok" 字符串 String
return {1,2,3} 数组 List<Object>
return nil Object

seckill.lua

lua 复制代码
-- 0 成功
-- 1 库存不足
-- 2 用户重复下单

local vouvherId = ARGV[1]
local userId = ARGV[2]

local stockKey = 'hmdp:seckill:stock:' .. vouvherId
local orderKey = 'hmdp:seckill:order:' .. userId

-- 判断库存
if tonumber(redis.call('get', stockKey)) <= 0 then
    return 1
end

-- 判断用户是否已经下单
if redis.call('sismember', orderKey, userId) == 1 then
    return 2
end

-- 扣库存
redis.call('incrby', stockKey, -1)
redis.call('sadd', orderKey, userId)

return 0

service实现类

  • Redis 协议里,lua脚本返回的整数是64 位有符号整数 ,对应 Java 的 long;即使数值很小 0、1、2,底层依然是 64bit,所以 Spring Data Redis 脚本 API 统一使用 Long 接收数字结果。
java 复制代码
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckillJvm.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }

	@Override
    public Result seckillVoucherAsync(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        "执行 lua 脚本"
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(), userId.toString()
        );
        int r = result.intValue();
        if(r != 0){
            // 1 库存不足;2 用户重复下单
            return Result.fail(r == 1 ? "库存不足" : "不能重复下单");
        }
    }

}



代码逐段拆解

java 复制代码
Long result = stringRedisTemplate.execute(
        SECKILL_SCRIPT,
        Collections.emptyList(),
        voucherId.toString(), userId.toString()
);

参数签名

java 复制代码
<T> T execute(
    RedisScript<T> script,
    List<String> keys,
    Object... args
)
  1. 第一个参数:SECKILL_SCRIPT

    就是我们定义的DefaultRedisScript<Long>,里面加载了你写的lua脚本文件,指定返回类型是Long。

  2. 第二个参数:Collections.emptyList() → KEYS\[\]数组

    📌 对应lua里 KEYS[1]、KEYS[2]

    你的 lua 脚本 完全没有使用 KEYS 数组,key都是在lua内部字符串拼接:

    lua 复制代码
    local stockKey = 'hmdp:seckill:stock:' .. vouvherId
    local orderKey = '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脚本:

    lua 复制代码
    local vouvherId = ARGV[1]
    local userId = ARGV[2]
    • voucherId.toString() → ARGV[1]
    • userId.toString() → ARGV[2]

    ARGV 放普通参数,没有集群槽位限制,可以随便传。


返回值 Long result

lua脚本 return 0 / 1 / 2,底层协议返回64位整数,所以Java接收为Long。

java 复制代码
"使用的时候 转换为 int"
if (result == 0) {
    // 成功
} else if (result ==1) {
    // 库存不足
} else if (result ==2) {
    // 重复下单
}

注意:result 不会是 null,脚本一定有 return。


🚨 你的脚本集群坑点(重点)

现在写法:key在lua内部拼接

lua 复制代码
local stockKey = 'hmdp:seckill:stock:' .. vouvherId
local orderKey = 'hmdp:seckill:order:' .. userId
  • 单机Redis:正常跑没问题
  • Redis集群环境:直接报错跨槽

改写成支持集群的正确版本(把key通过Java传入KEYS数组)

Java调用:

java 复制代码
String stockKey = "hmdp:seckill:stock:" + voucherId;
String orderKey = "hmdp:seckill:order:" + userId;

Long result = stringRedisTemplate.execute(
        SECKILL_SCRIPT,
        List.of(stockKey, orderKey), // KEYS[1] stockKey , KEYS[2] orderKey
        voucherId.toString(), userId.toString()
);

lua脚本修改:

lua 复制代码
-- KEYS[1] stockKey  hmdp:seckill:stock:xxx
-- KEYS[2] orderKey hmdp:seckill:order:xxx
-- ARGV[1] voucherId
-- ARGV[2] userId

local stockKey = KEYS[1]
local orderKey = KEYS[2]

if tonumber(redis.call('get', stockKey)) <= 0 then
    return 1
end

if redis.call('sismember', orderKey, ARGV[2]) == 1 then
    return 2
end

redis.call('incrby', stockKey, -1)
redis.call('sadd', orderKey, ARGV[2])

return 0

集群要求:脚本操作的所有 redis key,必须全部来自KEYS[]参数,不能在lua字符串拼接生成key。

相关推荐
码智社1 小时前
Spring Framework 全方位技术解析
java
wangqiaowq1 小时前
AST 安全求值
java·开发语言
Java_2017_csdn2 小时前
OFC、ERP、OMS、WMS、TMS 对比-详解
java
EatFan10 小时前
Spring Boot 4 落地观察:从 yudao-cloud、matecloud、JPower 看国产脚手架的升级路线与迁移清单
java·spring boot·后端·spring cloud·微服务·后端开发·jdk 21
Maiko Star11 小时前
* LangChain 提示词模板详解:ChatPromptTemplate 的使用与高级特性
java·人工智能·langchain
现任明教教主~12 小时前
Thinkphp站群蜘蛛池SaaS系统YanyvSEO含多用户/积分/六大引擎计费/易支付对接
java·开发语言·spring
一条破秋裤13 小时前
Linux 线程创建:pthread_create 与基本回收
java·linux·运维
布吉岛的石头13 小时前
Java 程序员第 49 阶段5:BERT 预训练目标 MLM+NSP 的工程含义
java·人工智能·深度学习·bert·transformer
摇滚侠14 小时前
《On Java 中文版 基础卷》阅读笔记 对象无处不在 03
java·笔记·python