如何排查Redis的延迟问题

在你使用Redis的过程中,有时可能会遇到延迟的问题,本文将会详述导致延迟的各项原因。

在本文中,延迟就是客户端请求执行一个命令和客户端收到命令执行结果之间的最大时延。通常,Redis的处理时间几乎可以忽略不计,基本在亚微秒的范围之内。但是,某些情况可能会导致较高的延迟时间。

1. 问题检查清单

以下内容非常重要,能够使得Redis以一种低延迟的方式运行。然而,你的工作可能非常繁忙,也许你想要从一个快速的检查清单开始解决问题。如果以下的检查步骤不能解决你的延迟问题,那么你还可以回来阅读全文。

(1)确保Redis没有执行速度较慢的命令,这些命令有可能会阻塞服务器。你可以使用Redis的慢查询日志功能排查这个问题,请参考《Redis的慢查询日志详解》。

(2)对于亚马逊EC2的用户来说,确保你使用的HVM是基于现在的EC2实例的,例如:m3.medium。否则,fork()系统调用的速度会非常慢。

(3)你的系统内核必须禁用透明巨页(THP)。你可以使用echo never > /sys/kernel/mm/transparent_hugepage/enabled命令禁用透明巨页,然后重启你的Redis进程。

(4)如果你正在使用一台虚拟机,那么可能会存在固有延迟,这种延迟与Redis毫无关系。你可以使用./redis-cli --intrinsic-latency 100命令,在你的运行时环境中检查你能够预期的最小延迟时间。注意:你需要在Redis服务端中运行这个命令,而不是在客户端中运行。

(5)你可以使用Redis的延迟监控子系统,这样便能获得易读的延迟时间和延迟原因的相关信息。请参考《Redis延迟监控框架详解》。

通常,你可以通过下面的列表,在持久性和延迟/性能之间作出权衡,这个列表按照更好的数据安全性至更好的延迟性进行排序。

  • AOF + 总是调用fsync
    这种方案的速度非常慢,应当只有在你知道自己要做什么的时候才能使用这个方案。

  • AOF + 每秒调用一次fsync
    这是一个很好的妥协方案。

  • AOF + 每秒调用一次fsync + 将no-appendfsync-on-rewrite 选项设为yes
    这个方案和上面的一样好,而且还能在重写AOF文件期间避免调用fsync操作,这样能够降低磁盘的I/O压力。

  • AOF + 从不调用fsync
    在这个方案中,fsync操作完全是由系统内核负责调用的。这个方案的磁盘压力较小,延迟飙升的风险也较小,但是数据安全性较差。

  • RDB
    这个方案取决于你配置的保存触发器,你可以在一个巨大的范围中作出权衡。

通过上述的检查清单,一般你只需要花费15分钟就可以排查出问题了。下文将会详述造成延迟的各种原因。

2. 测量延迟时间

如果你正在遭受延迟问题的困扰,你有可能想要知道如何在你的应用程序的背景下测量延迟时间,或者你遇到的延迟问题有可能在宏观上看也是非常明显的。然而,你可以使用redis-cli客户端来测量Redis服务器的延迟时间(单位为毫秒),只需要运行以下命令:

  1. redis-cli --latency -h <host> -p <port>

3. 使用Redis内部的延迟监控子系统

从Redis 2.8.13版本以来,Redis实现了延迟监控子系统,这个子系统能够对不同的执行路径进行采样,这样便能推测服务器在何处发生阻塞。这项功能大大简化了调试本文描述的延迟问题的复杂度,因此,强烈建议尽快启用延迟监控子系统。请参考《Redis延迟监控框架详解》。

虽然你可以利用延迟监控子系统的采样和上报功能更简单地推测Redis系统产生延迟问题的原因,但是仍然建议你仔细阅读本文,这样才能更好地理解Redis延迟飙升的相关技术细节。

4. 延迟基线

有一种类型的延迟是Redis的运行环境与生俱来的。通常,操作系统内核会产生这种延迟,如果你的Redis在虚拟化环境中运行,那么虚拟机管理器(hypervisor)也会产生这种延迟。

