参考:
- http://www.redis.cn/commands/eval.html
- https://www.runoob.com/redis/redis-scripting.html
Redis从2.6.0版本开始支持Lua脚本,通过在服务器嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务器端原子地执行多个Redis命令。
1.使用Lua脚本的好处:
- 减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延。
- 原子操作:redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
- 复用:客户端发送的脚本会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。
2.相关命令:
- EVAL:执行 Lua 脚本
- EVALSHA:根据给定的 sha1 校验码,执行 Lua 脚本
- SCRIPT DEBUG:使用EVAL可以开启对脚本的调试(3.2.0版本)
- SCRIPT EXISTS:查看指定的脚本是否已经被保存在缓存当中
- SCRIPT FLUSH:从脚本缓存中移除所有脚本
- SCRIPT KILL:杀死当前正在运行的 Lua 脚本
- SCRIPT LOAD:将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本
一、EVAL
EVAL script numkeys key [key ...] arg [arg ...]
- script:参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数
- numbers:指定键名参数的个数
- key:表示在脚本中用到的Redis 键(key),这些键名参数可以在Lua中通过全局变量
数组访问,下标从1开始,如: KEYS[1]、KEYS[2]KEYS
- arg:参数,在Lua中通过全局变量
数组访问,下标从1开始,如:ARGV[1]、ARGV[2]ARGV
示例:
(1)使用
KEYS
数组、
ARGV
数组
redis> eval "return {KEYS[1], KEYS[2], ARGV[1], ARGV[2]}" 2 key1 key2 arg1 arg2
1) "key1"
2) "key2"
3) "arg1"
4) "arg2"
(2)输出list中所有元素
redis> rpush nums 1 2 3
(integer) 3
redis> eval "return redis.call('lrange', KEYS[1], 0, -1)" 1 nums
1) "1"
2) "2"
3) "3"
(3)集合去重
使用lua脚本文件方式,在命令行里执行,和在redis里执行不太一样,命令如下:
redis-cli --eval lua_file key1 key2 , arg1 arg2 arg3
- 参考:https://www.cnblogs.com/tinywan/p/9643022.html
示例:
- 插入测试数据
redis> sadd nums 1 2 3
(integer) 3
redis> smembers nums
1) "1"
2) "2"
3) "3"
- member.lua脚本
-- key
local key = KEYS[1]
-- 所有参数
local args = ARGV
-- 初始化result
local result = {}
-- 判断元素是否存在
for m, n in ipairs(args) do
local is_repeat = redis.call("sismember", key, n)
if (is_repeat) then
table.insert(result, 1, n)
end
end
return result
- 测试
$ redis-cli --eval ./member.lua nums , 1 2
1) "2"
2) "1"
二、SCRIPT LOAD、EVALSHA
-
:将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本SCRIPT LOAD script
- script:lua脚本
-
:根据给定的 sha1 校验码,执行Lua脚本EVALSHA sha1 numkeys key [key ...] arg [arg ...]
- sha1: 通过 SCRIPT LOAD 生成的 sha1 校验码
- 其他参数同
命令EVAL
示例:
redis> script load "return 'hello redis'"
"69dd69fc0ba1e25d8e2972008b6baee8eccf7da6"
redis> evalsha 69dd69fc0ba1e25d8e2972008b6baee8eccf7da6 0
"hello redis"
三、SCRIPT EXISTS、SCRIPT FLUSH
-
:查看指定的脚本是否已经被保存在缓存当中SCRIPT EXISTS script [script ...]
- script:通过 SCRIPT LOAD 生成的 sha1 校验码
-
:从脚本缓存中移除所有脚本SCRIPT FLUSH
示例:
redis> script load "return 'hello redis'"
"69dd69fc0ba1e25d8e2972008b6baee8eccf7da6"
redis> script exists 69dd69fc0ba1e25d8e2972008b6baee8eccf7da6
1) (integer) 1
redis> script flush
OK
redis> script exists 69dd69fc0ba1e25d8e2972008b6baee8eccf7da6
1) (integer) 0
四、SCRIPT KILL
SCRIPT KILL
:杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效
注:
- 这个命令主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限循环的脚本。
-
执行之后,当前正在运行的脚本会被杀死,执行这个脚本的客户端会从 EVAL 命令的阻塞当中退出,并收到一个错误作为返回值。SCRIPT KILL
示例:
客户端A:
# 当前没有正在执行的脚本
redisA> script kill
(error) NOTBUSY No scripts in execution right now.
# 客户端B执行脚本,客户端A杀死运行的脚本
redis> script kill
OK
(0.92s)
客户端B:
# 脚本死循环
redisB> eval "local a = 1; while 1 do a=1; end" 0
# 被杀死后,返回以下信息
(error) ERR Error running script (call to f_c8600a7388e4dd63490c59ede33602dbef5971eb): @user_script:1: Script killed by user with SCRIPT KILL...
(5.00s)
五、SCRIPT DEBUG
SCRIPT DEBUG YES|SYNC|NO
:使用EVAL可以开启对脚本的调试
- YES:打开非阻塞异步调试模式,调试Lua脚本(回退修改的数据)
- SYNC:打开阻塞同步调试模式,调试Lua脚本(保留修改的数据)
- NO:关闭脚本调试模式
注:LDB可以设置成两种模式:同步和异步。
- 异步模式下,服务器会创建新的调试连接,不阻塞其他连接,同时在调试连接结束后会回滚所有的数据修改,这可以保证再次调试时初始状态不变。
- 同步模式下,调试过程中,服务器其他连接会被阻塞,当调试结束后,所有的数据修改会被保存。
示例:
(1)redis内调试
# 异步调试,回退修改后的数据
redis> script debug yes
OK
# 测试脚本
redis> eval "local a = 1; \n local b = 2; \n return 3" 0
* Stopped at 1, stop reason = step over
-> 1 local a = 1;
# 使用 n 单步调试
redis> n
* Stopped at 2, stop reason = step over
-> 2 local b = 2;
redis> n
* Stopped at 3, stop reason = step over
-> 3 return 3
redis> n
(integer) 3
(Lua debugging session ended -- dataset changes rolled back)
(2)命令行内调试
使用第一节中的示例(3),命令中添加
--ldb
进入调试模式:
redis-cli --ldb --eval ./member.lua nums , 1 2
- 进入调试模式后,通过 n 或 s 进行单步调试,直到结束
- 默认是异步模式,调试结束后回滚修改的数据
- 使用
进入同步模式,此时服务器其他链接被阻塞,调试结束后修改后的数据会被保存--ldb-sync-mode
调试示例:【参考:http://blog.huangz.me/2017/redis-lua-debuger-introduction.html】
$ redis-cli --ldb --eval ./member.lua nums , 1 2
Lua debugging session started, please use:
quit -- End the session.
restart -- Restart the script in debug mode again.
help -- Show Lua script debugging commands.
* Stopped at 2, stop reason = step over
-> 2 local key = KEYS[1]
lua debugger> n
* Stopped at 5, stop reason = step over
-> 5 local args = ARGV
lua debugger> n
* Stopped at 8, stop reason = step over
-> 8 local result = {}
lua debugger> n
* Stopped at 11, stop reason = step over
-> 11 for m, n in ipairs(args) do
lua debugger> n
* Stopped at 12, stop reason = step over
-> 12 local is_repeat = redis.call("sismember", key, n)
lua debugger> n
<redis> sismember nums 1
<reply> 1
* Stopped at 13, stop reason = step over
-> 13 if (is_repeat) then
lua debugger> n
* Stopped at 14, stop reason = step over
-> 14 table.insert(result, 1, n)
lua debugger> n
* Stopped at 11, stop reason = step over
-> 11 for m, n in ipairs(args) do
lua debugger> n
* Stopped at 12, stop reason = step over
-> 12 local is_repeat = redis.call("sismember", key, n)
lua debugger> n
<redis> sismember nums 2
<reply> 1
* Stopped at 13, stop reason = step over
-> 13 if (is_repeat) then
lua debugger> n
* Stopped at 14, stop reason = step over
-> 14 table.insert(result, 1, n)
lua debugger> n
* Stopped at 11, stop reason = step over
-> 11 for m, n in ipairs(args) do
lua debugger> n
* Stopped at 17, stop reason = step over
-> 17 return result
lua debugger> n
1) "2"
2) "1"
(Lua debugging session ended -- dataset changes rolled back)