诊断延迟问题
查找慢响应的原因
Redis Open Source |
---|
本文档将帮助您了解在使用 Redis 时遇到延迟问题可能的原因。
在此上下文中,延迟是指客户端发出命令到客户端接收到命令回复之间的最大延迟。通常,Redis 的处理时间极低,处于亚微秒级,但在某些情况下会导致延迟更高。
时间有限,请给我核对清单
以下文档对于以低延迟方式运行 Redis 非常重要。但我理解大家都很忙,因此我们先从一个快速核对清单开始。如果您未能遵循这些步骤,请返回此处阅读完整文档。
- 确保您没有运行阻塞服务器的慢命令。使用 Redis 的慢日志功能 (Slow Log) 进行检查。
- 对于 EC2 用户,请确保使用基于 HVM 的现代 EC2 实例,例如 m3.medium。否则 fork() 会太慢。
- 必须在内核中禁用透明大页 (Transparent Huge Pages)。使用
echo never > /sys/kernel/mm/transparent_hugepage/enabled
命令禁用它们,并重启 Redis 进程。 - 如果您正在使用虚拟机,可能存在与 Redis 无关的内在延迟。使用
./redis-cli --intrinsic-latency 100
命令检查运行时环境的最小预期延迟。注意:需要在服务器上运行此命令,而不是客户端上。 - 启用和使用 Redis 的延迟监控 (Latency monitor) 功能,以获取 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 提供了延迟监控功能,能够采样不同的执行路径以了解服务器在哪里被阻塞。这使得调试本文档中说明的问题变得简单得多,因此我们建议尽快启用延迟监控。请参阅延迟监控文档 (Latency monitor documentation)。
虽然延迟监控的采样和报告功能将使您更容易理解 Redis 系统中延迟的来源,但仍建议您广泛阅读本文档,以便更好地理解 Redis 和延迟峰值这一主题。
延迟基线
有一种延迟是运行 Redis 的环境所固有的,即您的操作系统内核提供的延迟,如果您使用虚拟化,则是您所使用的虚拟机管理器 (hypervisor) 提供的延迟。
虽然这种延迟无法消除,但研究它很重要,因为它构成了基线,换句话说,您无法获得比环境中运行的每个进程由于内核或虚拟机管理器实现或设置而经历的延迟更低的 Redis 延迟。
我们将这种延迟称为内在延迟 (intrinsic latency),从 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 微秒),这是个好消息,但请记住,内在延迟可能会随系统负载的变化而改变。
虚拟化环境不会显示如此好的数据,尤其是在高负载或存在吵闹的邻居时。以下是在运行 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 微秒 (us),而使用 Unix 域套接字的延迟可以低至 30 微秒 (us)。这实际上取决于您的网络和系统硬件。除了通信本身,系统还会增加一些额外的延迟(由于线程调度、CPU 缓存、NUMA 放置等)。在虚拟化环境中,系统引起的延迟显着高于物理机。
其结果是,即使 Redis 在亚微秒范围内处理大多数命令,执行多次往返 (roundtrips) 到服务器的客户端也必须为这些网络和系统相关的延迟付出代价。
因此,高效的客户端将尝试通过将多个命令流水线 (pipelining) 处理来限制往返次数。服务器和大多数客户端都完全支持这一点。聚合命令(如 MSET/MGET)也可用于此目的。从 Redis 2.4 开始,许多命令还支持所有数据类型的变长参数 (variadic parameters)。
以下是一些指导原则
- 如果条件允许,优先选择物理机而非虚拟机来托管服务器。
- 不要系统地连接/断开与服务器的连接(对于基于 Web 的应用程序尤其如此)。保持您的连接尽可能长时间存活。
- 如果您的客户端与服务器位于同一主机上,请使用 Unix 域套接字。
- 优先使用聚合命令(MSET/MGET)或带变长参数的命令(如果可能),而不是流水线。
- 优先使用流水线(如果可能),而不是一系列的往返。
- Redis 支持 Lua 服务器端脚本 (server-side scripting),以应对不适合原始流水线处理的情况(例如,当一个命令的结果是后续命令的输入时)。
在 Linux 上,有些人可以通过调整进程放置 (taskset)、cgroups、实时优先级 (chrt)、NUMA 配置 (numactl) 或使用低延迟内核来获得更好的延迟。请注意,原版 (vanilla) Redis 并不真正适合绑定在单个 CPU 核心上。Redis 可以 fork 出后台任务,这些任务可能非常消耗 CPU,例如 BGSAVE
或 BGREWRITEAOF
。这些任务绝不能与主事件循环运行在同一个核心上。
在大多数情况下,不需要进行这类系统级优化。仅在您需要并熟悉它们时才进行。
Redis 的单线程特性
Redis 使用一种大部分单线程的设计。这意味着单个进程使用一种称为多路复用 (multiplexing) 的技术来处理所有客户端请求。这意味着 Redis 在任一给定时刻只能处理一个请求,因此所有请求都是按顺序处理的。这与 Node.js 的工作方式也非常相似。然而,这两个产品通常不被认为慢。这部分是由于完成单个请求所需的时间很短,但主要是因为这些产品被设计成不会阻塞在系统调用上,例如从套接字读取数据或向其写入数据。
我说 Redis 大部分是单线程的,是因为实际上从 Redis 2.4 开始,我们在 Redis 中使用线程在后台执行一些慢速 I/O 操作,主要与磁盘 I/O 有关,但这并不能改变 Redis 使用单个线程处理所有请求的事实。
慢命令产生的延迟
作为单线程的结果,当一个请求处理缓慢时,所有其他客户端都会等待此请求被处理。执行正常命令(例如 GET
、SET
或 LPUSH
)时,这根本不是问题,因为这些命令在常数(且非常小)时间内执行。但是,有些命令作用于许多元素,例如 SORT
、LREM
、SUNION
等。例如,对两个大集合取交集可能需要相当长的时间。
所有命令的算法复杂度都有文档说明。一个好的做法是,在使用不熟悉的命令时,系统地检查其复杂度。
如果您担心延迟问题,您要么不应使用针对由许多元素组成的值的慢命令,要么应使用 Redis 复制运行一个副本,并在该副本上运行所有慢查询。
可以使用 Redis 的慢日志功能 (Slow Log) 来监控慢命令。
此外,您可以使用您喜欢的进程级监控程序(如 top、htop、prstat 等)快速检查主 Redis 进程的 CPU 消耗。如果在流量不高时 CPU 消耗很高,这通常表明使用了慢命令。
重要提示:由执行慢命令产生的延迟的一个非常常见的来源是在生产环境中使用 KEYS
命令。KEYS
命令,正如 Redis 文档中所述,仅应用于调试目的。从 Redis 2.8 开始,引入了一系列新命令,以便增量迭代键空间和其他大型集合,请查看 SCAN
、SSCAN
、HSCAN
和 ZSCAN
命令以获取更多信息。
fork 产生的延迟
为了在后台生成 RDB 文件,或者在启用 AOF 持久化时重写追加文件 (Append Only File),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
字段获得的。
然而好消息是,基于 HVM 的新型 EC2 实例在 fork 时间方面要好得多,几乎与物理服务器持平,因此例如使用 m3.medium(或更好)的实例将提供良好的结果。
- VMware 上的强大 Linux 虚拟机 6.0GB RSS 在 77 毫秒内完成 fork(每 GB 12.8 毫秒)。
- 物理机上运行的 Linux(未知硬件) 6.1GB RSS 在 80 毫秒内完成 fork(每 GB 13.1 毫秒)
- 物理机上运行的 Linux (Xeon @ 2.27Ghz) 6.9GB RSS 在 62 毫秒内完成 fork(每 GB 9 毫秒)。
- 6sync (KVM) 上的 Linux 虚拟机 360 MB RSS 在 8.2 毫秒内完成 fork(每 GB 23.3 毫秒)。
- EC2 旧实例类型 (Xen) 上的 Linux 虚拟机 6.1GB RSS 在 1460 毫秒内完成 fork(每 GB 239.3 毫秒)。
- EC2 新实例类型 (Xen) 上的 Linux 虚拟机 1GB RSS 在 10 毫秒内完成 fork(每 GB 10 毫秒)。
- Linode (Xen) 上的 Linux 虚拟机 0.9GB RSS 在 382 毫秒内完成 fork(每 GB 424 毫秒)。
如您所见,某些运行在 Xen 上的虚拟机存在性能损失,量级在一到两个数量级之间。对于 EC2 用户来说,建议很简单:使用基于 HVM 的现代实例。
透明大页引起的延迟
不幸的是,当 Linux 内核启用了透明大页时,Redis 在使用 fork
调用进行磁盘持久化后会遭遇巨大的延迟惩罚。大页是导致以下问题的原因
- 调用 fork,创建了两个共享大页的进程。
- 在一个繁忙的实例中,几次事件循环运行就会导致命令操作数千个页,从而导致几乎整个进程内存的写时复制 (copy on write)。
- 这将导致巨大的延迟和内存使用。
请务必使用以下命令禁用透明大页
echo never > /sys/kernel/mm/transparent_hugepage/enabled
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-cli info | grep process_id
process_id:5454
首先要做的是检查 Redis 内存有多少被交换到磁盘上。为此,您需要获取 Redis 实例的 pid
$ cd /proc/5454
现在进入该进程的 /proc 文件系统目录
在这里,您会找到一个名为 smaps 的文件,它描述了 Redis 进程的内存布局(假设您使用的是 Linux 2.6.16 或更新版本)。该文件包含关于我们进程内存映射的非常详细的信息,其中一个名为 Swap 的字段正是我们正在寻找的。然而,由于 smaps 文件包含 Redis 进程的不同内存映射(进程的内存布局比简单的线性页数组更复杂),所以 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: 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
由于我们对进程交换到磁盘上的所有内存都感兴趣,首先要做的是在整个文件中 grep 查找 Swap 字段
$ 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
如果所有值都是 0 kB,或者只有零星的 4k 条目,那么一切都完全正常。实际上,在我们的示例实例(一个运行 Redis 并每秒为数百用户提供服务的真实网站)中,有一些条目显示了更多被交换的页。为了调查这是否是严重问题,我们修改了命令,以便也打印出内存映射的大小
从输出可以看出,有一个 720896 kB 的映射(其中只有 12 kB 被交换)以及另一个映射中额外的 156 kB 被交换:基本上,我们只有非常少量的内存被交换,所以这完全不会产生任何问题。
$ 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
如果进程内存中有相当数量的部分被交换到磁盘上,那么您的延迟问题很可能与交换有关。如果您的 Redis 实例出现这种情况,您可以使用 vmstat 命令进一步验证。
对我们来说,输出中有趣的部分是 si 和 so 这两列,它们统计了从/到交换文件的内存交换量。如果您在这两列中看到非零计数,那么您的系统中就存在交换活动。
$ 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
最后,可以使用 iostat 命令检查系统的全局 I/O 活动。
如果您的延迟问题是由于 Redis 内存被交换到磁盘上引起的,您需要降低系统中的内存压力,可以增加更多 RAM(如果 Redis 使用的内存超过可用内存),或者避免在同一系统中运行其他内存消耗大的进程。
由 AOF 和磁盘 I/O 引起的延迟
延迟的另一个来源是 Redis 对追加文件 (Append Only File) 的支持。AOF 基本上使用两个系统调用来完成其工作。一个是 write(2),用于将数据写入追加文件;另一个是 fdatasync(2),用于将内核文件缓冲区刷新到磁盘,以确保用户指定的持久性级别。
write(2) 和 fdatasync(2) 调用都可能成为延迟的来源。例如,write(2) 在系统范围内正在进行同步时或者在输出缓冲区已满且内核需要刷新到磁盘才能接受新写入时都可能阻塞。
fdatasync(2) 调用是一个更严重的延迟来源,因为在使用多种内核和文件系统组合时,它可能需要几毫秒到几秒钟才能完成,尤其是在有其他进程正在进行 I/O 的情况下。因此,从 Redis 2.4 开始,Redis 在可能的情况下会在不同的线程中执行 fdatasync(2) 调用。
我们将看到配置如何影响使用 AOF 文件时的延迟量和来源。
-
可以使用 appendfsync 配置选项(此设置可以在运行时使用 CONFIG SET 命令修改)将 AOF 配置为以三种不同方式在磁盘上执行 fsync。
-
当 appendfsync 设置为 no 时,Redis 不执行 fsync。在这种配置下,延迟的唯一来源可能是 write(2)。当发生这种情况时,通常没有解决方案,因为简单来说,磁盘无法跟上 Redis 接收数据的速度,但是,如果磁盘没有被其他进行 I/O 的进程严重拖慢,这种情况并不常见。
-
当 appendfsync 设置为 everysec 时,Redis 每秒执行一次 fsync。它使用不同的线程,如果 fsync 仍在进行中,Redis 会使用缓冲区将 write(2) 调用延迟最多两秒(因为如果在 Linux 上对同一文件进行 fsync,write 会阻塞)。然而,如果 fsync 花费太长时间,Redis 最终即使 fsync 仍在进行中也会执行 write(2) 调用,这可能成为延迟的来源。
当 appendfsync 设置为 always 时,在向客户端返回 OK 代码之前,每次写入操作都会执行 fsync(实际上 Redis 会尝试将同时执行的许多命令聚合成一个 fsync)。在此模式下,性能通常非常低,强烈建议使用快速磁盘和能够在短时间内执行 fsync 的文件系统实现。
大多数 Redis 用户会使用 appendfsync 配置指令的 no 或 everysec 设置。对于最低延迟的建议是避免在同一系统中运行其他进行 I/O 的进程。使用 SSD 磁盘也有帮助,但通常即使是非 SSD 磁盘在使用追加文件时表现也很好,如果磁盘空闲,因为 Redis 在写入追加文件时不会执行任何寻道操作。
sudo strace -p $(pidof redis-server) -T -e trace=fdatasync
如果您想调查与追加文件相关的延迟问题,可以在 Linux 下使用 strace 命令
上述命令将显示 Redis 在主线程中执行的所有 fdatasync(2) 系统调用。使用上述命令,当 appendfsync 配置选项设置为 everysec 时,您将看不到后台线程执行的 fdatasync 系统调用。为此,只需向 strace 添加 -f 开关即可。
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,并且该过程每秒执行十次,通常每秒只有 200 个键被主动删除。这足以快速清理数据库,即使长期未访问已过期的键,使惰性算法无法发挥作用。同时,每秒仅删除 200 个键对 Redis 实例的延迟没有影响。
然而,该算法是自适应的,如果发现采样键集中超过 25% 的键已过期,它将循环执行。但考虑到我们每秒运行该算法十次,这意味着随机样本中超过 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:我们获得的跟踪越多,就越容易理解您的实例出了什么问题。