发布时间:2022-10-03 20:30
大家好,今天我们来学习一下如何确定Redis是不是真的变慢了。
我们在使用redis时一定会遇到变慢的时候,那我们如何来判断Redis是否真的变慢了呢, 一个最直接的方法就是查看Redis的响应延迟,一般情况下,Redis延迟很低,但是在某些时刻, Redis实例会出现比较高的响应延迟,甚至能达到几秒到十几秒,当你发现Redis命令的执行时间突然就增长到了几秒,基本就可以认定 Redis 变慢了。这种方法是看 Redis 延迟的绝对值。当我们不能根据延迟的绝对值来判断redis是否真的变慢了,我们还有一种方法可以判断,那就是redis的基线性能。
redis从2.8.7版本开始,redis-cli命令提供了–-intrinsic-latency 选项,可以用来监测和统计测试期间内的最大延迟,这个延迟可以作为 Redis 的基线性能。其中,测试时长可以用–-intrinsic-latency 选项的参数来指定。比如我们执行redis-cli –-intrinsic-latency 30这个命令,该命令会打印出30秒内检测到的最大延迟。如下所示,这里的最大延迟是3595微妙,所以我们把该redis实例的基线性能是3595微妙。
./redis-cli –-intrinsic-latency 30
Max latency so far: 1 microseconds.
Max latency so far: 4 microseconds.
Max latency so far: 14 microseconds.
Max latency so far: 33 microseconds.
Max latency so far: 48 microseconds.
Max latency so far: 51 microseconds.
Max latency so far: 100 microseconds.
Max latency so far: 110 microseconds.
Max latency so far: 488 microseconds.
Max latency so far: 944 microseconds.
Max latency so far: 1590 microseconds.
Max latency so far: 1921 microseconds.
Max latency so far: 3595 microseconds.
584098163 total runs (avg latency: 0.0514 microseconds / 51.36 nanoseconds per run).
Worst run took 69994x longer than the average latency.
一般来说,当你观察到Redis运行时延迟是其基线性能的2倍及以上,就可以认定 Redis 变慢了。在确定Redis变慢之后,我们需要去进一步去排查Redis变慢的原因。
我们不能没有章法地去排查redis是如何变慢的,我们需要基于自己对Redis本身的工作原理的理解, 并且结合和它交互的操作系统、存储以及网络等外部系统关键机制,再借助一些辅助工具来定位原因,并制定有效的解决方案。 影响Redis性能的三大要素是Redis自身的操作特性、文件系统和操作系统。下面我们来分别看一下。
下面我们重点介绍两个常见的操作,这两个操作是经常导致redis变慢的罪魁祸首。
当redis变慢时,我们可以通过日志或者latency monitor工具来查看Redis中查询变慢的请求,然后根据请求对应的具体命令以及官方文档,确认下是否采用了复杂度高的慢查询命令。如果的确有大量的慢查询命令,有两种处理方式:
我们可以对edis的键值对设置过期时间。默认情况下,Redis每100毫秒会删除一些过期key,具体的算法如下:
如果业务上确实需要一批key同时过期,我们可以在EXPIREAT 和 EXPIRE 的过期时间参数上 加上一个一定大小范围内的随机数,这样,既可以保证key 在一个临近时间范围内被删除,又避免了同时过期造成的压力。
Redis会采用AOF日志或者RDB来保证数据的可靠性。其中AOF日志提供了三种日志写回策略: no、everysec、always。这三种写回策略依赖文件系统的两个系统调用,也就是write和fsync。write 只要把日志记录写到内核缓冲区,就可以返回了,并不需要等待日志实际写回到磁盘;而fsync 需要把日志记录写回到磁盘后才能返回,时间较长。下面这张表展示了三种写回策略所执行的系统调用:
当AOF配置成everysec时,Redis允许丢失一秒的操作记录,所以Redis主线程不需要确保每个操作记录日志都写回到磁盘。所以,当配置为everysec时,Redis是使用后台的子线程异步完成fsync的操作。当AOF配置成always时,Redis需要确保每个操作记录日志都写回磁盘,如果采用后台子线程异步完成,主线程就无法及时地知道每个操作是否已经完成了。这就不符合 always 策略的要求了。所以,always 策略并不使用后台子线程来执行。
我们在这里知道,Redis持久化机制在使用 AOF 日志时,为了避免日AOF志文件变得太大,Redis 会采用后台子进程来进行AOF重写。但是,这里会出现一个风险点,AOF重写会对磁盘进行大量的IO操作,同时fsync需要等到数据写入到磁盘后才会返回。所以,当AOF重新操作磁盘压力比较大时,就会导致fsync被阻塞。尽管fsync是由后台子线程负责执行的,但是,主线程会监控fsync的执行进度。当主线程使用后台子线程执行了一次 fsync,需要再次把新接收的操作记录写回磁盘时,如果主线程发现上一次的 fsync 还没有执行完,那么它就会阻塞。所以,如果后台子线程执行的 fsync 频繁阻塞的话主线程也会阻塞,导致 Redis 性能变慢。
到目前为止,你已经知道了,当AOF重写导致磁盘压力大时,就会导致fsync阻塞,进而阻塞主线程 ,导致延迟增加。下面我们来看如何排查和解决这个问题。首先,我们可以先检查配置文件中的 appendfsync配置项,查看AOF的写入策略是什么样的。
如果我们的业务方对Redis的延迟很敏感,但是可以允许有一定数据的数据丢失,我们可以设置no-appendfsync-on-rewrite为yes
no-appendfsync-on-rewrite yes
这个配置项设置为 yes 时,表示在 AOF 重写时,不进行 fsync 操作。也就是说,Redis 实例把写命令写到内存后,不调用后台线程进行 fsync 操作,就可以直接返回了。当然,如果此时实例发生宕机,就会导致数据丢失。反之,如果这个配置项设置为 no(默认配置),在 AOF 重写时,Redis 实例仍然会调用后台线程进行 fsync 操作,这就会给实例带来阻塞。如果业务方既需要低延迟也需要高可靠性,我们可以采用固态硬盘作为AOF日志的写入设备。
内存 swap 是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制,会涉及到磁盘的读写,所以,一旦触发 swap,其性能都会受到慢速磁盘读写的影响。
Redis是内存数据库,如果没有控制好内存的使用量,就可能会引起操作系统的swap。一旦swap被触发了,Redis的读写操作就会有可能请求到磁盘,从而影响Redis的主线程执行,所以会增大Redis的响应时间。
操作系统触发swap机制,主要是因为物理机的内存不足导致的,所以,当发现操作系统进行了内存swap,最直接的处理方式就是增加物理机的内存大小。
我们先来看看什么是内存大页?我们都知道,应用程序向操作系统申请内存时,是按内存页进行申请的,而常规的内存页大小是 4KB。Linux 内核从 2.6.38 开始,支持了内存大页机制,该机制允许应用程序以 2MB 大小为单位,向操作系统申请内存。 我们在Redis持久化机制里知道,Redis的持久化是采用写时复制的,也就是说,一旦有数据要被修改,Redis 并不会直接修改内存中的数据,而是将这些数据拷贝一份,然后再进行修改。
如果采用了内存大页,那么,即使客户端请求只修改 1kb 的数据,Redis 也需要拷贝 2MB 的大页。相反,如果是常规内存页机制,只用拷贝 4KB。两者相比,你可以看到,当客户端请求修改或新写入数据较多时,内存大页机制将导致大量的拷贝,这就会影响 Redis 正常的访存操作,最终导致性能变慢。解决这个问题的方式,就是关闭大页机制。
我们在linux执行
cat /sys/kernel/mm/transparent_hugepage/enabled
如果是always,就表明启动了内存大页机制,我们执行
echo never /sys/kernel/mm/transparent_hugepage/enabled
来关闭内存大页机制就好了。