Redis Lua 调试器(LDB)完全指南
1. 前言
自 Redis 3.2 起,官方为内嵌的 Lua 解释器配备了完整的远程调试器——LDB
(Lua DeBugger)。
写复杂脚本时,你再也不必靠 redis.log()
+ 想象力“盲调”;LDB 提供 单步、断点、变量查看、命令跟踪 等桌面级 Debug 功能,而且在默认的 fork 模式下,不会破坏原有数据集,一次调完还能“重来一遍”。
2. LDB 总览
特性 | 说明 |
---|---|
客户端-服务器 | Redis 充当调试服务器;默认客户端是 redis-cli ,也可按协议自写 |
Fork 会话 | 新开子进程运行脚本, 主节点继续提供服务;会话结束即回滚数据 |
同步会话 | 使用 --ldb-sync-mode 强制阻塞主库,保留数据变更 |
断点 | 支持行号断点、动态断点 (redis.breakpoint() ) |
单步/继续 | step / next / continue |
变量检查 | print 查看局部 / 全局 (KEYS , ARGV ) |
脚本日志 | redis.debug() 高亮打印 |
命令追踪 | 单步模式下自动 Dump redis.call() 的命令与返回值 |
3. 调试会话模型
+-------------------+
redis-cli (--ldb) <─RESP─► Redis Server| ├─ main I/O| └─ forked child (runs script with LDB)
-
Fork 模式(默认)
- Redis 主进程
fork()
创建子进程。 - 子进程加载脚本,进入调试;所有写操作仅影响子进程内存。
- 调试结束 → 子进程退出 → 一切回滚。
- Redis 主进程
-
同步模式 (
--ldb-sync-mode
)
主进程直接调试脚本,调试期间 阻塞所有客户端,且数据修改会被保留。
👉 仅在本地开发环境使用!
4. 快速上手
-
编写脚本
/tmp/script.lua
local val = redis.call('INCR', KEYS[1]) return val
-
启动调试
redis-cli --ldb --eval /tmp/script.lua counter-key , # 逗号分隔 Keys 与 ARGV
-
第一行即断,输入
s
(step
) 执行当前行,或输入c
(continue
) 跳到下一个断点。
5. 核心调试命令详解
缩写 | 命令 | 功能 |
---|---|---|
s / n | step / next | 执行当前行,停在下一可执行行 |
c | continue | 运行至下一个断点 / 脚本结束 |
l | list [line] [ctx] | 查看源码。l 或 l 0 查看当前位置;l 1 1000000 (或 whole ) 打印全部 |
p | print [var] | 查看所有/指定局部变量;也支持 KEYS , ARGV |
b | break | b 10 增加行 10 断点;b -10 删除;b 0 清全部 |
t | trace | 打印 Lua 调用栈 |
e | eval <code> | 在新帧执行 Lua 代码片段 |
r | redis <cmd ...> | 手动发送 Redis 命令(调试态之上) |
a | abort | 同步模式下,保留 当前数据并终止脚本 |
maxlen [len] | 设置/查看打印裁剪长度;0 表示不限 | |
restart | 重新加载脚本文件并重开会话 | |
quit | 删除所有断点 → 继续执行 → 退出 redis-cli |
6. 断点系统
6.1 静态断点
b 5 8 12
:在第 5/8/12 行添加断点- 注意:只有被 Lua VM 真正执行的行 才能命中,如
local
声明或注释不会停。
6.2 动态断点 redis.breakpoint()
if tonumber(redis.call('GET', KEYS[1]) or 0) > 100 thenredis.breakpoint() -- 命中后停在下一行
end
适合只在特定条件下停下来,省去反复手动 continue
7. 同步模式(非 Fork)
redis-cli --ldb-sync-mode --eval /tmp/script.lua
- 调试期间 Redis 整体阻塞;脚本的写操作会真实落库。
- 可随时用
abort
中断并保留已执行的部分。
8. 日志与状态检查
8.1 redis.debug()
redis.debug('keys=', KEYS, 'val=', redis.call('GET', KEYS[1]))
-
仅在 LDB 会话中输出:
<debug> line 3: "keys=", {"counter-key"}, "val=", "42"
-
离开调试器后调用无任何副作用。
8.2 print
/ eval
print
(无参)→ 打印当前帧所有局部print foo
→ 深度查找上层帧的foo
eval <lua>
→ 在独立帧执行 Lua(常用于快速测试函数)
9. 会话终止与重启
操作 | 结果 |
---|---|
脚本正常结束 | 自动退出 LDB,redis-cli 恢复普通模式 |
Ctrl + C | 断开连接 → 会话终止(fork 会话数据回滚) |
restart | 重新读取文件并开启新调试 |
10. 自定义调试客户端
LDB 使用标准 RESP 消息沟通,因此你可以在任何语言里:
- 打开 TCP 连接至 Redis;
- 发送
SCRIPT DEBUG YES
; - 发送
EVAL <script> …
; - 按交互协议收发调试指令。
示例(Lua + redis-lua):
local redis = require 'redis'
redis.commands['ldbcontinue'] = redis.command('C')local script = [[local x, y = tonumber(ARGV[1]), tonumber(ARGV[2])local result = x * yreturn result
]]local cli = redis.connect('127.0.0.1', 6379)
cli:script('DEBUG', 'YES')
print(cli:eval(script, 0, 6, 9)) -- 断点停下
cli:ldbcontinue() -- 继续
11. 最佳实践与踩坑提示
- 永远在开发环境调试;生产库使用 fork 会话亦可能因子进程内存暴增而触发 OOM。
- 默认 fork 已回滚,若要保留变更慎用
--ldb-sync-mode
。 - 写复杂逻辑前先写单元测试;把 LDB 当“最后保险”。
- 长脚本宜拆功能函数,配合动态断点精确定位。
- 输出过大?用
maxlen 200
裁剪,或在脚本里自行string.sub()
。 - 调试 RESP3 返回值时,可在脚本第一行
redis.setresp(3)
,便于打印 Map/Set。