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。

相关推荐
Wang's Blog2 小时前
Java 项目实战: 交友项目 - FastDFS:入门案例
java·开发语言·交友
apcipot_rain3 小时前
C++转Java日记(03)
java·开发语言·c++
皮皮林5513 小时前
Java实现微信扫码登录
java
Yyyyyy~5 小时前
【Java】Day4Web基础知识
java·spring boot
灿飞屋7 小时前
Kotlin与Java虚拟机三:控制流
android·java·开发语言·后端·python·kotlin·安卓
NE_STOP7 小时前
Spring AI Alibaba - MCP入门
java
nice_lcj5207 小时前
【从零理解 Spring AI · 07】对话记忆:让模型记住上一句说了什么
java·spring ai·ai应用开发·chatmemory·对话记忆
SL_staff8 小时前
制造业交付崩盘?用跨部门节点协同计划打通销售、生产与质量链路
java·开源·产品
文丑颜不良啊9 小时前
LeetCode2261~2280题目解析
java·leetcode
SL_staff9 小时前
告别规则黑盒:JVS-Rules 在金融风控中的可视化决策编排实践
java·产品·全栈