*Redis 调试指南

Redis 的开发非常注重稳定性:我们尽最大努力确保每个版本都能为你提供非常稳定的产品,不会出现崩溃。然而,即使尽了最大努力,也无法 100% 成功地避免所有关键错误。

当 Redis 崩溃时,它会产生一份详细的报告,说明发生了什么,但有时仅查看崩溃报告是不够的,Redis 核心团队也无法独立重现该问题:在这种情况下,我们需要能够重现该问题的用户的帮助。

本小指南展示了如何使用 GDB 提供 Redis 开发者更容易追踪错误所需的所有信息。

*什么是 GDB?

GDB 是 GNU 调试器:一个能够检查另一个程序内部状态的程序。通常,追踪和修复错误是收集有关错误发生时程序状态信息的练习,因此 GDB 是一个极其有用的工具。

GDB 可以以两种方式使用:

  • 它可以附加到正在运行的程序并在运行时检查其状态。
  • 它可以使用所谓的 核心文件 检查已终止程序的状态,即程序运行时内存的映像。

从调查 Redis 错误的角度来看,我们需要同时使用这两种 GDB 模式:能够重现错误的用户将 GDB 附加到他们正在运行的 Redis 实例,当崩溃发生时,他们创建 core 文件,然后开发者将使用该文件检查 Redis 在崩溃时的内部状态。

这样,开发者可以在他或她的计算机上执行所有检查,而无需用户的帮助,用户可以在生产环境中自由重启 Redis。

*编译无优化的 Redis

默认情况下,Redis 使用 -O2 开关编译,这意味着启用了编译器优化。这使得 Redis 可执行文件更快,但同时它也使 Redis(像任何其他程序一样)更难使用 GDB 检查。

最好使用 make noopt 命令(而不是仅使用普通的 make 命令)将 GDB 附加到编译时无优化的 Redis。然而,如果你已经在生产环境中运行 Redis,如果重新编译和重启会给你带来问题,则无需这样做。即使程度较小,GDB 仍然对使用优化编译的可执行文件有效。

很好的是,在第一次崩溃后确保使用 make noopt 重新编译 Redis,这样下次追踪问题会更简单。

你无需担心编译无优化的 Redis 会导致性能损失,这在你的环境中不太可能引起问题,因为通常这只是很小的百分比,因为 Redis 不是非常受 CPU 限制(它执行大量 I/O 来为查询提供服务)。

*将 GDB 附加到正在运行的进程

如果你已经在运行 Redis 服务器,你可以将 GDB 附加到它,这样如果 Redis 崩溃,就可以检查内部状态并生成 core dump 文件。

将 GDB 附加到 Redis 进程后,它会继续像往常一样运行,不会有任何性能损失,因此这不是一个危险的操作。

为了附加 GDB,你首先需要正在运行的 Redis 实例的 进程 ID(进程的 pid)。你可以使用 redis-cli 轻松获取它:

$ redis-cli info | grep process_id
process_id:58414

在上面的例子中,进程 ID 是 58414

  • 登录到你的 Redis 服务器。
  • (可选但推荐)启动 screentmux 或任何其他程序,以确保即使你的 ssh 连接超时,你的 GDB 会话也不会关闭。如果你不知道 screen 是什么,请善待自己并 阅读这篇文章
  • 通过键入以下命令将 GDB 附加到正在运行的 Redis 服务器:

    gdb <redis 可执行文件路径> <pid>

    例如:gdb /usr/local/bin/redis-server 58414

GDB 将启动并附加到正在运行的服务器,打印如下内容:

Reading symbols for shared libraries + done
0x00007fff8d4797e6 in epoll_wait ()
(gdb)
  • 此时 GDB 已附加,但 你的 Redis 实例被 GDB 阻塞。为了让 Redis 实例继续执行,只需在 GDB 提示符下键入 continue,然后按回车。

    (gdb) continue
    Continuing.
    
  • 完成!现在你的 Redis 实例已附加 GDB。你可以等待……下一次崩溃 :)

  • 现在,如果你正在使用 screen / tmux 运行 GDB,是时候分离你的会话了,按下通常的 Ctrl-a a 组合键。

