Redis突然变慢了,如何排查呢
问题认定
如何判断Redis是不是真的变慢了?通过Redis的响应延迟
- 大部分时候,Redis延迟很低,某些时刻,Redis实例会出现几秒到十几秒延迟,基本就可以断定Redis变慢了
- 不同的软硬件环境下,Redis判断延迟的标准不同,所以需要通过当前环境下的Redis基线性能做判断
- Redis基线性能:一个系统在低压力、无干扰下的基本性能,这个性能只由当前的软硬件配置决定
- 如何确定基线性能:
redis-cli –intrinsic-latency 120
统计120s内的最大延迟作为基线性能 - 基线性能和当前的操作系统、硬件配置相关,一般Redis运行时延时是其基线性能的2倍以上,就可以认定Redis变慢了。
对于在虚拟化环境下运行的 Redis 来说,判断基线性能非常重要。在虚拟化环境(例如虚拟机或容器)中,由于增加了虚拟化软件层,与物理机相比,虚拟机或容器本身就会引入一定的性能开销,所以基线性能会高一些。
此时的基线性能达到了 9.871ms,如果该 Redis 实例的运行时延迟为 10ms,这并不能算作性能变慢,因为此时,运行时延迟只比基线性能增加了 1.3%。
- 为了避免网络对基线性能的影响, 这个命令需要在服务器端直接运行,只考虑服务器端软硬件环境的影响。
- 网络对 Redis 性能的影响,一个简单的方法是用 iPerf 这样的工具,测量从 Redis 客户端到服务器端的网络延迟。
- 如果这个延迟有几十毫秒甚至是几百毫秒,说明Redis 运行的网络环境中很可能有大流量的其他应用程序在运行,导致网络拥塞了
- 需要协调网络运维,调整网络的流量分配
应对方案
首先需要知道影响Redis性能的三大因素
- 自身的操作特性
- 文件系统
- 操作系统
Redis自身操作特性
慢查询命令
了解不同操作的复杂度,可以参考命令复杂度
通过 Redis 日志,或者是 latency monitor 工具,可以定位查询变慢的请求,根据请求对应的具体命令以及官方文档,确认下是否采用了复杂度高的慢查询命令。
如果有,处理方式如下
- 其他高效命令代替。比如返回Set里的所有成员,不用SMEMBERS,而用SSCAN多次迭代返回,避免一次返回大量数据造成线程阻塞
- 需要执行排序、交集、并集操作时,可以在客户端完成,以免拖慢Redis实例
- 如果业务逻辑就是要求使用慢查询命令,那你得考虑采用性能更好的 CPU,更快地 完成查询命令,避免慢查询的影响。
过期key操作
过期key会进行自动删除,是Redis用来回收内存空间的常用机制。算法如下
- 采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP (默认20)个数的 key,并将其中过期的 key 全部删除;
- 如果超过 25% 的 key 过期了,则重复删除的过程,直到过期 key 的比例降至 25% 以 下
正常情况下,一秒内基本有 200 个过期 key 会被删除,并不会对 Redis 造成太大影响,但是如果触发第二条,Redis就会一直删除以释放空间。
删除操作是阻塞的(Redis 4.0 后可以用异步线程机制来减少阻塞影响),一旦该条件触发,Redis 的线程就会一直执行删除,这样一来,就没办法正常服务其他的键值操作,就会进一步引起其他键值操作的延迟增加,Redis 就会变慢。
频繁使用带有相同时间参数的 EXPIREAT 命令设置过期 key,就会触发第二条规则。
可以在过期时间参数上,加上一定大小范围内的随机数,这样,既保证 key 在一个邻近时间范围内被删除,又避免了同时过期造成的压力。
文件系统
Redis 会持久化保存数据到磁盘,这个过程要依赖文件系统来完成,所以,文件系统将数 据写回磁盘的机制,会直接影响到 Redis 持久化的效率。在持久化的过程中,Redis 也还在接收其他请求,持久化的效率高低又会影响到 Redis 处理请求的性能。
AOF
AOF 日志提供了三种日志写回策略:no、everysec、always。这三种写回策略依赖文件系统的 两个系统调用 write 和 fsync。
- write 只要把日志记录写到内核缓冲区,就可以返回了,并不需要等待日志实际写回到磁盘
- fsync 需要把日志记录写回到磁盘后才能返回,时间较长。
下面这张表展示了三种 写回策略所执行的系统调用。
- everysec :Redis 允许丢失一秒的操作记录。Redis 主线程并不需要确保每个操作记录日志都写回磁盘。当写回策略配置为 everysec 时,Redis 会使用后台的子线程异步完成 fsync 的操作。
- always :Redis 需要确保每个操作记录日志都写回磁盘,always 策略使用主线程来执行fsync操作。
Redis使用子进程来进行AOF重写,重写贵对磁盘进行大量IO操作,fsync又要求等到数据写到磁盘后才能返回,所以AOF重写压力比较大的时候,会导致fsync阻塞
虽然fsync是由后台子线程负责执行的,但主线程会监控fsync的执行进度
- 当主线程使用后台子线程执行了一次 fsync,需要再次把新接收的操作记录写回磁盘时,如果主线程发现上一次的 fsync 还没有执行完,那么它就会阻塞。
- 如果后台子线程执行的 fsync 频繁阻塞的话(比如 AOF 重写占用了大量的磁盘 IO 带宽),主线程也会阻塞,导致 Redis 性能变慢。
由于 fsync 后台子线程和 AOF 重写子进程的存在,主 IO 线程一般不会被阻塞。但是,如果在重写日志时,AOF 重写子进程的写入量比较大, fsync 线程也会被阻塞,进而阻塞主线程,导致延迟增加。
解决方案
-
如果 AOF 写回策略使用了 everysec 或 always 配置,请先确认下业务方对数据可靠性的 要求,明确是否需要每一秒或每一个操作都记日志。
-
如果业务应用对延迟非常敏感,但同时允许一定量的数据丢失,那么,可以把配置项 no- appendfsync-on-rewrite 设置为 yes
- 这个配置项设置为 yes 时,表示在 AOF 重写时,不进行 fsync 操作。也就是说,Redis 实 例把写命令写到内存后,不调用后台线程进行 fsync 操作,就可以直接返回了。当然,如 果此时实例发生宕机,就会导致数据丢失。反之,
- 如果这个配置项设置为 no(也是默认配 置),在 AOF 重写时,Redis 实例仍然会调用后台线程进行 fsync 操作,这就会给实例带 来阻塞。
-
如果的确需要高性能,同时也需要高可靠数据保证,我建议你考虑采用高速的固态硬盘作 为 AOF 日志的写入设备。
操作系统
Swap
Redis 是内存数据库,内存使用量大,如果没有控制好内存的使用量,或者和其他内存需求大的应用一起运行了,操作系统会启动 swap 机制,而导致性能变慢。
内存 swap 是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制,涉及到磁盘的读写,一旦触发 swap,无论是被换入数据的进程,还是被换出数据的进程,其性能都会受到慢速磁盘读写的影响。
正常情况下,Redis 的操作是直接通过访问内存就能完成,一旦 swap 被触发了,Redis 的请求操作需要等到磁盘数据读写完成才行,swap 触发后影响的是 Redis 主 IO 线程,这会极大地增加 Redis 的响应时间。
触发 swap 的原因主要是物理机器内存不足,对于 Redis 而言,有两种常见的情况:
- Redis 实例自身使用了大量的内存,导致物理机器的可用内存不足;
- 和 Redis 实例在同一台机器上运行的其他进程,在进行大量的文件读写操作。文件读写本身会占用系统内存,这会导致分配给 Redis 实例的内存量变少,进而触发 Redis 发生 swap。
排查步骤
查看进程号
redis-cli info | grep process_id
- 1
cd /proc/进程号id
cat smaps | egrep '^(Swap|Size)'
- 1
- 2
- 每一行 Size 表示的是 Redis 实例所用的一块内存大小,而 Size 下方的 Swap 和它相对应,表示这块 Size 大小的内存区域有多少已经被换出到磁盘上了。如果这两个值相等,就 表示这块内存区域已经完全被换出到磁盘了。
- 作为内存数据库,Redis 本身会使用很多大小不一的内存块,所以,你可以看到有很多 Size 行,有的很小,就是 4KB,而有的很大,例如 462044KB。不同内存块被换出到磁盘 上的大小也不一样,例如刚刚的结果中的第一个 4KB 内存块,它下方的 Swap 也是 4KB, 这表示这个内存块已经被换出了;另外,462044KB 这个内存块也被换出了 462008KB, 差不多有 462MB。
这里有个重要的地方,我得提醒你一下,当出现百 MB,甚至 GB 级别的 swap 大小时, 就表明,此时,Redis 实例的内存压力很大,很有可能会变慢。所以,swap 的大小是排查 Redis 性能变慢是否由 swap 引起的重要指标。
解决方案
- 一旦发生内存 swap,最直接的解决方法就是增加机器内存。如果该实例在一个 Redis 切 片集群中,可以增加 Redis 集群的实例个数,来分摊每个实例服务的数据量,进而减少每 个实例所需的内存量。
- 当然,如果 Redis 实例和其他操作大量文件的程序(例如数据分析程序)共享机器,你可 以将 Redis 实例迁移到单独的机器上运行,以满足它的内存需求量。如果该实例正好是Redis 主从集群中的主库,而从库的内存很大,也可以考虑进行主从切换,把大内存的从 库变成主库,由它来处理客户端请求。
内存大页
内存大页机制(Transparent Huge Page, THP),也会影响 Redis 性能。
Linux 内核从 2.6.38 开始支持内存大页机制,该机制支持 2MB 大小的内存页分配,而常规的内存页分配是按 4KB 的粒度来执行的
系统的设计通常是一个取舍过程,我们称之为 trade-off。很多机制通常都是优势和劣势并存的。Redis 使用内存大页就是一个典型的例子
- 虽然内存大页可以给 Redis 内存减少分配次数,但是Redis 为了提供数据可靠性保证,需要将数据做持久化保存。这个写入过程由额外的线程执行,Redis 就会采用写时复制机制,也就是说,一旦有数据要被修 改,Redis 并不会直接修改内存中的数据,而是将这些数据拷贝一份,然后再进行修改。
- 如果采用了内存大页,那么客户端请求只修改 100B 的数据,Redis 也需要拷贝 2MB 的大页。
- 内存大页机制将导致大量的拷贝,这就会影响 Redis 正常的访存操作,最终导致性能变慢。
查看内存大页是否开启
cat /sys/kernel/mm/transparent_hugepage/enabled
- 1
- always:内存大页机制被启动
- never : 内存大页机制被禁止
很简单,关闭内存大页,就行了。
CheckList
- 获取 Redis 实例在当前环境下的基线性能。
- 是否用了慢查询命令?如果是的话,就使用其他命令替代慢查询命令,或者把聚合计算 命令放在客户端做。
- 是否对过期 key 设置了相同的过期时间?对于批量删除的 key,可以在每个 key 的过期 时间上加一个随机数,避免同时删除。
- 是否存在 bigkey? 对于 bigkey 的删除操作,如果你的 Redis 是 4.0 及以上的版本, 可以直接利用异步线程机制减少主线程阻塞;如果是 Redis 4.0 以前的版本,可以使用 SCAN 命令迭代删除;对于 bigkey 的集合查询和聚合操作,可以使用 SCAN 命令在客 户端完成。
- Redis AOF 配置级别是什么?业务层面是否的确需要这一可靠性级别?如果我们需要高 性能,同时也允许数据丢失,可以将配置项 no-appendfsync-on-rewrite 设置为 yes,避免 AOF 重写和 fsync 竞争磁盘 IO 资源,导致 Redis 延迟增加。当然, 如果既 需要高性能又需要高可靠性,最好使用高速固态盘作为 AOF 日志的写入盘。
- Redis 实例的内存使用是否过大?发生 swap 了吗?如果是的话,就增加机器内存,或 者是使用 Redis 集群,分摊单机 Redis 的键值对数量和内存压力。同时,要避免出现 Redis 和其他内存需求大的应用共享机器的情况。
- 在 Redis 实例的运行环境中,是否启用了透明大页机制?如果是的话,直接关闭内存大 页机制就行了。
- 是否运行了 Redis 主从集群?如果是的话,把主库实例的数据量大小控制在 2~4GB, 以免主从复制时,从库因加载大的 RDB 文件而阻塞。
- 是否使用了多核 CPU 或 NUMA 架构的机器运行 Redis 实例?使用多核 CPU 时,可以 给 Redis 实例绑定物理核;使用 NUMA 架构时,注意把 Redis 实例和网络中断处理程 序运行在同一个 CPU Socket 上。