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。

相关推荐
大文说跨境3 分钟前
亚马逊多账号管理浏览器怎么选?云登/紫鸟/AdsPower 对比
java·前端·自动化
CoderYanger18 分钟前
Java EE 进阶:4.Spring Web MVC 入门(第一弹)
java·开发语言·前端·spring·面试·java-ee·mvc
顽疲39 分钟前
java springboot vue 养老系统源码详解:请假 care_leave 销假与床位停费日扣实践
java·vue.js·spring boot
XIAEE1 小时前
一个即时通讯项目是怎么长出来的:(Spring Boot 3 + Vue 3 微前端 + 悟空 IM + LiveKit)
java·spring
n87651 小时前
Playwright Java WEB UI 自动化实现框架与逻辑
java·前端·ui
Java_AI工程师1 小时前
Spring4 / SpringBoot1.x 老项目想接入 AI?不用升级框架!3 套无痛落地方案
java·人工智能·spring boot
BBmmo1 小时前
Java Stream 的 filter 为什么看起来没运行?
java
VimorphTech1 小时前
Java 文件缓存实战:如何避免读到未下载完成的视频?
java·spring boot
白露与泡影2 小时前
后端 Java 面试核心八股汇总|高频面试题 + 解析,覆盖基础到进阶
java·开发语言·面试
SL_staff2 小时前
JVS-APS 实践:用双向反馈闭环打破制造计划孤岛
java·spring cloud·全栈