Redis Lua 调试器(LDB)完全指南

发布于:2025-07-02 ⋅ 阅读:(18) ⋅ 点赞:(0)

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 模式(默认)

    1. Redis 主进程 fork() 创建子进程。
    2. 子进程加载脚本,进入调试;所有写操作仅影响子进程内存。
    3. 调试结束 → 子进程退出 → 一切回滚。
  • 同步模式 (--ldb-sync-mode)
    主进程直接调试脚本,调试期间 阻塞所有客户端,且数据修改会被保留。
    👉 仅在本地开发环境使用!

4. 快速上手

  1. 编写脚本 /tmp/script.lua

    local val = redis.call('INCR', KEYS[1])
    return val
    
  2. 启动调试

    redis-cli --ldb --eval /tmp/script.lua counter-key ,   # 逗号分隔 Keys 与 ARGV
    
  3. 第一行即断,输入 s (step) 执行当前行,或输入 c (continue) 跳到下一个断点。

5. 核心调试命令详解

缩写 命令 功能
s / n step / next 执行当前行,停在下一可执行行
c continue 运行至下一个断点 / 脚本结束
l list [line] [ctx] 查看源码。ll 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 then
  redis.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 消息沟通,因此你可以在任何语言里:

  1. 打开 TCP 连接至 Redis;
  2. 发送 SCRIPT DEBUG YES
  3. 发送 EVAL <script> …;
  4. 按交互协议收发调试指令。

示例(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 * y
  return result
]]

local cli = redis.connect('127.0.0.1', 6379)
cli:script('DEBUG', 'YES')
print(cli:eval(script, 0, 6, 9))   -- 断点停下
cli:ldbcontinue()                  -- 继续

11. 最佳实践与踩坑提示

  1. 永远在开发环境调试;生产库使用 fork 会话亦可能因子进程内存暴增而触发 OOM。
  2. 默认 fork 已回滚,若要保留变更慎用 --ldb-sync-mode
  3. 写复杂逻辑前先写单元测试;把 LDB 当“最后保险”。
  4. 长脚本宜拆功能函数,配合动态断点精确定位。
  5. 输出过大?用 maxlen 200 裁剪,或在脚本里自行 string.sub()
  6. 调试 RESP3 返回值时,可在脚本第一行 redis.setresp(3),便于打印 Map/Set。

网站公告

今日签到

点亮在社区的每一天
去签到