虽然这种延迟并不能被消除,但是对其展开研究却是非常重要的,因为它是Redis延迟的基线。换句话说,由于操作系统内核或虚拟机管理器的实现或配置的原因,你无论怎么对Redis延迟进行优化,都不可能低于上述的延迟基线,毕竟Redis运行环境中的每个进程都会遭受这种延迟。

这种类型的延迟被称为固有延迟,从Redis 2.8.7版本以来的redis-cli客户端能够测量这种延迟。以下示例在CentOS 6.6操作系统中运行,服务器是一台入门级的虚拟机。

注意:参数100表示测试执行的时间,单位为秒。测试运行的时间越长,就越有可能发现延迟飙升。100秒通常是合适的,然而你可能想要执行几次不同时间的测试。注意,这项测试是CPU密集型的,很有可能会导致单核心系统性能饱和。

Redis的固有延迟示例

注意:在这个特例中,应当在Redis的服务端主机中运行redis-cli程序,而不是在客户端主机中运行。在这个特殊模式中,redis-cli程序完全不会连接至任何Redis服务器,它只会尝试测量内核没有花费CPU时间来运行redis-cli进程本身的最大时间。

在上述示例中,Redis系统的固有延迟只有0.094毫秒(或94微秒),这是一个很好的结果!然而,请记住,固有延迟会随着时间而改变,取决于系统负载的情况。

虚拟化环境的延迟性能并不是很好,特别是在系统负载较高时,或者邻近的虚拟机抢占资源时。以下示例在一台VMware虚拟机实例中运行,它同时运行Redis服务和Apache服务:

Redis在高负载下的固有延迟

上述示例的固有延迟大约为7.6毫秒,这就意味着这个Redis系统的性能差强人意。然而,在虚拟化环境中运行更多次测试时,如果使用不同的测试时间、更高的系统负载、邻近的争抢资源的虚拟机,那么固有延迟的性能数据会更差。根据多次的测量结果,只要系统的固有延迟不大于40毫秒,Redis就能够正常提供服务。

5. 网络和通信导致的延迟

客户端会通过两种方式连接至Redis服务器:TCP/IP连接或Unix域连接。通常,1 Gbit/s带宽的网络的延迟时间大约为200μs,而Unix域套接字的延迟时间可以低至30μs。延迟时间实际上取决于你的网络和系统硬件。在网络通信之上,系统也会增加一些延迟时间(线程调度、CPU缓存、NUMA布局等,都会增加延迟)。在虚拟化环境中,系统导致的延迟时间明显高于在物理机中运行的系统。

这种延迟造成的结果便是,即使Redis处理大多数命令所耗费的时间在亚微秒的范围之内,当某个客户端正在和Redis服务器进行许多次来回通信时,这个客户端仍然会为这些网络和系统相关的延迟付出很多额外的性能开销。

因此,高效的客户端会尽量限制来回通信的次数,可以利用管道机制,将若干条命令一次性发送给Redis服务器。Redis服务器和大多数客户端完全支持上述的管道机制。类似于MSET/MGET的聚合命令也可以达到相似的效果。从Redis 2.4版本以来,很多命令还支持可变参数,适用于所有数据类型。

下面是一些指导方针:

  • 如果你可以接受较高的成本,那么尽量使用物理机来部署Redis服务器,而不是虚拟机。

  • 不要有计划地连接/断开Redis服务器(对于WEB应用程序尤其要注意)。尽可能长时间地保持连接不要断开。

  • 如果你的客户端和Redis服务器在相同的宿主机上,那么请使用Unix域套接字。

  • 如果可以的话,请尽量使用聚合命令(MSET/MGET),或者使用支持可变参数的命令,而不是使用管道机制。

  • 如果可以的话,请尽量使用管道机制,而不是需要来回通信的命令序列。

  • Redis服务器支持Lua脚本编程,在不支持原生管道机制的应用场景中,你可以使用Lua脚本(例如,当某条命令的执行结果是后续命令的一个输入参数时)。

