难道 Redis 真的变慢了吗?
大家好,今天我们来学习一下如何确定 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 变慢的罪魁祸首。
1.慢查询命令
当 redis 变慢时,我们可以通过日志或者 latency monitor 工具来查看 Redis 中查询变慢的请求,然后根据请求对应的具体命令以及官方文档,确认下是否采用了复杂度高的慢查询命令。如果的确有大量的慢查询命令,有两种处理方式:
用其它高效命令代替。
当你需要执行排序、交集、并集操作时,可以在客户端完成,而不要用 SORT、SUNION、SINTER 这些命令,以免拖慢 Redis 实例。
2.过期 key 操作
我们可以对 edis 的键值对设置过期时间。默认情况下,Redis 每 100 毫秒会删除一些过期 key,具体的算法如下:
采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 个数的 key,并将其中过期的 key 全部删除;
如果超过 25% 的 key 过期了,则重复删除的过程,直到过期 key 的比例降至 25% 以下。如果我们同一时间有大量过期的 key 要删除,就会触发第二条策略,Redis 就会一直删除以释放内存空间。注意,删除操作是阻塞的(Redis 4.0 后可以用异步线程机制来减少阻塞影响)。所以,一旦该条件触发,Redis 的线程就会一直执行删除,这样一来,就没办法正常服务其他的键值操作了,就会进一步引起其他键值操作的延迟增加,Redis 就会变慢。所以我们需要避免给一批 key 设置相同的过期时间,
如果业务上确实需要一批 key 同时过期,我们可以在 EXPIREAT 和 EXPIRE 的过期时间参数上 加上一个一定大小范围内的随机数,这样,既可以保证 key 在一个临近时间范围内被删除,又避免了同时过期造成的压力。
文件系统:AOF 日志
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 日志的写入设备。
操作系统:
1.内存 swap。
内存 swap 是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制,会涉及到磁盘的读写,所以,一旦触发 swap,其性能都会受到慢速磁盘读写的影响。
Redis 是内存数据库,如果没有控制好内存的使用量,就可能会引起操作系统的 swap。一旦 swap 被触发了,Redis 的读写操作就会有可能请求到磁盘,从而影响 Redis 的主线程执行,所以会增大 Redis 的响应时间。
操作系统触发 swap 机制,主要是因为物理机的内存不足导致的,所以,当发现操作系统进行了内存 swap,最直接的处理方式就是增加物理机的内存大小。
2.内存大页机制。
我们先来看看什么是内存大页?我们都知道,应用程序向操作系统申请内存时,是按内存页进行申请的,而常规的内存页大小是 4KB。Linux 内核从 2.6.38 开始,支持了内存大页机制,该机制允许应用程序以 2MB 大小为单位,向操作系统申请内存。 我们在 Redis 持久化机制里知道,Redis 的持久化是采用写时复制的,也就是说,一旦有数据要被修改,Redis 并不会直接修改内存中的数据,而是将这些数据拷贝一份,然后再进行修改。
如果采用了内存大页,那么,即使客户端请求只修改 1kb 的数据,Redis 也需要拷贝 2MB 的大页。相反,如果是常规内存页机制,只用拷贝 4KB。两者相比,你可以看到,当客户端请求修改或新写入数据较多时,内存大页机制将导致大量的拷贝,这就会影响 Redis 正常的访存操作,最终导致性能变慢。解决这个问题的方式,就是关闭大页机制。
评论