*Redis 延迟问题排查
本文档将帮助你了解如果你在使用 Redis 时遇到延迟问题,问题可能是什么。
在此上下文中,延迟 是指客户端发出命令与客户端收到命令回复之间的最大延迟。通常 Redis 处理时间极低,在亚微秒范围内,但某些条件会导致更高的延迟数值。
*我时间不多,给我检查清单
以下文档对于以低延迟方式运行 Redis 非常重要。然而我理解我们都是忙碌的人,所以让我们从一个快速检查清单开始。如果你未能遵循这些步骤,请返回此处阅读完整文档。
- 确保你没有运行阻塞服务器的慢速命令。使用 Redis 慢日志功能 检查这一点。
- 对于 EC2 用户,确保你使用基于 HVM 的现代 EC2 实例,如 m3.medium。否则 fork() 太慢。
- 必须从内核中禁用透明大页。使用
echo never > /sys/kernel/mm/transparent_hugepage/enabled禁用它们,然后重新启动 Redis 进程。 - 如果你使用的是虚拟机,你可能存在与 Redis 无关的固有延迟。使用
./redis-cli --intrinsic-latency 100检查运行时环境的最小延迟。注意:你需要在 服务器 而不是客户端上运行此命令。 - 启用并使用 Redis 的 延迟监控 功能,以便获得 Redis 实例中延迟事件和原因的人类可读描述。
一般来说,使用以下表格进行持久性与延迟/性能之间的权衡,从更强的安全性到更好的延迟排序。
- AOF + fsync always:这非常慢,只有在你知道自己在做什么时才应该使用它。
- AOF + fsync every second:这是一个很好的折衷方案。
- AOF + fsync every second + no-appendfsync-on-rewrite 选项设置为 yes:这与上面相同,但避免了在重写期间进行 fsync 以降低磁盘压力。
- AOF + fsync never。在此设置中,fsync 由内核完成,磁盘压力和延迟峰值风险更小。
- RDB。根据你配置的保存触发器,你有广泛的权衡选择。
现在对于有 15 分钟时间的人来说,下面是详细信息……
*测量延迟
如果你遇到延迟问题,你可能知道如何在应用程序上下文中测量它,或者你的延迟问题可能非常明显。然而 redis-cli 可以用来测量 Redis 服务器的延迟(以毫秒为单位),只需尝试:
redis-cli --latency -h `host` -p `port`
*使用 Redis 内部延迟监控子系统
从 Redis 2.8.13 开始,Redis 提供了延迟监控功能,能够对不同执行路径进行采样以了解服务器在哪里阻塞。这使得本文档中说明的问题调试变得更加简单,因此我们建议尽快启用延迟监控。请参阅 延迟监控文档。
虽然延迟监控采样和报告功能将使理解 Redis 系统中延迟来源变得更加简单,但仍建议你广泛阅读本文档以更好地理解 Redis 和延迟峰值的主题。
*延迟基线
有一种延迟是运行 Redis 的环境所固有的,即操作系统内核提供的延迟,如果你使用虚拟化,则是你所使用的管理程序提供的延迟。
虽然这种延迟无法消除,但研究它很重要,因为它是基线,换句话说,你无法实现比环境中每个进程由于内核或管理程序实现或设置而经历的延迟更好的 Redis 延迟。
我们将这种延迟称为 固有延迟,从 Redis 2.8.7 版本开始,redis-cli 能够测量它。这是在运行 Linux 3.11.0 的入门级服务器上的示例运行。
注意:参数 100 是测试将执行的秒数。我们运行测试的时间越长,就越有可能发现延迟峰值。100 秒通常是合适的,但你可以在不同时间执行几次运行。请注意,该测试是 CPU 密集型的,可能会饱和系统中的单个核心。
$ ./redis-cli --intrinsic-latency 100
Max latency so far: 1 microseconds.
Max latency so far: 16 microseconds.
Max latency so far: 50 microseconds.
Max latency so far: 53 microseconds.
Max latency so far: 83 microseconds.
Max latency so far: 115 microseconds.
注意:在这种特殊情况下,redis-cli 需要在运行或计划运行 Redis 的 服务器 上运行,而不是在客户端上。在这种特殊模式下,redis-cli 根本不连接 Redis 服务器:它只是尝试测量内核不提供 CPU 时间让 redis-cli 进程本身运行的最长时间。
在上面的示例中,系统的固有延迟仅为 0.115 毫秒(或 115 微秒),这是个好消息,但请记住,固有延迟可能会根据系统负载随时间变化。
虚拟化环境不会显示如此好的数字,尤其是在高负载或有嘈杂邻居的情况下。以下是在运行 Redis 和 Apache 的 Linode 4096 实例上的运行:
$ ./redis-cli --intrinsic-latency 100
Max latency so far: 573 microseconds.
Max latency so far: 695 microseconds.
Max latency so far: 919 microseconds.
Max latency so far: 1606 microseconds.
Max latency so far: 3191 microseconds.
Max latency so far: 9243 microseconds.
Max latency so far: 9671 microseconds.
这里我们有 9.7 毫秒的固有延迟:这意味着我们无法要求 Redis 比这更好。然而,在不同时间、不同负载或嘈杂邻居的虚拟化环境中,其他运行很容易显示更差的值。我们在表面上正常运行的系统中能够测量到高达 40 毫秒的延迟。
*网络和通信引起的延迟
客户端使用 TCP/IP 连接或 Unix 域连接连接到 Redis。典型的 1 Gbit/s 网络延迟约为 200 微秒,而使用 Unix 域套接字时延迟可低至 30 微秒。实际上这取决于你的网络和系统硬件。除了通信本身之外,系统还会增加一些额外的延迟(由于线程调度、CPU 缓存、NUMA 放置等)。在虚拟化环境中,系统引起的延迟明显高于物理机。
结果是,即使 Redis 在亚微秒范围内处理大多数命令,对服务器进行多次往返的客户端也必须为这些网络和系统相关的延迟付出代价。
因此,高效的客户端会尝试通过将多个命令流水线化在一起来限制往返次数。服务器和大多数客户端完全支持这一点。聚合命令如 MSET/MGET 也可以用于此目的。从 Redis 2.4 开始,许多命令还支持所有数据类型的可变参数。
以下是一些指南:
- 如果负担得起,优先选择物理机而不是虚拟机来托管服务器。
- 不要系统性地连接/断开服务器连接(对于基于 Web 的应用程序尤其如此)。保持连接尽可能长久。
- 如果你的客户端与服务器在同一主机上,请使用 Unix 域套接字。
- 优先使用聚合命令(MSET/MGET)或具有可变参数的命令(如果可能),而不是流水线化。
- 如果可能,优先使用流水线化而不是连续往返。
- Redis 支持 Lua 服务器端脚本,以涵盖不适合原始流水线化的情况(例如当一个命令的结果是后续命令的输入时)。
在 Linux 上,一些人可以通过调整进程放置(taskset)、cgroups、实时优先级(chrt)、NUMA 配置(numactl)或使用低延迟内核来获得更好的延迟。请注意,原版 Redis 并不适合绑定在 单个 CPU 核心上。Redis 可以派生非常消耗 CPU 的后台任务,如 BGSAVE 或 BGREWRITEAOF。这些任务必须 绝不 与主事件循环在同一个核心上运行。
在大多数情况下,这类系统级优化是不需要的。只有在你需要它们并且熟悉它们时才这样做。
*Redis 的单线程特性
Redis 使用 主要 单线程设计。这意味着单个进程使用称为 多路复用 的技术为所有客户端请求提供服务。这意味着 Redis 在任何给定的时刻只能为单个请求提供服务,因此所有请求都是顺序服务的。这与 Node.js 的工作方式非常相似。然而,这两个产品通常不被认为很慢。这部分是因为完成单个请求所需的时间很少,但主要是因为这些产品被设计为不在系统调用上阻塞,例如从套接字读取数据或向套接字写入数据。
我说 Redis 主要 是单线程的,因为实际上从 Redis 2.4 开始,我们在 Redis 中使用线程来在后台执行一些慢速 I/O 操作,主要与磁盘 I/O 相关,但这并没有改变 Redis 使用单个线程为所有请求提供服务的事实。
*慢速命令产生的延迟
单线程的一个后果是,当一个请求服务很慢时,所有其他客户端都会等待这个请求被服务。执行正常命令时,如 GET 或 SET 或 LPUSH,这根本不是问题,因为这些命令在恒定(且非常小)的时间内执行。然而有一些命令操作许多元素,如 SORT、LREM、SUNION 等。例如,取两个大集合的交集可能需要相当长的时间。
所有命令的算法复杂度都有文档记录。在使用你不熟悉的命令时,系统地检查它是一个好习惯。
如果你有延迟顾虑,你应该避免对由许多元素组成的值使用慢速命令,或者你应该使用 Redis 复制运行一个副本,在那里运行所有慢速查询。
可以使用 Redis 慢日志功能 监控慢速命令。
此外,你可以使用你喜欢的每进程监控程序(top、htop、prstat 等)快速检查主 Redis 进程的 CPU 消耗。如果它很高而流量不高,通常是使用了慢速命令的迹象。
重要说明:由执行慢速命令产生的延迟的一个 非常 常见的来源是在生产环境中使用 KEYS 命令。KEYS,如 Redis 文档中所述,应该仅用于调试目的。自 Redis 2.8 以来,引入了新的命令来增量迭代键空间和其他大型集合,请查看 SCAN、SSCAN、HSCAN 和 ZSCAN 命令以获取更多信息。
*Fork 产生的延迟
为了在后台生成 RDB 文件,或者如果启用了 AOF 持久化则重写仅追加文件,Redis 必须派生后台进程。 fork 操作(在主线程中运行)本身就可能引起延迟。
在大多数类 Unix 系统上,forking 是一个昂贵的操作,因为它涉及复制与进程链接的大量对象。这对于与虚拟内存机制关联的页表尤其如此。
例如在 Linux/AMD64 系统上,内存被划分为 4 kB 页面。为了将虚拟地址转换为物理地址,每个进程存储一个页表(实际上表示为树),其中至少包含进程地址空间每页的指针。因此一个大型 24 GB 的 Redis 实例需要一个 24 GB / 4 kB * 8 = 48 MB 的页表。
当执行后台保存时,此实例将不得不被 fork,这将涉及分配和复制 48 MB 的内存。这需要时间和 CPU,尤其是在虚拟机上,分配和初始化大内存块可能很昂贵。
*不同系统中的 Fork 时间
现代硬件在复制页表方面相当快,但 Xen 不是。Xen 的问题不是虚拟化特有的,而是 Xen 特有的。例如,使用 VMware 或 Virtual Box 不会导致缓慢的 fork 时间。
以下是比较不同 Redis 实例大小的 fork 时间的表格。数据通过执行 BGSAVE 并查看 INFO 命令输出中的 latest_fork_usec 字段获得。
然而好消息是 新型的基于 EC2 HVM 的实例在 fork 时间方面要好得多,几乎与物理服务器持平,因此例如使用 m3.medium(或更好)实例将提供良好的结果。
- VMware 上的 Linux 强健虚拟机:6.0GB RSS,fork 耗时 77 毫秒(每 GB 12.8 毫秒)。
- 物理机上运行的 Linux(未知硬件):6.1GB RSS,fork 耗时 80 毫秒(每 GB 13.1 毫秒)
- 物理机上运行的 Linux(Xeon @ 2.27Ghz):6.9GB RSS,fork 耗时 62 毫秒(每 GB 9 毫秒)。
- 6sync (KVM) 上的 Linux 虚拟机:360 MB RSS,fork 耗时 8.2 毫秒(每 GB 23.3 毫秒)。
- EC2 上的 Linux 虚拟机,旧实例类型 (Xen):6.1GB RSS,fork 耗时 1460 毫秒(每 GB 239.3 毫秒)。
- EC2 上的 Linux 虚拟机,新实例类型 (Xen):1GB RSS,fork 耗时 10 毫秒(每 GB 10 毫秒)。
- Linode (Xen) 上的 Linux 虚拟机:0.9GB RSS,fork 耗时 382 毫秒(每 GB 424 毫秒)。
如你所见,某些在 Xen 上运行的虚拟机的性能下降幅度在一个数量级到两个数量级之间。对于 EC2 用户的建议很简单:使用现代基于 HVM 的实例。
*透明大页引起的延迟
不幸的是,当 Linux 内核启用了透明大页时,Redis 在调用 fork 以持久化到磁盘后会产生很大的延迟惩罚。大页是以下问题的原因:
- 调用 Fork,创建两个具有共享大页的进程。
- 在一个繁忙的实例上,几次事件循环运行将导致命令针对几千页,导致几乎整个进程内存的写时复制。
- 这将导致巨大的延迟和巨大的内存使用。
确保使用以下命令 禁用透明大页:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
*交换(操作系统分页)引起的延迟
Linux(和许多其他现代操作系统)能够将内存页从内存重新定位到磁盘,反之亦然,以便有效使用系统内存。
如果 Redis 页面被内核从内存移动到交换文件,当 Redis 使用此内存页中存储的数据时(例如访问存储在此内存页中的键),内核将停止 Redis 进程以便将该页移回主内存。这是一个慢速操作,涉及随机 I/O(与访问已经在内存中的页相比),将导致 Redis 客户端经历异常延迟。
内核将 Redis 内存页重新定位到磁盘主要有三个原因:
- 系统处于内存压力下,因为运行中的进程需要比可用数量更多的物理内存。这个问题最简单的实例就是 Redis 使用的内存超过了可用内存。
- Redis 实例数据集或数据集的一部分几乎完全空闲(从未被客户端访问),因此内核可以将空闲内存页交换到磁盘。这个问题非常罕见,因为即使是一个中等速度的实例也会经常触及所有内存页,迫使内核将所有页保留在内存中。
- 某些进程正在系统上产生大量读取或写入 I/O。由于文件通常被缓存,这往往会给内核带来增加文件系统缓存的压力,从而产生交换活动。请注意,这包括可能产生大文件的 Redis RDB 和/或 AOF 后台线程。
幸运的是,Linux 提供了很好的工具来调查这个问题,所以当怀疑交换导致延迟时,最简单的事情就是检查是否确实如此。
首先要做的是检查有多少 Redis 内存被交换到磁盘上。为此,你需要获取 Redis 实例的 pid:
$ redis-cli info | grep process_id
process_id:5454
现在进入此进程的 /proc 文件系统目录:
$ cd /proc/5454
在这里你会找到一个名为 smaps 的文件,它描述了 Redis 进程的内存布局(假设你使用的是 Linux 2.6.16 或更新版本)。 此文件包含有关我们进程内存映射的非常详细的信息,一个名为 Swap 的字段正是我们要找的。然而,不只有一个 swap 字段,因为 smaps 文件包含我们 Redis 进程的不同内存映射(进程的内存布局比简单的线性页数组更复杂)。
由于我们对我们进程交换的所有内存感兴趣,首先要做的是在整个文件中 grep Swap 字段:
$ cat smaps | grep 'Swap:'
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 12 kB
Swap: 156 kB
Swap: 8 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 4 kB
Swap: 0 kB
Swap: 0 kB
Swap: 4 kB
Swap: 0 kB
Swap: 0 kB
Swap: 4 kB
Swap: 4 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
如果一切都是 0 kB,或者偶尔有 4k 条目,一切都完全正常。实际上在我们的示例实例(一个运行 Redis 并为每秒数百用户服务的真实网站)中,有一些条目显示了更多的交换页。为了调查这是否是一个严重的问题,我们修改命令以便同时打印内存映射的大小:
$ cat smaps | egrep '^(Swap|Size)'
Size: 316 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 8 kB
Swap: 0 kB
Size: 40 kB
Swap: 0 kB
Size: 132 kB
Swap: 0 kB
Size: 720896 kB
Swap: 12 kB
Size: 4096 kB
Swap: 156 kB
Size: 4096 kB
Swap: 8 kB
Size: 4096 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 1272 kB
Swap: 0 kB
Size: 8 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 16 kB
Swap: 0 kB
Size: 84 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 8 kB
Swap: 4 kB
Size: 8 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 4 kB
Swap: 4 kB
Size: 144 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 4 kB
Swap: 4 kB
Size: 12 kB
Swap: 4 kB
Size: 108 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 272 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
从输出中可以看出,有一个 720896 kB 的映射(只有 12 kB 被交换)和另一个映射中有 156 kB 被交换:基本上我们的内存只有非常小的部分被交换,所以这根本不会造成任何问题。
如果进程内存的相当部分被交换到磁盘上,你的延迟问题可能与交换有关。如果你的 Redis 实例出现这种情况,你可以使用 vmstat 命令进一步验证:
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
0 0 3980 697932 147180 1406456 0 0 2 2 2 0 4 4 91 0
0 0 3980 697428 147180 1406580 0 0 0 0 19088 16104 9 6 84 0
0 0 3980 697296 147180 1406616 0 0 0 28 18936 16193 7 6 87 0
0 0 3980 697048 147180 1406640 0 0 0 0 18613 15987 6 6 88 0
2 0 3980 696924 147180 1406656 0 0 0 0 18744 16299 6 5 88 0
0 0 3980 697048 147180 1406688 0 0 0 4 18520 15974 6 6 88 0
^C
输出中对我们需要的有趣部分是两列 si 和 so,它们计算从/到交换文件交换的内存量。如果你在这两列中看到非零计数,则你的系统中存在交换活动。
最后,iostat 命令可用于检查系统的全局 I/O 活动。
$ iostat -xk 1
avg-cpu: %user %nice %system %iowait %steal %idle
13.55 0.04 2.92 0.53 0.00 82.95
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util
sda 0.77 0.00 0.01 0.00 0.40 0.00 73.65 0.00 3.62 2.58 0.00
sdb 1.27 4.75 0.82 3.54 38.00 32.32 32.19 0.11 24.80 4.24 1.85
如果你的延迟问题是由于 Redis 内存被交换到磁盘,你需要降低系统中的内存压力,如果 Redis 使用的内存超过可用内存,则增加更多 RAM,或者避免在同一系统中运行其他内存密集型进程。
*AOF 和磁盘 I/O 引起的延迟
延迟的另一个来源是 Redis 上的仅追加文件支持。AOF 基本上使用两个系统调用来完成其工作。一个是 write(2),用于将数据写入仅追加文件,另一个是 fdatasync(2),用于将内核文件缓冲区刷新到磁盘,以确保用户指定的持久性级别。
write(2) 和 fdatasync(2) 调用都可能是延迟的来源。例如,write(2) 可以在系统范围的同步正在进行时阻塞,或者当输出缓冲区已满且内核需要刷新到磁盘以接受新写入时阻塞。
fdatasync(2) 调用是更严重的延迟来源,因为在使用的许多内核和文件系统组合中,它可能需要几毫秒到几秒钟才能完成,尤其是在某些其他进程正在进行 I/O 的情况下。因此,如果可能的话,Redis 从 Redis 2.4 开始在不同的线程中进行 fdatasync(2) 调用。
我们将看到配置如何影响使用 AOF 文件时的延迟量和来源。
AOF 可以配置为使用 appendfsync 配置选项以三种不同方式在磁盘上执行 fsync(此设置可以使用 CONFIG SET 命令在运行时修改)。
当 appendfsync 设置为 no 时,Redis 不执行 fsync。 在此配置中,延迟的唯一来源可能是 write(2)。 当这种情况发生时通常没有解决方案,因为简单地说磁盘无法应对 Redis 接收数据的速度,然而如果磁盘没有被其他进行 I/O 的进程严重拖慢,这是不常见的。
当 appendfsync 设置为 everysec 时,Redis 每秒执行一次 fsync。它使用不同的线程,如果 fsync 仍在进行中,Redis 会使用缓冲区将 write(2) 调用延迟最多两秒(因为在 Linux 上,如果对同一文件进行 fsync,write 会阻塞)。然而如果 fsync 耗时过长,即使 fsync 仍在进行中,Redis 最终也会执行 write(2) 调用,这可能是一个延迟来源。
当 appendfsync 设置为 always 时,每次写入操作都会执行 fsync,然后在向客户端回复 OK 代码之前(实际上 Redis 会尝试将同时执行的许多命令聚类到单个 fsync 中)。在这种模式下,性能通常非常低,强烈建议使用快速磁盘和能够在短时间内执行 fsync 的文件系统实现。
大多数 Redis 用户将使用 no 或 everysec 设置作为 appendfsync 配置指令。最小延迟的建议是避免其他进程在同一系统上进行 I/O。 使用 SSD 磁盘也可以帮助,但通常即使非 SSD 磁盘在仅追加文件方面也表现良好,如果磁盘是空闲的,因为 Redis 写入仅追加文件而不进行任何寻道。
如果你想调查与仅追加文件相关的延迟问题,可以在 Linux 下使用 strace 命令:
sudo strace -p $(pidof redis-server) -T -e trace=fdatasync
上述命令将显示 Redis 在主线程中执行的所有 fdatasync(2) 系统调用。使用上述命令,当 appendfsync 配置选项设置为 everysec 时,你将看不到后台线程执行的 fdatasync 系统调用。为了做到这一点,只需向 strace 添加 -f 开关。
如果你愿意,你也可以使用以下命令查看 fdatasync 和 write 系统调用:
sudo strace -p $(pidof redis-server) -T -e trace=fdatasync,write
然而由于 write(2) 也用于向客户端套接字写入数据,这可能会显示太多与磁盘 I/O 无关的内容。显然没有办法告诉 strace 只显示慢速系统调用,所以我使用以下命令:
sudo strace -f -p $(pidof redis-server) -T -e trace=fdatasync,write 2>&1 | grep -v '0.0' | grep -v unfinished
*过期产生的延迟
Redis 通过两种方式驱逐过期键:
- 一种 惰性 方式在命令请求键时发现它已过期时将其过期。
- 一种 主动 方式每 100 毫秒过期几个键。
主动过期被设计为自适应的。每 100 毫秒(每秒 10 次)启动一次过期周期,并将执行以下操作:
- 采样
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP个键,驱逐所有已经过期的键。 - 如果发现超过 25% 的键已过期,则重复。
鉴于 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 默认设置为 20,并且该过程每秒执行 10 次,通常每秒只有 200 个键被主动过期。这足以快速清理数据库,即使已经过期的键长时间未被访问,因此 惰性 算法没有帮助。同时每秒仅过期 200 个键对 Redis 实例的延迟没有影响。
然而该算法是自适应的,如果在采样的键集中发现超过 25% 的键已过期,它将循环。但鉴于我们每秒运行算法 10 次,这意味着在我们的随机样本中超过 25% 的键正在过期的不幸事件至少发生在 同一秒内。
基本上这意味着 如果数据库中有许多键在同一秒过期,并且这些键至少占当前设置了过期的键人口的 25%,Redis 可能会阻塞,以使已经过期的键百分比低于 25%。
需要这种方法是为了避免对已经过期的键使用太多内存,而且通常完全无害,因为一大群键在同一确切秒过期是很奇怪的,但用户广泛使用具有相同 Unix 时间的 EXPIREAT 并非不可能。
简而言之:请注意,许多键在同一时刻过期可能是延迟的来源。
*Redis 软件看门狗
Redis 2.6 引入了 Redis 软件看门狗,这是一个调试工具,旨在追踪那些由于某种原因使用正常工具无法分析的延迟问题。
软件看门狗是一个实验性功能。虽然它被设计为用于生产环境,但在继续之前应该谨慎备份数据库,因为它可能对 Redis 服务器的正常执行产生意想不到的交互。
仅在 作为最后手段 且没有其他方法可以追踪问题时才使用它。
此功能的工作原理如下:
- 用户使用 CONFIG SET 命令启用软件看门狗。
- Redis 开始不断监控自身。
- 如果 Redis 检测到服务器阻塞在某个没有足够快返回的操作中,并且这可能是延迟问题的来源,则会将有关服务器阻塞位置的高级报告转储到日志文件中。
- 用户在 Redis Google Group 中撰写消息联系开发人员,并在消息中包含看门狗报告。
请注意,此功能无法使用 redis.conf 文件启用,因为它被设计为仅在已运行的实例上启用,且仅用于调试目的。
要启用该功能,只需使用以下命令:
CONFIG SET watchdog-period 500
周期以毫秒为单位指定。在上面的示例中,我指定仅当服务器检测到 500 毫秒或更长的延迟时才记录延迟问题。可配置的最小周期为 200 毫秒。
当你完成软件看门狗的使用后,你可以通过将 watchdog-period 参数设置为 0 来关闭它。重要提示:请记住这样做,因为让实例保持看门狗开启的时间超过需要通常不是一个好主意。
以下是一旦软件看门狗检测到超过配置时间的延迟时,你将在日志文件中看到的内容示例:
[8547 | signal handler] (1333114359)
--- WATCHDOG TIMER EXPIRED ---
/lib/libc.so.6(nanosleep+0x2d) [0x7f16b5c2d39d]
/lib/libpthread.so.0(+0xf8f0) [0x7f16b5f158f0]
/lib/libc.so.6(nanosleep+0x2d) [0x7f16b5c2d39d]
/lib/libc.so.6(usleep+0x34) [0x7f16b5c62844]
./redis-server(debugCommand+0x3e1) [0x43ab41]
./redis-server(call+0x5d) [0x415a9d]
./redis-server(processCommand+0x375) [0x415fc5]
./redis-server(processInputBuffer+0x4f) [0x4203cf]
./redis-server(readQueryFromClient+0xa0) [0x4204e0]
./redis-server(aeProcessEvents+0x128) [0x411b48]
./redis-server(aeMain+0x2b) [0x411dbb]
./redis-server(main+0x2b6) [0x418556]
/lib/libc.so.6(__libc_start_main+0xfd) [0x7f16b5ba1c4d]
./redis-server() [0x411099]
------
注意:在示例中,DEBUG SLEEP 命令被用来阻塞服务器。如果服务器在不同上下文中阻塞,堆栈跟踪会有所不同。
如果你碰巧收集了多个看门狗堆栈跟踪,我们鼓励你将所有内容发送到 Redis Google Group:我们获得的跟踪越多,就越容易理解你实例的问题所在。