Redis Lua 脚本调试是一种强大的工具,可以帮助您快速发现和解决Lua脚本中的问题。它允许您在运行脚本时逐步执行脚本,并检查每个步骤的结果。
两种调试模式
从Redis 3.2开始,内置了 Lua debugger
(简称LDB),使用Lua debugger
可以很方便的对我们编写的Lua脚本进行调试
异步模式 --ldb
开启 lua dubegger
,将会进入debug命令行。这个模式下 redis 会 fork 一个进程进入隔离环境,不会影响 redis 正常提供服务,但调试期间,原始 redis 执行命令、脚本的结果也不会体现到 fork 之后的隔离环境之中
同步模式 --ldb-sync-mode
同步模式,这个模式下,会阻塞 redis 上所有的命令、脚本,直到脚本退出,完全模拟了正式环境使用时候的情况,使用的时候务必注意这点。
参考案例
/data/lua # redis-cli -a 123456 --ldb --eval /data/lua/pong.lua | |
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. | |
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 1, stop reason = step over | |
-> 1 local foo = redis.call('ping') | |
lua debugger> |
-a 123456
Redis 登录密码--ldb
异步模式--eval
运行一个脚本help
可以查看更多帮助信息
Lua 脚本
案例1、执行一条命令
pong.lua
脚本
local foo = redis.call('ping') | |
return foo |
运行截图
案例2、携带参数的脚本执行
demo1.lua
local src = KEYS[1] | |
local dst = KEYS[2] | |
local count = tonumber(ARGV[1]) | |
return true |
运行截图
打印所有的KEYS
lua debugger> print KEYS | |
<value> {"list_a"; "list_b"; "10"} |
打印所有的ARGV
lua debugger> print ARGV | |
<value> {"10"} |
案例3、脚本中执行 Redis 命令
demo2.lua
local src = KEYS[1] | |
local dst = KEYS[2] | |
local count = tonumber(ARGV[1]) | |
-- 移除 list_a 列表的最后一个元素,返回值为移除的元素,即:e | |
local item = redis.call('rpop', src) | |
-- 将 e 值插入到 list_b 列表表头部 | |
redis.call('lpush', dst, item) | |
-- 返回 list_b 列表长度 | |
return redis.call('llen', dst) |
准备数据
执行结果
注意:KEYS
和ARGV
使用,
逗号分隔
r
调试命令可以 执行redis命令,在调试环境里redis.call("redis command")
函数执行Redis 命令print
可以打印脚本中的变量
案例4、脚本中执行 Redis 命令
demo3.lua
local src = KEYS[1] | |
local dst = KEYS[2] | |
local count = tonumber(ARGV[1]) | |
while count > 0 do | |
local item = redis.call('rpop', src) | |
redis.debug("value of item: ",item); | |
if item ~= false then | |
redis.call('lpush', dst, item) | |
end | |
count = count - 1 | |
end | |
return redis.call('llen', dst) |
redis.debug()
函数打印变量信息,配置c
调试命令,可以一次性输出所有值restart
修改lua脚本后,执行该命令可以重新开始调试quit
可以退出调试模式
如果移除代码 count = count - 1
,则会进入系循环
案例5、多个命令执行
demo4.lua
local src = KEYS[1] | |
local dst = KEYS[2] | |
local count = tonumber(ARGV[1]) | |
while count > 0 do | |
local username = redis.call('get',src) | |
redis.debug('username : ',username) | |
local age = redis.call('get',dst) | |
redis.debug('age : ',age) | |
count = count - 1 | |
end |
- 使用
w(whole)
命令,显示所有代码,看看需要在哪一行打断点
- 例如要在第7行打断点,则需要输入
b 7
lua debugger> b 7 | |
6 local username = redis.call('get',src) | |
#7 redis.debug('username : ',username) | |
8 local age = redis.call('get',dst) |
- 查看所有断点,输入命令:
b
- 我们需要直接运行到打断点的地方,则需要输入:
c
命令,会直接跳转到打第一个断点的语句,这时候可以打印断点之前的 变量,以下可以看出运行结果:
- 命令
b 0
删除所有断点(这里删除后,再第9行我们在打个断点),再次输入b
,发现已经没有断点。并且断点后面的值没法打印,只能打断点之前的变量
- 第 9 行打断点(b 9 ),查看所有代码,第9行已经被打伤断点了
- 再次跳转(c 命令)到打断点的地方。再次打印变量,发现已经可以打印了
- print 打印所有变量
案例6、Redis lua 版本和函数
Redis Lua脚本是 5.1.5
-- Copyright (C) ShaoBo Wan (Tinywan) | |
local KEYS1 = KEYS[1] | |
local KEYS2 = KEYS[2] | |
local ARGV1 = ARGV[1] | |
local ARGV2 = ARGV[2] | |
local ARGV3 = ARGV[3] | |
local ARGV4 = ARGV[4] | |
local status, type = next(redis.call('TYPE', KEYS[1])) -- type=none status=ok | |
if status ~= nil and status == 'ok' then | |
if type == 'zset' then | |
-- list = {"10090"; "10089"; "10088"; "10087"; "10086"} | |
local list = redis.call('ZREVRANGEBYSCORE', KEYS[1], ARGV[1], ARGV[2], 'LIMIT', ARGV[3], ARGV[4]) | |
-- 获取数组table长度:#list | |
local kk = #list | |
-- unpack它接受一个数组(table)作为参数,并默认从下标1开始返回数组的所有元素 | |
local k1, k2, k3, k4 ,k5 = unpack(list) | |
redis.debug('k1 ', k1) -- 10090 | |
redis.debug('k2 ', k2) -- 10089 | |
redis.debug('k3 ', k3) -- 10088 | |
redis.debug('k4 ', k4) -- 10087 | |
redis.debug('k5 ', k5) -- 10087 | |
if list ~= nil and #list > 0 then | |
-- ZREM key member [member ...] | |
redis.call('ZREM', KEYS[1], unpack(list)) -- unpack(list) 返回过期数组的所有元素 | |
-- HMGET key field [field ...] | |
local result = redis.call('HMGET', KEYS[2], unpack(list)) -- ["username:Tinywan,age:24"] | |
-- HDEL key field [field ...] | |
redis.call('HDEL', KEYS[2], unpack(list)) -- 1 | |
return result | |
end | |
end | |
end | |
return nil | |
next()
函数:第一个值返回函数是否执行成功(ok),第二个值返回执行结果(对应的值)>- 如果该key不存在,则返回
none
- 如果该key存在,则返回该key数据结构类型,如上返回
zset
,表示有序集合。 unpack()
函数:unpack它接受一个数组(table)作为参数,并默认从下标1开始返回数组的所有元素- 移除有序集中的一个或多个成员
ZREM key member [member ...]
- 获取多个字段的hash值数组
HMGET key field [field ...]
- 删除hash值的key
HDEL key field [field ...]
b 27
,b 28
打两个端点c
命令直接到第一个端点(b 26)p
打印之前所有的变量
重要: 以上消费者脚本会直接删除有序集合key和所对应的哈希值。所以为了消息的可靠性。通过以上脚本返回的值会存储在一个stream流中,如果在stream消费失败(没有进行ACK机制),则会进入待办Pending队列重复消费(知道ACK机制或者删除该消息队列)