在Linux系统中,有些人会优化进程布局(任务集)、cgroup、实时优先级(chrt)、NUMA配置(numactl),或者使用低延迟的系统内核,这些措施有可能获得更好的延迟性能。请注意,Redis实际上不适合绑定至单个CPU核心。Redis会调用fork操作来创建后台任务,类似于bgsave或AOF重写的操作会极大地消耗CPU性能。这些任务绝对不能和主事件循环(也就是Redis主进程)在相同的CPU核心上运行。

在大多数情况下,不需要进行这些类型的系统级优化。只有当你需要的时候才能进行优化,同时你也得非常熟悉这些优化方案。

6. Redis的单线程特性

通常,Redis是以单线程模式工作的。这就意味着,Redis只会使用一个进程处理所有客户端的请求,这种技术被称为多路复用。这就意味着,在每个给定的时刻,Redis只需要处理一个客户端请求。因此,所有的请求都是按照顺序依次处理的。这种设计非常类似于Node.js的工作原理。然而,这两种产品都具有非常快的运行速度。有一部分原因在于处理单个请求的时间非常短,但主要原因还在于这两种产品被设计为不会阻塞系统调用(例如,从socket读取数据或向socket写入数据)。

正如先前所述,Redis只是在大多数情况下以单线程模式工作。实际上,从Redis 2.4版本以来,Redis会使用多线程来执行某些较慢的后台I/O操作(主要是磁盘I/O操作),但是这并不能否定Redis使用单线程来处理所有客户端请求的事实。

7. 慢速命令导致的延迟

单线程模式有一个缺点,当某个请求处理较慢时,其他所有的客户端都必须等待这个请求处理完成。当执行普通命令(例如,GET、SET或LPUSH命令)时,这个缺点一点都不成问题,因为这些命令的执行时间都是常数,并且耗时非常短。然而,某些命令(例如,SORT、LREM或SUNION命令)会操作很多元素。例如,如果Redis要获得两个大集合的交集,那么耗费的时间会非常可观。

Redis的官方文档列出了所有命令的算法复杂度。如果你要使用某些不熟悉的命令,那么在使用之前,最好了解一下这些命令的技术细节。

如果你对延迟时间心存疑虑,那么你不应当使用速度较慢的命令来操作由很多元素构成的对象,你也不应当在运行较慢的查询操作时通过Redis复制功能进行数据备份。

通过Redis的慢查询日志功能,你可以监控运行速度较慢的命令。请参考《Redis的慢查询日志详解》。

另外,你可以使用你喜欢的监控程序(top、htop、prstat等等)来快速检查Redis主进程的CPU消耗程度。如果CPU消耗较高,而网络流量却较低,那么通常Redis很有可能正在执行速度较慢的命令。

重要提示:在生产环境中,执行KEYS命令是造成延迟的一个常见原因,这个命令的运行速度很慢。正如Redis官方文档所述,KEYS命令只应当在调试程序时使用。从Redis 2.8版本以来,Redis引入了一种新的命令类型,这些命令能够增量迭代键空间和其他的大型集合。请参考SCAN、SSCAN、HSCAN和ZSCAN命令的用法。

8. fork导致的延迟

为了在后台生成RDB文件,或者重写AOF文件(如果启用AOF持久化功能的话),Redis必须调用fork()操作来创建后台进程。fork()操作(在主线程中运行)本身就会产生延迟。

在类Unix系统中,fork()操作的性能开销比较大,因为它需要拷贝很多链接至进程的对象。对于虚拟内存机制相关的页表来说,这种性能开销显得尤为明显。

例如,在Linux/AMD64系统中,内存会被划分为4 kB大小的页。为了将虚拟地址转换为物理地址,每个进程都会存储一个页表(实际的表现形式为一棵树),这个页表至少会包含进程地址空间的每个内存页的一个指针。因此,如果某个Redis实例需要使用24 GB的内存,那么它需要24 GB / 4 kB * 8 = 48 MB的内存空间来存储相应的页表。

