从 Redis 3.2 版本开始, Redis 将内置一个完整的 Lua 调试器, 它的存将会让编写复杂的 Lua 脚本变得容易。
Redis Lua 调试器, 代号 LDB , 拥有以下主要特性:
学习如何使用 Lua 调试器的其中一个简单的方法, 就是观看以下视频教程:
Note
请使用开发服务器而不是生成服务器来进行调试。 另外别忘了, 使用非默认的同步调试模式将导致服务器在整个调试过程中都会被阻塞。
用户可以通过以下步骤来开启一个新的调试会话:
。
- /tmp/script.lua
。
- redis-cli --ldb --eval /tmp/script.lua
注意, 在使用 redis-cli 客户端的
选项的时候, 你可以将需要传递给脚本的键名以及参数一并提供给客户端, 其中键名和参数之间使用一个逗号来进行分割, 就像这样:
- -eval
- redis - cli--ldb--eval / tmp / script.lua mykey somekey,
- arg1 arg2
在执行这条命令之后, redis-cli 就会进入特殊的调试模式, 它不再接受普通的 Redis 命令, 而是会打印出一个帮助界面, 并将用户键入的调试命令原原本本地发送给 Redis 服务器。
进入了调试模式的 redis-cli 将提示用户使用以下三个命令:
—— 结束调试回话。 调试器将移除所有断点, 跳过所有未执行语句, 并最终退出 redis-cli 。
- quit
—— 重新载入脚本文件, 并重头开始一个新的调试会话。 用户在调试的过程中, 通常会在调试之后对脚本进行修改, 然后通过执行
- restart
来对修改后的脚本继续进行调试, 这个步骤一般会迭代发生多次。
- restart
—— 打印出可用的调试命令。
- help
- lua debugger> help
- Redis Lua debugger help:
- [h]elp 打印这个帮助
- [s]tep 运行当前行然后再次停止
- [n]ext step 的别名,作用相同
- [c]continue 运行直到遇到下个断点为止
- [l]list 列出当前行附近的代码
- [l]list [line] 列出指定行 line 附近的代码
- line = 0 代表列出当前行附近的代码
- [l]list [line] [ctx] 列出位于行 line 附近的 ctx 行代码
- [w]hole 列出整个脚本源码,相当于执行 'list 1 1000000'
- [p]rint 打印出所有局部变量
- [p]rint <var> 打印出指定的局部变量,也可以用于打印全局变量 KEYS 以及 ARGV
- [b]reak 列出所有断点
- [b]reak <line> 将断点添加至指定行
- [b]reak -<line> 移除指定行的断点
- [b]reak 0 移除所有断点
- [t]race 打印回溯链条(Show a backtrace)
- [e]eval <code> 在不同的调用幁中执行指定的 Lua 代码
- [r]edis <cmd> 执行给定的 Redis 命令
- [m]axlen [len] 将 Redis 的回复以及 Lua 变量转储(dump)截断至指定的长度。
- 将参数 len 的值设置为 0 表示不对长度进行限制。
- [a]abort 停止执行脚本。
- 在同步模式下,对数据库的修改将被保留。
- 以下是两个可以在 Lua 脚本中进行调用的调试函数:
- redis.debug() 在调试终端中输出日志。
- redis.breakpoint() 暂停脚本的执行,就像遇到了一个断点一样。
需要注意的是, 在默认情况下, 调试器在启动之后将处于单步调试模式。 调试器会停在脚本第一行具有实际作用的代码之前, 然后等待用户的指令。
这时, 用户可以通过执行
命令来让调试器执行当前行的代码, 并移动到下一行具有实际作用的代码之前。 在执行
- step
命令时, 服务器将会展示出服务器执行的所有命令, 就像这样:
- step
- * Stopped at 1, stop reason = step over
- -> 1 redis.call('ping')
- lua debugger> step
- <redis> ping
- <reply> "+PONG"
- * Stopped at 2, stop reason = step over
其中
和
- <redis>
分别展示了被执行的命令以及服务器返回的回复。 注意, 这种情况只会出现在单步调试模式中。 如果用户使用
- <reply>
命令, 让调试器一直执行代码直到碰到断点为止, 那么为了防止信息输出过多, 调试器将不会显示出相关的命令信息。
- continue
当脚本自然终止时, 调试会话将结束,
将返回至正常的非调试状态。 用户可以通过
- redis-cli
命令来重新开始一个调试会话。
- restart
另一种终止调试会话的方法是通过按下 CTRL + C , 手动终止
。 另外, 当
- redis-cli
和
- redis-cli
服务器因为任何原因而断开连接时, 调试会话也会终止。
- redis-server
当服务器关闭时, 所有子进程调试会话都会被终止。
因为调试通常是一个繁重的重复性任务, 所以每个 Redis 调试命令都以不同的字符为开始, 用户可以通过键入这些单个字符来代替键入整个命令。
比如说, 用户可以通过只键入
来代替键入
- s
。
- step
正如视频教程中所说, 添加和移除断点是非常容易的。 用户只要执行命令
即可以在第 1 、2 、3 、 4 行分别加上断点。 而执行命令
- b 1 2 3 4
则会移除所有断点。 如果用户想要移除指定行的断点, 那么只需要在执行命令时在行数面前加一个负号就可以了, 比如执行命令
- b 0
就可以移除第 3 行的断点。
- b -3
需要注意的是, 向 Lua 不会执行的那些行 —— 比如声明局部变量的行以及注释行 —— 是无效的: 虽然断点会添加到这些行上面, 但用于这些行不会被执行, 所以调试器将不会在这些行上面停止。
使用
命令虽然可以给指定的行添加断点, 但有时候我们想要在某些情况发生时才停止程序的执行, 这时, 我们可以考虑在 Lua 脚本中使用
- breakpoint
函数, 这个函数将在接下来将要被执行的代码行前面模拟一个断点。
- redis.breakpoint()
以下是一个使用动态断点的例子:
- if counter > 10 then redis.breakpoint() end
这个特性在调试时非常有用, 它可以避免我们为了遇到特定的条件而一直手动地控制脚本的执行进程。
正如之前所说, LDB 在默认情况下将使用子进程来创建调试会话, 并且在调试完成之后, 脚本对数据库进行的任何修改都将会被回滚。 因为后续的调试会话不需要重置数据库就可以直接启动, 所以这种做法通常来说都是合理的。
但是在一些特殊情况下, 为了追踪特定的 bug , 用户可以会想要保留每个调试会话对数据库所做的修改。 想要这么做的用户可以在启动调试器时, 向
客户端给定
- redis-cli
选项:
- ldb-sync-mode
- redis-cli --ldb-sync-mode --eval /tmp/script.lua
注意, 运行在这一调试模式下的服务器在进行调试的过程中将不可用, 所以请小心使用这一选项。 当处于这一模式时,
命令可以在中途停止那些已经对数据库进行过修改的脚本。 使用
- abort
命令来终止调试会话与正常地终止调试会话是不同的: 如果用户只是简单地用 CTRL + C 来停止
- abort
, 那么调试会话将在整个脚本都执行完毕之后终止; 而
- redis-cli
则会中途停止脚本并在有需要时启动一个新的调试会话。
- abort
函数是一个非常强力的调试手段, 它可以在 Lua 脚本内部调用, 并将日志写入至调试终端:
- redis.debug()
- lua debugger > list - >1 local a = {
- 1,
- 2,
- 3
- }
- 2 local b = false 3 redis.debug(a, b) lua debugger >
- continue < debug > line 3 : {
- 1;
- 2;
- 3
- },
- false
如果脚本不是在调试会话中执行, 那么
函数将不会引起任何效果。 另外需要注意的是,
- redis.debug()
可以接受多个参数, 这些参数在输出中将由逗号以及空格进行分隔。
- redis.debug()
为了让值可以更为直观地展示给正在调试脚本的程序员, 表格以及嵌套表格将以正确的方式(displayed correctly)进行展示。
虽然使用
函数可以在 Lua 脚本内部打印指定的值, 但是在进行单步调试以及在断点中暂停时, 能够观察局部变量也是非常有用的一项能力。
- redis.debug()
函数就是完成这一工作的其中一种方法, 它可以从当前行开始向之前的行进行回溯并查找指定的变量, 一直到顶层(top-level)为止。 这意味着即使调试器位于 Lua 脚本的一个嵌套函数之内, 它仍然可以使用
来查找位于当前被调用函数上下文中的
- print foo
变量的值。 如果用户以无参数的方式调用
- foo
命令, 那么
将打印出所有变量以及它们的值。
命令可以在当前调用幁之外的上下文中执行指定的一小段 Lua 脚本 (顺带一提,在当前的 Lua 内部实现中,在当前调用幁的上下文中进行求值是不可能的)。 用户可以通过这个命令来测试 Lua 函数:
- eval
- lua debugger> e redis.sha1hex('foo')
- <retval> "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
LDB 使用客户端 - 服务器模型, 作为调试服务器的 Redis 服务器使用 进行通讯, 而
则作为默认的调试客户端。 与此同时, 所有客户端只要满足以下条件的任意一个, 就可以用于调试:
- redis-cli
比如说, 的 就通过 集成了 LDB 。 以下这个简单的示例演示了这个插件是如何完成这一工作的:
- local redis = require 'redis'
- -- add LDB's Continue command
- redis.commands['ldbcontinue'] = redis.command('C')
- -- script to be debugged
- local script = [[
- local x, y = tonumber(ARGV[1]), tonumber(ARGV[2])
- local result = x * y
- return result
- ]]
- local client = redis.connect('127.0.0.1', 6379)
- client:script("DEBUG", "YES")
- print(unpack(client:eval(script, 0, 6, 9)))
- client:ldbcontinue()
黄健宏
2017.3.28
来源: http://www.tuicool.com/articles/rYBF3mf