文档:调试 Lua 脚本

Valkey 包含一个完整的 Lua 调试器,可以大大简化编写复杂 Lua 脚本的任务。

Valkey Lua 调试器,代号 LDB,具有以下重要特性

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

快速开始

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

重要提示:请务必避免在 Valkey 生产服务器上调试 Lua 脚本。请使用开发服务器。另请注意,使用同步调试模式(非默认模式)会导致 Valkey 服务器在调试会话期间始终处于阻塞状态。

要使用 valkey-cli 启动新的调试会话,请执行以下操作

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

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

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

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

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

您将进入一个特殊模式,在该模式下 valkey-cli 不再接受其常规命令,而是打印帮助屏幕并将未修改的调试命令直接传递给 Valkey。

不传递给 Valkey 调试器的唯一命令是

  • quit -- 这将终止调试会话。它类似于移除所有断点并使用 continue 调试命令。此外,该命令将退出 valkey-cli
  • restart -- 调试会话将从头开始重新启动,从文件中重新加载脚本的新版本。因此,正常的调试周期包括在调试后修改脚本,并调用 restart 以便用新的脚本更改重新开始调试。
  • help -- 此命令将传递给 Valkey Lua 调试器,它将打印如下命令列表
lua debugger> help
Valkey 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 Valkey command.
[m]axlen [len]       Trim logged Valkey 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:
server.debug()        Produce logs in the debugger console.
server.breakpoint()   Stop execution as if there was a breakpoint in the
                     next line of code.

请注意,当您启动调试器时,它将以单步模式启动。它将在脚本中实际执行某事的第一个行停止,然后再执行该行。

从这一点开始,您通常会调用 step 来执行当前行并转到下一行。在单步执行时,Valkey 将显示服务器执行的所有命令,如下例所示

* Stopped at 1, stop reason = step over
-> 1   server.call('ping')
lua debugger> step
<redis> ping
<reply> "+PONG"
* Stopped at 2, stop reason = step over

<redis><reply> 行显示刚执行的行所执行的命令以及服务器的回复。请注意,这仅发生在单步模式下。如果您使用 continue 来执行脚本直到下一个断点,命令将不会被转储到屏幕上,以防止输出过多。

调试会话的终止

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

停止调试会话的另一种方法是手动按 Ctrl+C 中断 valkey-cli。请注意,任何中断 valkey-clivalkey-server 之间连接的事件也会中断调试会话。

当服务器关闭时,所有分叉的调试会话都将终止。

调试命令的缩写

调试可能是一项非常重复的任务。因此,每个 Valkey 调试器命令都以不同的字符开头,您可以使用单个首字母来指代该命令。

例如,您只需输入 s 而不是输入 step

断点

添加和移除断点非常简单,如在线帮助中所述。只需使用 b 1 2 3 4 在第 1、2、3、4 行添加断点。命令 b 0 移除所有断点。可以通过将要移除的断点所在行作为参数来移除选定的断点,但要加上负号前缀。例如,b -3 移除第 3 行的断点。

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

动态断点

使用 breakpoint 命令可以在特定行添加断点。然而,有时我们只想在发生特殊情况时才停止程序执行。为此,您可以在 Lua 脚本中使用 server.breakpoint() 函数。当调用它时,它会在将要执行的下一行模拟一个断点。

if counter > 10 then server.breakpoint() end

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

同步模式

如前所述,但默认情况下 LDB 使用分叉会话,并在脚本调试期间回滚所有数据更改。确定性通常在调试期间很有用,这样可以启动连续的调试会话而无需将数据库内容重置为原始状态。

然而,为了跟踪某些错误,您可能希望保留每个调试会话对键空间所做的更改。如果这是个好主意,您应该在 valkey-cli 中使用一个特殊选项 ldb-sync-mode 来启动调试器。

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

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

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

脚本日志记录

server.debug() 命令是一个强大的调试工具,可以在 Valkey Lua 脚本中调用,以便将内容记录到调试控制台

lua debugger> list
-> 1   local a = {1,2,3}
   2   local b = false
   3   server.debug(a,b)
lua debugger> continue
<debug> line 3: {1; 2; 3}, false

如果脚本在调试会话之外执行,server.debug() 将完全没有效果。请注意,该函数接受多个参数,这些参数在输出中用逗号和空格分隔。

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

使用 printeval 检查程序状态

虽然 server.debug() 函数可以用于直接在 Lua 脚本内部打印值,但通常在单步执行或在断点处停止时,观察程序的局部变量会很有用。

print 命令就是这样做的,它从当前调用帧开始,向上回溯到之前的调用帧,直到顶层,进行查找。这意味着即使我们在 Lua 脚本中的一个嵌套函数内,我们仍然可以使用 print foo 在调用函数的上下文中查看 foo 的值。当不带变量名调用时,print 将打印所有变量及其各自的值。

eval 命令执行 Lua 脚本的小片段,这些片段位于当前调用帧的上下文之外(由于当前的 Lua 内部机制,无法在当前调用帧的上下文中进行评估)。然而,您可以使用此命令来测试 Lua 函数。

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

调试客户端

LDB 采用客户端-服务器模型,其中 Valkey 服务器充当使用 RESP 进行通信的调试服务器。虽然 valkey-cli 是默认的调试客户端,但任何 客户端 只要满足以下条件之一即可用于调试

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