当某个Redis实例执行后台保存操作(BGSAVE)时,这个实例将会调用fork()操作,这项操作需要分配和拷贝48 MB的内存。fork()操作会耗费时间和CPU性能,尤其对于虚拟机来说,一个大内存块的分配和初始化会带来相当可观的开销。

9. 不同系统的fork时间

现代硬件拷贝页表的速度非常快,但是Xen却不行。Xen的问题并不是虚拟化特有的问题,而只是Xen特有的问题。例如,使用VMware或Virtual Box虚拟机时,调用fork()操作的速度也是非常快的。对于多个使用不同内存大小的Redis实例,以下列表会比较它们执行fork()操作所耗费的时间。在每个Redis实例中执行BGSAVE命令就能够获得各自耗费的时间,这个时间表现为INFO命令输出信息中的latest_fork_usec字段。

然而好消息是,如果你的实例是基于新型的EC2 HVM的,那么fork()操作耗费的时间会短得多,几乎和物理机相同。因此,如果你使用m3.medium(或更好的)实例,那么就不用担心fork()操作的性能。

  • Linux操作系统 + VMware虚拟机 + 6.0 GB的RSS内存:fork()操作耗费77毫秒(每GB耗费12.8毫秒)。

  • Linux操作系统 + 物理机(未知硬件) + 6.1 GB的RSS内存:fork()操作耗费80毫秒(每GB耗费13.1毫秒)。

  • Linux操作系统 + 物理机(Xeon@2.27GHz) + 6.9 GB的RSS内存:fork()操作耗费62毫秒(每GB耗费9毫秒)。

  • Linux操作系统 + 6sync虚拟机(KVM)+ 360 MB的RSS内存:fork()操作耗费8.2毫秒(每GB耗费23.3毫秒)。

  • Linux操作系统 + EC2虚拟机 + 旧实例类型(Xen)+ 6.1 GB的RSS内存:fork()操作耗费1460毫秒(每GB耗费239.3毫秒)。

  • Linux操作系统 + EC2虚拟机 + 新实例类型(Xen)+ 1 GB的RSS内存:fork()操作耗费10毫秒(每GB耗费10毫秒)。

  • Linux操作系统 + Linode实例(Xen)+ 0.9 GB的RSS内存:fork()操作耗费382毫秒(每GB耗费424毫秒)。

正如你看到的,通过Xen运行的某些虚拟机的性能有着1到2个数量级的差距。对于EC2用户来说,优化建议非常简单:使用基于新型HVM的实例就可以了。

10. 透明巨页(THP)导致的延迟

不幸的是,如果Linux内核启用透明巨页,那么当Redis调用fork()操作,以便于将数据持久化至磁盘时,Redis便会遇到很大的延迟问题。巨页是导致下列问题的原因:

(1)Redis调用fork()操作时,会创建两个共享巨页的进程。
(2)Redis的工作负载较重时,运行几次事件循环就会导致命令需要处理几千个内存页,这样便会造成Redis几乎要对整个进程内存空间执行写时拷贝(Copy On Write)操作。
(3)这将会导致较大的延迟时间和较高的内存使用率。

使用以下命令,确保操作系统禁用透明巨页

  1. echo never > /sys/kernel/mm/transparent_hugepage/enabled

11. 内存交换(操作系统分页)导致的延迟

为了能够更高效地使用系统内存,当Linux系统(以及很多其他的现代操作系统)将数据从内存交换至磁盘时,它能够重新分配内存页,反之亦然。

如果内核将Redis的某个内存页从内存交换至swap文件,那么当Redis需要使用存放在这个内存页中的数据时(例如,访问某个存放在这个内存页中的Key),为了将这个内存页移回内存之中,内核将会停止Redis进程的运行。上述操作的速度很慢(相比起访问已在内存之中的内存页来说),它牵涉到随机的I/O操作,这样会导致Redis客户端遭受异常的延迟时间。

