Lua脚本
**简介:**Lua是一种功能强大的,高效,轻量级,可嵌入的脚本语言。它是动态类型语言,通过使用基于寄存器的虚拟机解释字节码运行,并具有增量垃圾收集的自动内存管理,是配置,脚本和快速原型设计的最佳选择
Redis中使用lua脚本的原因
-
减少网络延迟:Lua脚本将多个Redis命令组合成一个脚本,减少了客户端与服务器之间的网络交互。同时,Redis服务器还提供了EVALSHA命令,可以将脚本的SHA1值缓存在服务器中,下次再执行同样的脚本时,只需传递SHA1值即可,减少了网络传输时间。
-
原子操作:Lua脚本可以保证多个Redis命令的原子性,避免了并发问题
Redis怎么执行Lua脚本
1、lua函数
Lua脚本使用两个不同的Lua函数来调用任意的Redis命令
redis.call()
redis.pcall()
当redis命令执行结果返回错误时
-
call()
将返回给调用者一个错误, -
pcall()
将捕获的错误以Lua表的形式返回
127.0.0.1:6379> EVAL "return redis.call('xxxx')" 0
(error) ERR Error running script (call to f_1e6efd00ab50dd564a9f13e5775e27b966c2141e): @user_script:1: @user_script: 1: Unknown Redis command called from Lua script
127.0.0.1:6379> EVAL "return redis.pcall('xxxx')" 0
(error) @user_script: 1: Unknown Redis command called from Lua script
2、脚本执行
Redis Lua脚本可以通过 EVAL
命令或者 EVALSHA
命令执行
EVAL script numkeys [key [key ...]] [arg [arg ...]]
EVALSHA sha1 numkeys [key [key ...]] [arg [arg ...]]
numkeys
: the number of input key name arguments,keyname的数量key
: the name of key,多个keyname空格隔开。在Lua 脚本中通过KEYS[INDEX]
来获取对应的name,其中1 <= INDEX <= numkeys
。arg
: all the keys accessed by the script,多个key空格隔开,在Lua脚本中通过ARGV[INDEX]
来获取对应的key,其中1 <= INDEX <= numkeys
EVAL
和 EVALSHA
命令的区别
- EVAL 命令要求你在每次执行脚本的时候都发送一次脚本主体(script body)。Redis 有一个内部的脚本缓存机制,因此它不会每次都重新编译脚本。
- EVALSHA 命令, Evaluate a script from the server’s cache by its SHA1 digest. 。在执行EVALSHA的时候根据
具体的使用方法如下
使用 EVAL
命令
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 username zhangsan
OK
127.0.0.1:6379> EVAL "return redis.call('GET', KEYS[1])" 1 username
"zhangsan"
在较新的版本中,lua脚本又新增了许多命令,比如下面的 EVAL_RO
,是 EVAL
的一个变种,表示只读,修改数据的命令是不能被执行的
SCRIPT LOAD
和 EVALSHA
命令
使用 SCRIPT LOAD
命令加载lua脚本到服务器脚本缓存中,以达到重复使用,避免多次加载浪费带宽,加载但不会执行这个lua脚本,并返回一个sha校验值。需要配合EVALSHA
命令来执行缓存后的脚本
127.0.0.1:6379> SCRIPT LOAD "return 'hello'"
"1b936e3fe509bcbc9cd0664897bbe8fd0cac101b"
127.0.0.1:6379> EVALSHA 1b936e3fe509bcbc9cd0664897bbe8fd0cac101b 0
"hello"
不会经常变动的脚本推荐使用 EVALSHA
官方文档:https://redis.io/docs/latest/commands/?group=scripting
应用场景
- 事务操作:Lua脚本可以保证一组Redis命令的原子性
springboot实战
实现分布式锁
public String tryLock() {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("tryLock.lua")));
script.setResultType(Long.class);
Long state = stringRedisTemplate.execute(script, Lists.newArrayList("lock-good"), "uuid");
if (state == 1){
log.info("获取锁成功");
}else {
log.info("获取锁失败");
}
return "";
}
public String releaseLock() {
DefaultRedisScript<Boolean> script = new DefaultRedisScript<>();
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("releaseLock.lua")));
Boolean releaseLock = stringRedisTemplate.execute(script, Lists.newArrayList("lock-good"));
if (releaseLock){
log.info("释放锁成功");
}else {
log.info("释放锁失败");
}
return "";
}
lua脚本
tryLock.lua
local key = KEYS[1];
local value = ARGV[1];
local expireTime = ARGV[2];
if redis.call('SETNX', key, value) == 1 then
return redis.call('PEXPIRE', key, expireTime);
else
return 0;
end
releaseLock.lua
local key = KEYS[1];
local value = ARGV[1];
if redis.call('GET', key) == value then
redis.call('DEL', key);
return true;
else
return false;
end
数据更新
update.lua
local key = KEYS[1];
local value = ARGV[1];
local pexpire = ARGV[1];
if redis.call('GET',key) == value then
redis.call('SET', key, value);
redis.call('PEXPIRE', key, pexpire)
return 1;
else
return 0;
end