如何在Redis中使用Lua

244 阅读3分钟

在Redis中执行Lua脚本有两种方法:eval和evalsha。这两种方法的区别在于,是否要事先上传到reidis服务端,其中,eval不需要事先上传到服务端,而evalsha要将Lua脚本加载到Redis服务端。

1. 使用eval执行Lua脚本

语法:

  • eval 脚本内容 key个数 key列表 参数列表

示例:

  • eval 'return "hello " .. KEYS[1] .. ARGV[1]' 1 redis world

输出结果:"hello redisworld"

2. 使用evalsha执行Lua脚本

将Lua脚本加载到Redis服务端,得到该脚本的SHA1校验和,evalsha命令使用SHA1作为参数可以直接执行对应Lua脚本,避免每次发送 Lua脚本的开销。

加载脚本: script load命令可以将脚本内容加载到Redis内存中

script load "$(cat lua_get.lua)" ---> "7413dc2440db1fea7c0a0bde841fa68eefaf149c"

执行脚本: 语法:

  • evalsha 脚本SHA1值 key个数 key列表 参数列表

示例:

  • evalsha 7413dc2440db1fea7c0a0bde841fa68eefaf149c 1 redis world

输出结果:"hello redisworld"

3. 在Lua脚本中使用Redis API

Redis 提供给 Lua 脚本内置函数,以便于 Lua脚本可以使用redis的全部功能。

3.1. redis.call

示例:

redis.call("set", "hello", "world")

redis.call("get", "hello")

3.1. redis.pcall

redis.call和redis.pcall的不同在于,如果redis.call执行失败,那么脚本执行结束会直接返回错误,而redis.pcall会忽略错误继续执行脚本

4. 示例,使用lua脚本实现分布式锁

4.1 获取锁

-- KEYS[1] - 锁的键名 (lock key)
-- ARGV[1] - 请求锁的客户端标识 (client identifier)
-- ARGV[2] - 锁的超时时间 (timeout in milliseconds)

local lock_key = KEYS[1]
local client_id = ARGV[1]
local timeout = tonumber(ARGV[2])

-- 尝试设置锁
if redis.call("SET", lock_key, client_id, "NX", "PX", timeout) then
    -- 设置成功,返回 1 表示获取锁成功
    return 1
else
    -- 设置失败,返回 0 表示获取锁失败
    return 0
end

4.2 释放锁

-- KEYS[1] - 锁的键名 (lock key)
-- ARGV[1] - 请求锁的客户端标识 (client identifier)

local lock_key = KEYS[1]
local client_id = ARGV[1]

-- 检查当前锁是否由该客户端持有
if redis.call("GET", lock_key) == client_id then
    -- 如果是,则删除锁
    return redis.call("DEL", lock_key)
else
    -- 否则,不做任何事情并返回 0
    return 0
end

4.3 自动续期

-- 续期锁的 Lua 脚本
-- KEYS[1] - 锁的键名 (lock key)
-- ARGV[1] - 请求锁的客户端标识 (client identifier)
-- ARGV[2] - 新的超时时间 (new timeout in milliseconds)

local lock_key = KEYS[1]
local client_id = ARGV[1]
local new_timeout = tonumber(ARGV[2])

-- 检查当前锁是否由该客户端持有
if redis.call("GET", lock_key) == client_id then
    -- 如果是,则更新锁的有效期
    return redis.call("PEXPIRE", lock_key, new_timeout)
else
    -- 否则,不做任何事情并返回 0
    return 0
end

4.4 使用 Jedis 执行 Lua 脚本

import redis.clients.jedis.Jedis;

public class RedisLockExample {
    private static final String LOCK_SCRIPT_GET =
        "if redis.call('SET', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) then return 1 else return 0 end";
    private static final String LOCK_SCRIPT_RELEASE =
        "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";

    public static void main(String[] args) {
        try (Jedis jedis = new Jedis("localhost")) {
            // 获取锁
            Object result = jedis.eval(LOCK_SCRIPT_GET, 1, "mylock", "client1", "10000"); // 10秒超时
            if ("1".equals(result.toString())) {
                System.out.println("Lock acquired!");

                // 模拟工作负载
                Thread.sleep(5000);

                // 释放锁
                result = jedis.eval(LOCK_SCRIPT_RELEASE, 1, "mylock", "client1");
                if ("1".equals(result.toString())) {
                    System.out.println("Lock released!");
                } else {
                    System.out.println("Failed to release lock.");
                }
            } else {
                System.out.println("Failed to acquire lock.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5. Redis管理lua脚本的命令

  • script load 用于将Lua脚本加载到redis内存中
  • script exists 用于判断sha1是否已经加载到Redis内存中
  • script flush 用于清除Redis内存已经加载的所有Lua脚本
  • script kill 用于杀掉正在执行的Lua脚本