内核会重新分配存放在磁盘上的Redis内存页,主要归结为以下三个原因:

  • 如果正在运行的进程请求更多的物理内存,而可用的内存总量却不能满足需求,那么系统便会遭受很大的内存压力。这个问题的最简单的例子就是Redis实际需要使用的内存总量多于服务器的可用内存总量。

  • 如果Redis实例的数据集,或者数据集的一部分,在大多数时候是完全空闲的(客户端从未访问),那么内核会将空闲的内存页交换至磁盘。这种问题非常少见,因为即使某个Redis具有中等水平的运行速度,它也会经常访问所有的内存页,这就会强迫Redis将所有的内存页保留在内存之中。

  • 在系统中,某些进程会产生大量的读取或写入的I/O操作。由于系统通常会缓存文件,这就很有可能迫使内核增加文件系统的缓存,因此就会产生内存交换活动。注意,Redis的RDB和/或AOF后台线程也存在上述行为,这两种持久化机制都会产生大型文件。

幸运的是,Linux操作系统有一个很好的工具能够排查这个问题。因此,当Redis发生延迟问题时,最简单的方法就是检查系统是否正在进行内存交换。

首先,需要查看交换至磁盘的Redis内存总量。为了这样做,你需要获得Redis实例的PID,如下图所示:

Redis的进程ID

现在,进入这个进程对应的/proc文件系统的目录,运行以下命令:

  1. cd /proc/1534

此处,你会发现一个名为smaps的文件,它会描述Redis进程的内存布局(假设你正在使用Linux 2.6.16或更新的版本)。这个文件包含系统进程的内存映射有关的非常详细的信息,其中有一个名为Swap的字段,它正是我们需要观察的字段。然而,smaps文件并不是只有Swap字段,因为这个文件会包含Redis进程的各种内存映射关系(相比起简单的内存页线性数组,进程的内存布局要复杂得多)。

因为我们对进程交换的所有内存都感兴趣,所以我们首先需要从smap文件抓取所有的Swap字段,如下图所示:

smaps文件的swap字段

如果每一行都是0 KB,或者如果只有零星的4k条目,那么表示系统运行一切正常。实际上,在我们示例的Redis实例中(这台服务器同时运行一个网站和一个Redis实例,每秒钟为上百个用户提供服务),只有少数几个条目表示发生内存页交换。为了排查这台服务器是否有严重的问题,我们修改了先前的命令,同时输出内存映射的大小,如下图所示:

smaps文件的swap和size字段

正如你可以从输出信息中看到的,最大的内存映射为1689600 kB,其中只有2000 kB被交换;第二大的内存映射为96844 kB,没有任何内存被交换:基本上只有很少量的内存被交换,因此根本不会产生任何问题。

如果有大量的进程内存被交换至磁盘,那么你遇到的延迟问题很有可能和内存交换相关。如果这是你的Redis实例发生延迟问题的原因,那么你还可以使用vmstat命令进一步验证延迟问题,如下图所示:

vmstat命令的输出信息

vmstat命令的输出信息中,我们只对siso列感兴趣,这两列分别表示从swap文件换出和从swap文件换入的内存总量。如果你在这两列看到非零值,那么就表示你的系统发生过内存交换活动。

最后,可以使用iostat命令检查系统的全局I/O活动,如下图所示:

iostat命令的输出信息

如果你的延迟问题是由于Redis内存的交换操作而导致的,那么你需要降低系统的内存压力:若Redis需要使用的内存总量多于可用的内存总量,则应当添加更多内存,或者避免在同一个系统中运行其他消耗内存较多的进程。

12. AOF和磁盘I/O导致的延迟

Redis的AOF持久化功能是导致延迟问题的另一个原因。基本上,AOF持久化会使用两个系统调用来实现它的功能。其中一个是write(2)系统调用,它会将数据写入AOF文件;另一个是fdatasync(2)系统调用,它会将内核文件缓冲的数据刷入磁盘,这样便能确保用户指定的持久性等级。

