如何应对变慢的Redis-波动的响应延迟

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
cd /proc/进程号id
cat smaps | egrep '^(Swap|Size)'

在这里插入图片描述

  • 每一行 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
  • 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 上。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值