诊断延迟问题

寻找响应慢的原因

如果你在使用 Redis 时遇到延迟问题,本文档将帮助你了解可能出现的问题。

在此上下文中,延迟是客户端发出命令与客户端收到命令回复之间的时间的最大延迟。通常 Redis 处理时间非常短,在微秒范围内,但是某些条件会导致延迟数字升高。

时间紧迫,给我一份清单

以下文档对于以低延迟方式运行 Redis 非常重要。但是我知道我们都很忙,所以我们先从一份快速清单入手吧。如果你无法按照这些步骤操作,请返回此处阅读完整文档。

  1. 确保未运行阻塞服务器的缓慢命令。使用 Redis 慢日志功能 来检查这一点。
  2. 对于 EC2 用户,请确保使用基于 HVM 的现代 EC2 实例,例如 m3.medium。否则 fork() 将过于缓慢。
  3. 必须从内核禁用透明大页面。使用 echo never > /sys/kernel/mm/transparent_hugepage/enabled 将其禁用,然后重新启动 Redis 进程。
  4. 如果你使用的是虚拟机,则可能会有与 Redis 无关的固有延迟。使用 ./redis-cli --intrinsic-latency 100 检查运行时环境可以预期的最小延迟。注意:你需要在服务器中运行此命令,而不是在客户端中。
  5. 启用并使用 Redis 的延迟监视器功能,以获得对 Redis 实例中的延迟事件和原因的人类可读描述。

通常,使用下表针对耐久性对比延迟/性能权衡,从较强的安全性到更好的延迟进行排序。

  1. 始终使用 AOF + fsync:这非常慢,你只应该在了解其作用的情况下使用它。
  2. 每秒使用一次 AOF + fsync:这是一种很好的折衷方案。
  3. 每秒使用一次 AOF + fsync + 将 no-appendfsync-on-rewrite 选项设置为 yes:这与上述情况相同,但在重写期间避免 fsync 以降低磁盘压力。
  4. 从不使用 AOF + fsync。在这种设置中,fsync 交由内核处理,磁盘压力甚至更低并且延迟峰值的风险也更低。
  5. 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 服务器:它只会尝试测量内核未向 redis-cli 进程本身提供 CPU 时间运行的最长时间。

在上面的示例中,系统的固有延迟仅为 0.115 毫秒(或 115 微秒),这是一个好消息,但请记住,固有延迟可能会随着系统负载而变化。

虚拟化环境不会显示出如此好的数字,特别是在负载较高或有嘈杂的邻居时。以下是运行在 Linode 4096 实例上,同时运行 Redis 和 Apache 的结果

$ ./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 千兆位网络的典型延迟约为 200 微秒,而使用 Unix 域套接字的延迟可能低至 30 微秒。实际上这取决于网络和系统硬件。除通信本身外,系统还增加了一些延迟(由于线程调度、CPU 高速缓存、NUMA 置入等)。在虚拟化环境中,由系统引起的延迟明显高于物理机上的延迟。

结果是,即使 Redis 在亚微秒范围内处理大部分命令,向服务器执行多次往返的客户端也必须承担这些与网络和系统相关的延迟。

因此,高效的客户端将尝试通过将多个命令组合在一起以流水线方式发送,来限制往返次数。此功能得到服务器和大多数客户端的完全支持。对于该目的,还可以使用 MSET/MGET 之类的聚合命令。从 Redis 2.4 开始,许多命令还需要支持对所有数据类型进行变参。

这里有一些指南

  • 如果承受得起,优先使用物理机而不是 VM 托管服务器。
  • 不要系统地连接/断开到服务器(特别是对于基于 Web 的应用程序而言)。尽可能让连接保持长久。
  • 如果客户端与服务器位于同一主机上,请使用 Unix 域套接字。
  • 优先使用聚合命令(MSET/MGET)或具有变参的命令(如果可能),而不是使用流水线传输。
  • 优先使用流水线传输(如果可能),而不是往返序列。
  • Redis 支持 Lua 服务器端脚本,用来涵盖不适合原始流水线处理的情况(例如,当某个命令的结果是后续命令的输入时)。

在 Linux 上,某些人可以通过调整进程位置(taskset)、cgroup、实时优先级 (chrt)、NUMA 配置 (numactl) 或通过使用低延迟内核来获得更好的延迟。请注意,香草 Redis 并不真正适合绑定在单一 CPU 内核上。Redis 可以 fork 后台任务,这些任务可能会消耗大量 CPU,例如 BGSAVEBGREWRITEAOF。这些任务绝不能在与主事件循环相同内核上运行。

在大多数情况下,不需要这种级别的系统优化。只有在需要并且熟悉这些优化时才进行。

Redis 的单线程性质

Redis 使用了一个几乎单线程的设计。这意味着一个单一进程使用称为多路复用的技术响应所有客户端请求。这意味着 Redis 可以随时响应一个请求,所以所有请求都按顺序响应。这与 Node.js 的工作方式非常相似。然而,这两个产品却经常被认为很慢。这是因为完成一个请求的时间很少,但主要是由于这些产品的设计不会阻塞系统调用,例如从套接字中读取数据或向套接字中写入数据。

我说 Redis 是几乎单线程,因为实际上从 Redis 2.4 开始,我们在 Redis 中使用线程在后台执行一些慢速 I/O 操作,这主要与磁盘 I/O 相关,但这并不影响 Redis 使用单线程响应所有请求的事实。

由慢速命令产生的延迟

单线程的后果是当一个请求响应缓慢时,所有其他客户端都将等待此请求得到响应。执行普通命令(如 GETSETLPUSH)时这完全不是问题,因为这些命令在恒定(并且很短)的时间内执行。然而,有些命令对许多元素进行操作,例如 SORTLREMSUNION 等。例如,取两个大集合的交集可能需要相当长的时间。