*崩溃之后

Redis 有一个命令可以使用 DEBUG SEGFAULT 命令模拟段错误(换句话说就是严重崩溃)(当然不要对真正的生产实例使用它 ;)。所以我将使用这个命令来崩溃我的实例,以展示在 GDB 端会发生什么:

(gdb) continue
Continuing.

Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0xffffffffffffffff
debugCommand (c=0x7ffc32005000) at debug.c:220
220         *((char*)-1) = 'x';

如你所见,GDB 检测到 Redis 崩溃了,甚至能够向我显示导致崩溃的文件名和行号。这已经比 Redis 崩溃报告的堆栈跟踪(仅包含函数名和二进制偏移量)好得多了。

*获取堆栈跟踪

首先要做的是使用 GDB 获取完整的堆栈跟踪。这很简单,只需使用 bt 命令:(这是 backtrace 的缩写):

(gdb) bt
#0  debugCommand (c=0x7ffc32005000) at debug.c:220
#1  0x000000010d246d63 in call (c=0x7ffc32005000) at redis.c:1163
#2  0x000000010d247290 in processCommand (c=0x7ffc32005000) at redis.c:1305
#3  0x000000010d251660 in processInputBuffer (c=0x7ffc32005000) at networking.c:959
#4  0x000000010d251872 in readQueryFromClient (el=0x0, fd=5, privdata=0x7fff76f1c0b0, mask=220924512) at networking.c:1021
#5  0x000000010d243523 in aeProcessEvents (eventLoop=0x7fff6ce408d0, flags=220829559) at ae.c:352
#6  0x000000010d24373b in aeMain (eventLoop=0x10d429ef0) at ae.c:397
#7  0x000000010d2494ff in main (argc=1, argv=0x10d2b2900) at redis.c:2046

这显示了回溯,但我们也想使用 info registers 命令转储处理器寄存器:

(gdb) info registers
rax            0x0  0
rbx            0x7ffc32005000   140721147367424
rcx            0x10d2b0a60  4515891808
rdx            0x7fff76f1c0b0   140735188943024
rsi            0x10d299777  4515796855
rdi            0x0  0
rbp            0x7fff6ce40730   0x7fff6ce40730
rsp            0x7fff6ce40650   0x7fff6ce40650
r8             0x4f26b3f7   1327936503
r9             0x7fff6ce40718   140735020271384
r10            0x81 129
r11            0x10d430398  4517462936
r12            0x4b7c04f8babc0  1327936503000000
r13            0x10d3350a0  4516434080
r14            0x10d42d9f0  4517452272
r15            0x10d430398  4517462936
rip            0x10d26cfd4  0x10d26cfd4 <debugCommand+68>
eflags         0x10246  66118
cs             0x2b 43
ss             0x0  0
ds             0x0  0
es             0x0  0
fs             0x0  0
gs             0x0  0

确保在你的错误报告中包含 这两个输出。

*获取核心文件

下一步是生成核心转储,即正在运行的 Redis 进程的内存映像。这是使用 gcore 命令完成的:

(gdb) gcore
Saved corefile core.58414

现在你有核心转储可以发送给 Redis 开发者,但 重要的是要理解 这恰好包含了崩溃时 Redis 实例内部的所有数据;Redis 开发者会确保不会与任何其他人共享内容,并会在文件不再用于调试目的时立即删除,但你被警告,通过发送核心文件,你正在发送你的数据。

如果数据集中有敏感内容,我们建议直接将转储发送给 Salvatore Sanfilippo(即撰写本文档的人),邮箱地址为 antirez at gmail dot com

*发送给开发者的内容

最后,你可以将所有内容发送给 Redis 核心团队:

  • 你正在使用的 Redis 可执行文件。
  • bt 命令生成的堆栈跟踪,以及寄存器转储。
  • 你用 gdb 生成的核心文件。
  • 有关你正在使用的操作系统、GCC 版本和 Redis 版本的信息。

*谢谢

你的帮助极其重要!许多问题只能通过这种方式追踪,谢谢!帮助 Redis 调试也可能使你成为下一届 Redis Moka 奖 的获奖者之一。