write(2)fdatasync(2)系统调用都可能导致延迟问题。例如,当正在执行涉及整个系统的数据同步(sync)时,或者当输出缓冲已满,内核需要将缓冲数据刷入磁盘,以便于接收新写入的数据时,write(2)系统内调用便会阻塞系统。

相比之下,fdatasync(2)系统调用更有可能导致延迟问题。当执行这个系统调用时,很多种内核和文件系统的组合方式都会花费几毫秒至几秒才能完成,当某些其他进程正在执行I/O操作时尤其如此。为了解决这个问题,从Redis 2.4版本以来,Redis会尽可能在一个不同的进程中执行fdatasync(2)系统调用。

经过多次修改AOF的相关配置,我们发现了AOF持久化是如何导致Redis产生延迟问题的。

通过appendfsync配置选项,你可以将AOF持久化配置为以三种不同的方式调用fsync操作,将数据同步至磁盘上(你可以在运行时修改这项配置,使用CONFIG SET命令):

  • appendfsync被设置为no时,Redis就不会执行fsync操作。在这种配置中,导致延迟问题的原因只有write(2)操作。当这种情况经常发生时,因为磁盘不能应对Redis接收数据时的速率问题,所以并没有什么解决方法。然而,如果磁盘没有被其他执行I/O操作的进程拖慢速率,那么这种情况并不会经常发生。

  • appendfsync被设置为everysec时,Redis会每秒钟执行一次fsync操作。Redis会使用一个不同的线程,如果正在执行fsync操作,那么Redis会使用缓冲来延迟write(2)操作的调用,最多延迟2秒钟(在Linux操作系统中,如果正在执行的fsync操作和write操作会处理相同的文件,那么write操作很有可能会发生阻塞)。然而,如果fsync操作消耗的时间过长,那么Redis最终还是会执行write(2)调用(即使fsync操作仍然未执行完成),这很有可能是导致延迟问题的原因。

  • appendfsync被设置为always时,Redis会在每次写入操作完成之后和向客户端回复OK结果代码之前,执行一次fsync操作(实际上,Redis会尝试将很多命令聚集在同一时刻执行,这样便只需要为这些命令执行一次fsync操作就可以了)。在这种模式中,Redis的性能通常会很慢,强烈建议使用高速磁盘(例如:SSD)和高速文件系统,它们可以在短时间内执行完成fsync-操作。

大多数的Redis用户会将appendfsync配置项设为noeverysec。为了将延迟降低至最低,建议避免在同一个系统中运行其他需要执行I/O操作的进程。使用SSD磁盘也可以降低延迟。但是,即便使用普通的机械磁盘,Redis的AOF持久化也可以获得较好的性能,只要确保Redis向AOF文件写入数据时,磁盘正处于空闲状态,那么就不用执行任何额外的寻道操作。

如果你想要排查AOF持久化相关的延迟问题,那么可以在Shell中运行strace命令:

  1. strace -p $(pidof redis-server) -T -e trace=fdatasync

上述命令将会显示在主线程中运行的Redis执行的所有fdatasync(2)系统调用。当appendfsync配置项被设置为everysec时,你不能通过上述命令查看后台线程执行的fdatasync系统调用。为了查看fdatasync系统调用,你只要为strace命令添加-f选项就可以了。

如果你想要同时查看fdatasyncwrite系统调用,那么可以使用以下命令:

  1. strace -p $(pidof redis-server) -T -e trace=fdatasync,write

然而,Redis还会使用write(2)系统调向客户端的socket写入数据,上述命令很有可能显示太多和磁盘I/O无关的信息。显然,没有任何方法可以告诉strace命令只显示速度较慢的系统调用,因此我会使用以下命令:

  1. strace -f -p $(pidof redis-server) -T -e trace=fdatasync,write 2>&1 | grep -v '0.0' | grep -v unfinished

13. 过期键导致的延迟

Redis会通过以下两种方式回收已过期的键:

  • 被动方式:当某个命令请求一个已过期的键时,会发现这个键已经过期了,然后便会执行回收操作。
  • 主动方式:每隔100毫秒执行一次回收操作。

