在 Redis 中调试 Lua 脚本

如何使用内置的 Lua 调试器

从版本 3.2 开始,Redis 包含一个完整的 Lua 调试器,它可以是 用于使编写复杂 Redis 脚本的任务变得更加简单。

Redis Lua 调试器(代号为 LDB)具有以下重要功能:

  • 它使用服务器-客户端模型,因此它是一个远程调试器。 Redis 服务器充当调试服务器,而默认客户端是redis-cli. 但是,可以按照服务器实现的简单协议开发其他客户端。
  • 默认情况下,每个新的调试会话都是一个分叉会话。 这意味着在调试 Redis Lua 脚本时,服务器不会阻塞,可用于开发或并行执行多个调试会话。 这也意味着在脚本调试会话完成后回滚更改,因此可以使用与上一个调试会话完全相同的 Redis 数据集再次重新启动新的调试会话。
  • 按需提供替代的同步(非分叉)调试模型,以便可以保留对数据集的更改。 在此模式下,服务器会在调试会话处于活动状态时阻止。
  • 支持逐步执行。
  • 支持静态和动态断点。
  • 支持将调试的脚本记录到调试器控制台中。
  • 检查 Lua 变量。
  • 跟踪脚本执行的 Redis 命令。
  • Redis 和 Lua 值的漂亮打印。
  • 无限循环和长执行检测,用于模拟断点。

快速开始

开始使用 Lua 调试器的一种简单方法是观看此视频 介绍:

重要提示:请确保避免使用 Redis 生产服务器调试 Lua 脚本。 请改用开发服务器。 另请注意,使用同步调试模式(不是默认模式)会导致 Redis 服务器在调试会话持续的所有时间内阻塞。

要使用redis-cli执行以下作:

  1. 使用您喜欢的编辑器在某个文件中创建脚本。假设您正在编辑位于/tmp/script.lua.

  2. 使用以下命令启动调试会话:

    ./redis-cli --ldb --eval /tmp/script.lua

请注意,使用--eval选项redis-cli您可以将键名称和参数传递给脚本,用逗号分隔,如以下示例所示:

./redis-cli --ldb --eval /tmp/script.lua mykey somekey , arg1 arg2

您将进入一个特殊模式,其中redis-cli不再接受其 normal 命令,而是打印帮助屏幕并传递未修改的调试 命令直接发送到 Redis。

唯一未传递给 Redis 调试器的命令是:

  • quit-- 这将终止调试会话。 这就像删除所有断点并使用continuedebugging 命令。 此外,该命令将从redis-cli.
  • restart-- 调试会话将从头开始重新启动,并从文件中重新加载新版本的脚本。 所以正常的调试周期包括在一些调试后修改脚本,然后调用restart以便使用新的脚本更改再次开始调试。
  • help-- 此命令将传递给 Redis Lua 调试器,该调试器将打印如下所示的命令列表:
lua debugger> help
Redis Lua debugger help:
[h]elp               Show this help.
[s]tep               Run current line and stop again.
[n]ext               Alias for step.
[c]ontinue           Run till next breakpoint.
[l]ist               List source code around current line.
[l]ist [line]        List source code around [line].
                     line = 0 means: current position.
[l]ist [line] [ctx]  In this form [ctx] specifies how many lines
                     to show before/after [line].
[w]hole              List all source code. Alias for 'list 1 1000000'.
[p]rint              Show all the local variables.
[p]rint <var>        Show the value of the specified variable.
                     Can also show global vars KEYS and ARGV.
[b]reak              Show all breakpoints.
[b]reak <line>       Add a breakpoint to the specified line.
[b]reak -<line>      Remove breakpoint from the specified line.
[b]reak 0            Remove all breakpoints.
[t]race              Show a backtrace.
[e]val <code>        Execute some Lua code (in a different callframe).
[r]edis <cmd>        Execute a Redis command.
[m]axlen [len]       Trim logged Redis replies and Lua var dumps to len.
                     Specifying zero as <len> means unlimited.
[a]bort              Stop the execution of the script. In sync
                     mode dataset changes will be retained.

Debugger functions you can call from Lua scripts:
redis.debug()        Produce logs in the debugger console.
redis.breakpoint()   Stop execution as if there was a breakpoint in the
                     next line of code.

请注意,当您启动调试器时,它将以单步执行模式启动。 它将在脚本的第一行停止,该脚本在执行之前实际执行某些作。

从这一点开始,您通常会调用step以便执行该行并转到下一行。 当您执行步骤时,Redis 将显示服务器执行的所有命令,如以下示例所示:

* 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>行显示行执行的命令 执行,以及来自服务器的回复。请注意,这仅在 stepping 模式下发生。 如果您使用continue为了执行脚本直到下一个断点,命令不会转储到屏幕上,以防止输出过多。

终止调试会话

当脚本自然终止时,调试会话结束,并且redis-cli返回其正常的非调试模式。您可以重新启动 会话使用restart命令。

停止调试会话的另一种方法是中断redis-cliCtrl+C.请注意,任何破坏 关系redis-cliredis-server将中断 debugging 会话。

当服务器关闭时,所有 fork 调试会话都将终止 下。

缩写调试命令

调试可能是一项非常重复的任务。因此,每个 Redis debugger 命令以不同的字符开头,你可以使用单个 initial 字符来引用命令。

因此,例如,不要键入step你可以直接输入s.

断点

添加和删除断点非常简单,如联机帮助中所述。 只需使用b 1 2 3 4在第 1、2、3、4 行中添加断点。 命令b 0删除所有断点。选定的断点可以是 removed using as argument 我们要删除的断点所在的行,但前缀为减号。 例如,b -3删除第 3 行的断点。

请注意,向 Lua 从不执行的行添加断点(如局部变量或注释的声明)将不起作用。 将添加断点,但由于脚本的这一部分永远不会执行,因此程序永远不会停止。

动态断点

使用breakpoint命令中,可以将断点添加到特定的 线。但是,有时我们只想停止程序的执行 当特殊的事情发生时。为此,您可以使用redis.breakpoint()函数。调用时,它会模拟 下一行中将要执行的断点。

if counter > 10 then redis.breakpoint() end

这个功能在调试时非常有用,这样我们就可以避免 多次手动继续执行脚本,直到达到给定条件 。

同步模式

如前所述,但默认 LDB 使用具有回滚功能的分叉会话 在调试脚本时作的所有数据更改。 确定性通常是调试过程中的好东西,这样连续的 调试会话可以在不重置数据库内容的情况下启动 恢复到其原始状态。

但是,为了跟踪某些 Bug,您可能希望保留执行的更改 添加到每个调试会话的密钥空间。当这是个好主意时,您 应使用特殊选项ldb-sync-moderedis-cli.

./redis-cli --ldb-sync-mode --eval /tmp/script.lua

注意:在此模式下,在调试会话期间无法访问 Redis 服务器,因此请谨慎使用。

在此特殊模式下,abort命令可以中途停止脚本,对数据集执行更改。 请注意,这与正常结束调试会话不同。 如果你只是打断redis-cli脚本将完全执行,然后会话终止。 相反,使用abort如果需要,您可以在中间中断脚本执行并启动新的调试会话。

从脚本记录

redis.debug()command 是一个强大的调试工具,可以 在 Redis Lua 脚本中调用,以便将内容记录到调试中 安慰:

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()完全没有效果。 请注意,该函数接受多个参数,这些参数在输出中用逗号和空格分隔。

表和嵌套表正确显示,以便程序员调试脚本时易于观察值。

检查程序状态printeval

虽然redis.debug()函数可用于打印值 直接从 Lua 脚本中,通常观察本地的 变量。

printcommand 就是这样做的,并在调用帧中执行查找 从当前 1 个返回到上一个 1 个,直到 top-level。 这意味着,即使我们在 Lua 脚本中加入嵌套函数, 我们仍然可以使用print foo查看foo在上下文中 调用函数。当在没有变量名称的情况下调用时,print将 打印所有变量及其各自的值。

eval命令在当前调用帧的上下文之外执行小段 Lua 脚本(使用当前的 Lua 内部组件,无法在当前调用帧的上下文中进行评估)。 但是,您可以使用此命令来测试 Lua 函数。

lua debugger> e redis.sha1hex('foo')
<retval> "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"

调试客户端

LDB 使用客户端-服务器模型,其中 Redis 服务器充当使用 RESP 进行通信的调试服务器。而redis-cli是默认的调试客户端,则任何客户端都可以用于调试,只要满足以下条件之一:

  1. 客户端提供用于设置调试模式和控制调试会话的本机接口。
  2. 客户端提供了一个接口,用于通过 RESP 发送任意命令。
  3. 客户端允许向 Redis 服务器发送原始消息。

例如,ZeroBrane StudioRedis 插件使用 redis-lua 与 LDB 集成。以下 Lua 代码是插件如何实现此目的的简化示例:

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()
RATE THIS PAGE
Back to top ↑