所有命令的算法复杂度都有说明。一个好的做法是在使用不熟悉的命令时系统地检查它。

如果您关心延迟问题,您要么不应该对由许多元素组成的值使用慢速命令,要么应该使用 Redis 复制来运行一个副本,所有慢速查询都可以在副本上运行。

您可以使用 Redis 慢日志功能来监视慢速命令。

此外,您可以使用您最喜欢的按进程监视程序(top、htop、prstat 等)来快速检查主 Redis 进程的 CPU 使用情况。如果在没有流量的情况下 CPU 使用情况很高,这通常表明使用了慢速命令。

重要提示:由慢速命令执行产生的延迟的一个非常常见的来源是在生产环境中使用 KEYS 命令。KEYS(如 Redis 文档中所述)只应该用于调试目的。从 Redis 2.8 版开始,引入了新的命令以对键空间和其他大集合进行增量迭代,如需了解更多信息,请查看 SCANSSCANHSCANZSCAN 命令。

由 fork 产生的延时

为了在后台生成 RDB 文件或在启用了 AOF 持久性的情况下重写 Append only 文件,Redis 必须 fork 后台进程。fork 操作(在主线程中运行)本身就能引起延时。

在大多数类似 Unix 的系统中,fork 都是一项开销很大的操作,因为它涉及复制大量与该进程关联的对象。尤其对于与虚拟内存机制关联的页表来说是这样。

例如,在 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 filed 获得的。

然而好消息是,新的 EC2 HVM 实例类型的 fork 时间要好很多,几乎与物理服务器持平,所以例如使用 m3.medium(或更高)实例将提供良好的结果。

  • Linux beefy VM on VMware 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 上的 Linux VM(KVM) 360 MB RSS fork 耗时 8.2 毫秒(每 GB 23.3 毫秒)。
  • EC2 上的 Linux VM,旧实例类型(Xen) 6.1GB RSS fork 耗时 1460 毫秒(每 GB 239.3 毫秒)。
  • EC2 上的 Linux VM,新实例类型(Xen) 1GB RSS fork 耗时 10 毫秒(每 GB 10 毫秒)。
  • Linode 上的 Linux VM(Xen) 0.9GBRSS fork 耗时 382 毫秒(每 GB 424 毫秒)。

正如您所看到的,运行在 Xen 上的某些 VM 性能受到了一到两个数量级的打击。对于 EC2 用户,建议很简单:使用基于现代 HVM 的实例。

由透明巨大页引起的延时

不幸的是,当 Linux 内核启用了透明大页面时,Redis 在使用 fork 调用持久化到磁盘后将产生较大的延迟损失。大页面是造成以下问题的根源

  1. 调用 Fork,创建具有共享大页面的两个进程。
  2. 在忙碌的实例中,几个事件循环的运行将导致命令针对数千页面,从而导致几乎整个进程内存的写入时复制。
  3. 这会导致较大的延迟和较大内存使用率。

确保使用以下命令禁用透明大页面

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 或更新版本)。此文件包含关于我们的进程内存映射的非常详细的信息,其中有一个称为交换的字段,正是我们正在寻找的信息。但不仅仅有一个单一的交换字段,因为 smaps 文件包含我们 Redis 进程的不同内存映射(一个进程的内存布局比简单的线性页面数组复杂)。

由于我们对我们进程交换的所有内存感兴趣,因此首先要对整个文件中的交换字段进行 grep

$ 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:                  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

对于我们的需要来说,输出中有趣的部分是两个列siso,它们计算从交换文件交换的内存量。如果您在这两列中看到非零计数,则您的系统中存在交换活动。

最后,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 文件时,配置如何影响延迟量和延迟源。

可以使用 appendfsync 配置选项,以三种不同的方式配置 AOF,使其在磁盘上执行 fsync(可以使用 CONFIG SET 命令在运行时修改此设置)。

  • 当 appendfsync 设置为 no 值时,Redis 不执行 fsync。在此配置中,延迟的唯一来源可能是 write(2)。发生这种情况时,通常没有解决办法,因为磁盘无法适应 Redis 接收数据的速度,但是,如果磁盘没有因执行 I/O 而严重变慢,这种情况并不常见。

  • 当 appendfsync 设置为 everysec 值时,Redis 每秒执行一次 fsync。它使用另一个线程,并且如果 fsync 仍在进行,Redis 将使用一个缓冲区,将 write(2) 调用延迟最多两秒(因为如果对同一文件正在进行 fsync,write将在 Linux 中被阻塞)。不过,如果 fsync 花费的时间过长,Redis 最终会执行 write(2) 调用,即使 fsync 仍在进行,而且这可能是导致延迟的来源。

  • 当 appendfsync 设置为 always 值时,在使用 OK 代码回复客户端之前,会在每次写操作中执行 fsync(实际上,Redis 会尝试将同时执行的多个命令集中到一次 fsync 中)。在这种模式下,通常情况下性能很低,强烈建议使用快速磁盘和能在短时间内执行 fsync 的文件系统实现。

大多数 Redis 用户会为 appendfsync 配置指令使用 noeverysec 设置。避免其他流程在同一系统中执行 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

但是由于 wirte(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% 以下。

需要此方法以避免向已过期的键使用太多内存,而且通常绝对无害,因为大量的键在同一秒完全过期是不常见的,但用户使用 EXPIREAT 与同一 Unix 时间大范围交互并非不可能。

简而言之:请注意,许多键在同一时刻过期可能是延迟的来源。

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:我们获得的跟踪越多,理解你的实例有什么问题就越容易。

RATE THIS PAGE
Back to top ↑