Redis--Lua脚本以及在SpringBoot中的使用

发布于:2025-09-02 ⋅ 阅读:(16) ⋅ 点赞:(0)

前言、为什么要用 Lua?

  • 多步操作合并为一步,保证原子性。

  • 减少网络通信次数。

  • 下推逻辑到 Redis,提高性能。

一、Redis 使用 Lua 脚本的两种方式

方式一:使用 --eval 执行脚本文件

这种方式 需要先写一个 Lua 文件

📌 示例:创建一个 setname.lua 文件,内容如下:

-- KEYS[1] 表示 key
-- ARGV[1] 表示 value
-- ARGV[2] 表示过期时间(秒)
return redis.call("set", KEYS[1], ARGV[1], "EX", ARGV[2])

在命令行执行:

redis-cli --eval setname.lua name , czq 5

📌 解释:

  • --eval setname.lua namename 传给 KEYS[1]

  • 逗号 , 后面的是 ARGV"czq"ARGV[1]5ARGV[2]

执行效果:

OK

再验证:

get name
"czq"
ttl name
(integer) 5   # 有效期 5 秒

方式二:使用 eval 命令直接写脚本

这种方式 直接在 redis-cli 里执行 Lua 代码,不需要写文件。

📌 示例:

redis-cli

进入 redis-cli 后执行:

eval "return redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2])" 1 name czq 5

📌 解释:

  • "return redis.call(...)" → 直接写 Lua 代码

  • 1 表示有 1 个 KEYS 参数

  • name → KEYS[1]

  • czq → ARGV[1]

  • 5 → ARGV[2]

结果:

OK

二者区别

  • redis-cli --eval ... 是在 Linux shell 里执行,不用手动进入交互模式。(方式一)

    • redis-cli --eval lua文件的路径/lua的名称.lua....

  • eval "..." 必须进入 redis-cli 交互模式才能用。(方式二)


三、KEYS 和 ARGV 的作用

在 Redis 的 Lua 脚本里:

  • KEYS → 存放 key(可以有多个,KEYS[1]、KEYS[2]...)

  • ARGV → 存放参数(value、过期时间等)

📌 示例:

-- 假设脚本里是这样:
return "KEY=" .. KEYS[1] .. ", VALUE=" .. ARGV[1] .. ", TTL=" .. ARGV[2]

执行:

eval "return 'KEY='..KEYS[1]..', VALUE='..ARGV[1]..', TTL='..ARGV[2]" 1 name czq 5

输出结果:

"KEY=name, VALUE=czq, TTL=5"

👉 总结:

  • KEYS 专门用来传 key(好处是 Redis 会自动进行 key hash 定位,支持集群)

  • ARGV 专门用来传其他参数(value、过期时间等)


四、Spring Boot 使用 Lua 脚本

 这个例子是SETNX + 过期时间结合成的原子性,通常用于 分布式锁一人一单 之类的业务。

在 Spring Boot 里也可以执行这个 Lua 脚本。

📌 示例代码:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Collections;

@Service
public class RedisLuaService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 使用 SETNX + EX 实现键值设置(例如分布式锁)
     */
    public Object setNxWithExpire() {
        // 1️⃣ Lua 脚本
        // 先尝试 SETNX,如果成功,再设置过期时间
        String lua =
                "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
                "   redis.call('expire', KEYS[1], ARGV[2]) " +
                "   return 1 " +
                "else " +
                "   return 0 " +
                "end";

        // 2️⃣ 封装脚本
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(lua);
        script.setResultType(Long.class);

        // 3️⃣ 执行脚本
        return redisTemplate.execute(
                script,     
                Arrays.asList("lock_key"), // KEYS[1] 例如分布式锁的 key,keys 参数要求传的是一个 List<K>,定义成 List<String>,方便支持多个 key。
                "czq", "5" // ARGV[1] = value (锁的标识),ARGV[2] = 过期时间秒
        );
    }
}
✅ 使用说明
Object result = redisLuaService.setNameWithExpireIfAbsent();
System.out.println(result); 
  • 返回 1:设置成功(key 原本不存在)。

  • 返回 0:设置失败(key 已经存在)。

Redis 里结果:

127.0.0.1:6379> get name
"czq"
127.0.0.1:6379> ttl name
(integer) 5

📌 改造后的好处

  1. 保证原子性

    • 用 Lua 保证 SETNXEXPIRE 是在 Redis 内部一次性执行,避免 SETNX 成功但服务挂掉导致没有设置过期时间,从而出现“死锁”

  2. 分布式锁场景

    • SETNX 确保只有一个客户端能拿到锁。

    • EXPIRE 确保即使客户端崩溃,锁也会在过期时间后自动释放。

  3. 一人一单 / 防重提交

    • SETNX 用来保证某个 key(比如订单 ID 或用户 ID)只能被设置一次。

    • EXPIRE 防止 key 永久占用,给系统自动恢复的能力。

  4. 返回值可控

    • 返回 1 表示设置成功(抢到锁 / 成功下单)。

    • 返回 0 表示设置失败(别人已经抢到锁 / 已经下过单

📌小贴士:

        Redis 本身就支持 SET key value EX seconds NX 命令(SETNX + EXPIRE的原子性组合版),它是原子性的,不需要 Lua。Spring 的 RedisTemplate 里也可以直接调用,避免自己写 Lua。

@Service
public class RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public Boolean setIfAbsentWithExpire(String key, String value, long seconds) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, seconds, TimeUnit.SECONDS);
    }
}
优点

        ✅ 原子性保证(内部就是单条 Redis 命令)。
        ✅ 使用简单,无需写 Lua。
        ✅ 代码更可读,Spring 已经封装好了。


网站公告

今日签到

点亮在社区的每一天
去签到