Redis的主动过期方式是自适应的。Redis会每隔100毫秒执行一次过期循环(每秒10次),每次循环都会执行以下操作:

  • 采样ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP个键,回收所有已经过期的键。
  • 如果发现超过25%的键已经过期了,那么重复上述操作。

注意,ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP的值是在Redis源码的server.h文件中定义的。ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP的默认值为20,Redis每秒钟执行10次过期循环,通常每秒钟只有200个键会主动过期。即使很长时间都没有访问已经过期的键,主动方式仍然能够快速地清理Redis数据库,因此被动方式的算法并没有任何作用。同时,每秒钟仅仅回收200个已经过期的键并不会导致Redis实例发生延迟问题。

然而,主动方式的算法是自适应的,如果它发现键的采样集合之内有超过25%的键已经过期了,那么便会重复执行。但是,假设这个算法每秒钟运行10次,这就意味着随机的采样集合之内有超过25%的键几乎在同一时刻过期,这并不是一个好现象。

换句话说,如果Redis有很多很多的键几乎在同一时刻过期,并且这些键在使用SETEX命令设置的所有键之中的比率至少达到25%,那么为了使得上述比率降低至小于25%,Redis便有可能发生阻塞。

为了避免已经过期的键占用太多的内存空间,Redis有必要使用上述的主动方式。通常,这种方式不会产生任何问题,虽然大量的键在同一时刻过期是一个很奇怪的现象,但是用户在使用EXPIREAT命令时不大可能大范围地使用相同的Unix时间戳。

简而言之:请注意,如果有很多键在同一时刻过期,那么Redis就有可能发生延迟问题。

14. Redis的软件看门狗

Redis 2.6版本引入一个名为Redis软件看门狗的调试工具,它可以用来跟踪上述的各种延迟问题,你不必再使用其他工具来分析问题了。

软件看门狗是一个实验性的功能。虽然可以在生产环境中使用软件看门狗,但是在使用这项功能之前,你应当注意备份Redis数据库,因为这项功能和Redis服务器的正常操作之间可能会产生无法预料的相互作用。

只有当你通过其他所有方法都不能够跟踪延迟问题时,才能将软件看门狗作为排查问题的最后手段。

这项功能的工作流程如下所示:

  • 用户通过CONFIG SET命令启用软件看门狗功能。
  • Redis不断地监控自身。
  • 如果Redis发现服务器由于某个操作未能快速返回而导致阻塞,这个操作很有可能就是延迟问题的原因,软件看门狗便会在日志文件中生成关于服务器阻塞原因的底层报告。
  • 在Redis的谷歌讨论组中,用户可以向Redis的开发者发送求助信息,包含看门狗生成的延迟报告。

注意,你无法通过redis.conf文件启用这项功能,因为它本来就被设计为只有正在运行的Redis实例才能够启用,并且只能用作调试用途。

若要启用这项功能,则在shell中运行以下命令:

  1. redis-cli config set watchdog-period 500

周期时间的单位为毫秒。在上述示例中,看门狗只会在监测到Redis服务器发生500毫秒(或更长)的延迟时,才会将这个延迟问题记录至日志文件。能够配置的最小周期时间为200毫秒。

当你用完软件看门狗之后,你可以将watchdog-period参数设置为0,这样便能关闭这项功能。重要提示:用完软件看门狗之后切记要关闭这项功能,通常长时间启用不是一个好主意。

当软件看门狗监测到Redis服务器的延迟时间超过指定的阈值时,便会在日志文件中产生相应的信息,如下图所示:

看门狗的堆栈跟踪信息

注意:在示例中,为了阻塞服务器,我们会使用DEBUG SLEEP命令。如果服务器在不同的环境中发生阻塞,那么日志中的堆栈跟踪也就不同。

如果你收集到多个看门狗的堆栈跟踪,那么你最好将所有这些信息都发送至Redis的谷歌讨论组:Redis的开发者获得越多的跟踪信息,排查你的Redis实例遇到的延迟问题也就越简单。