在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脚本