18 - 19 | 波动的响应延迟:如何应对变慢的Redis?


Redis核心技术与实战

实践篇

18 - 19 | 波动的响应延迟:如何应对变慢的Redis?

Redis 真的变慢了吗?
方法一:查看 Redis 的响应延迟。

Redis 命令的执行时间突然就增长到了几秒,基本就可以认定 Redis 变慢了。

这种方法是看 Redis 延迟的绝对值,但是,在不同的软硬件环境下,Redis 本身的绝对性能并不相同。比如,在环境 A 中,当延迟为 1ms 时,判定 Redis 变慢了,但是环境 B 硬件配置高,那么,在环境 B 下,可能延迟是 0.2ms 的时候,就可以认定 Redis 变慢了。

方法二:基于当前环境下的 Redis 基线性能做判断

所谓的基线性能,就是指一个系统在低压力、无干扰下的基本性能,这个性能只由当前的软硬件配置决定。

从 2.8.7 版本开始,redis-cli 命令提供了 –intrinsic-latency 选项,可以用来监测和统计测试期间内的最大延迟,这个延迟可以作为 Redis 的基线性能。

./redis-cli --intrinsic-latency 120
Max latency so far: 17 microseconds.
Max latency so far: 44 microseconds.
Max latency so far: 94 microseconds.
Max latency so far: 110 microseconds.
Max latency so far: 119 microseconds.

36481658 total runs (avg latency: 3.2893 microseconds / 3289.32 nanoseconds per run).
Worst run took 36x longer than the average latency.

如果 Redis 运行时延迟是其基线性能的 2 倍及以上,就可以认定 Redis 变慢了。

在虚拟化环境(例如虚拟机或容器)中,由于增加了虚拟化软件层,与物理机相比,虚拟机或容器本身就会引入一定的性能开销,所以基线性能会高一些。

如何应对 Redis 变慢?

Redis 自身的操作特性、文件系统和操作系统,它们是影响 Redis 性能的三大要素。
在这里插入图片描述

Redis 自身操作特性的影响

1. 慢查询命令

当 Redis 性能变慢时,可以通过 Redis 日志(slowlog),或者是 latency monitor 工具,查询变慢的请求,根据请求对应的具体命令以及官方文档,确认下是否采用了复杂度高的慢查询命令。

如果的确有大量的慢查询命令,有两种处理方式:

  • 用其他高效命令代替。比如说,如果需要返回一个 SET 中的所有成员时,不要使用 SMEMBERS 命令,而是要使用 SSCAN 多次迭代返回,避免一次返回大量数据,造成线程阻塞。
  • 当需要执行排序、交集、并集操作时,可以在客户端完成,而不要用 SORT、SUNION、SINTER 这些命令,以免拖慢 Redis 实例。

易忽略的慢查询命令 KEYS。它用于返回和输入模式匹配的所有 key,例如,以下命令返回所有包含“name”字符串的 keys。

redis> KEYS *name*
1) "lastname"
2) "firstname"

因为 KEYS 命令需要遍历存储的键值对,所以操作延时高。所以,KEYS 命令一般不被建议用于生产环境中。

2. 过期 key 操作

默认情况下,Redis 每 100 毫秒(即每秒十次)会删除一些过期 key,具体的算法如下:

  1. 采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 个数的 key,并将其中过期的 key 全部删除;
  2. 如果超过 25% 的 key 过期了,则重复删除的过程,直到过期 key 的比例降至 25% 以下。

ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 是 Redis 的一个参数,默认是 20,那么,一秒内基本有 200 个过期 key 会被删除(200 = 1000 / 100 * 20,每秒执行10次,每次20个,共200)。

如果触发了上面这个算法的第二条,Redis 就会一直删除以释放内存空间。注意,删除操作是阻塞的(Redis 4.0 后可以用异步线程机制来减少阻塞影响)。所以,一旦该条件触发,Redis 的线程就会一直执行删除,这样一来,就没办法正常服务其他的键值操作,就会进一步引起其他键值操作的延迟增加,Redis 就会变慢。

算法的第二条是怎么被触发的?

频繁使用带有相同时间参数的 EXPIREAT(作用和 EXPIRE 类似,都用于为 key 设置生存时间,
不同的地方在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳) 命令设置过期 key,这就会导致,在同一秒内有大量的 key 同时过期。

检查业务代码在使用 EXPIREAT 命令设置 key 过期时间时,是否使用了相同的 UNIX 时间戳,有没有使用 EXPIRE 命令给批量的 key 设置相同的过期秒数。 因为,这都会造成大量 key 在同一时间过期,导致性能变慢。

如果一批 key 的确是同时过期,可以在 EXPIREAT 和 EXPIRE 的过期时间参数上,加上一个一定大小范围内的随机数,这样,既保证了 key 在一个邻近时间范围内被删除,又避免了同时过期造成的压力。

文件系统:AOF 模式

为了保证数据可靠性,Redis 会采用 AOF 日志或 RDB 快照。其中,AOF 日志提供了三种日志写回策略:no、everysec、always。这三种写回策略依赖文件系统的两个系统调用完成,也就是 write 和 fsync。

write 只要把日志记录写到内存缓冲区,就可以返回,并不需要等待日志实际写回到磁盘;而 fsync 需要把日志记录写回到磁盘后才能返回,时间较长。

在这里插入图片描述

当写回策略配置为 everysec 和 always 时,Redis 需要调用 fsync 把日志写回磁盘。

在使用 everysec 时,Redis 允许丢失一秒的操作记录,所以,Redis 主线程并不需要确保每个操作记录日志都写回磁盘。而且,fsync 的执行时间很长,如果是在 Redis 主线程中执行 fsync,就容易阻塞主线程。

当写回策略配置为 everysec 时,Redis 会使用后台的子线程异步完成 fsync 的操作。

对于 always 策略来说,Redis 需要确保每个操作记录日志都写回磁盘,如果用后台子线程异步完成,主线程就无法及时地知道每个操作是否已经完成,不符合 always 策略的要求。

always 策略并不使用后台子线程来执行。

在使用 AOF 日志时,为了避免日志文件不断增大,Redis 会执行 AOF 重写,生成体量缩小的新的 AOF 日志文件。AOF 重写本身需要的时间很长,也容易阻塞 Redis 主线程,所以,Redis 使用子进程来进行 AOF 重写。

潜在的风险点:AOF 重写会对磁盘进行大量 IO 操作,同时,fsync 又需要等到数据写到磁盘后才能返回,所以,当 AOF 重写的压力比较大时,就会导致 fsync 被阻塞。虽然 fsync 是由后台子线程负责执行的,但是,主线程会监控 fsync 的执行进度。

当主线程使用后台子线程执行了一次 fsync,需要再次把新接收的操作记录写回磁盘时,如果主线程发现上一次的 fsync 还没有执行完,那么它就会阻塞。

如果后台子线程执行的 fsync 频繁阻塞(比如 AOF 重写占用了大量的磁盘 IO 带宽),主线程也会阻塞,导致 Redis 性能变慢。

在这里插入图片描述

排查和解决建议

如果业务应用对延迟非常敏感,但同时允许一定量的数据丢失,那么,可以把配置项 no-appendfsync-on-rewrite 设置为 yes,如下所示:

no-appendfsync-on-rewrite yes

no-appendfsync-on-rewrite 设置为 yes 时,表示在 AOF 重写时,不进行 fsync 操作。 也就是说,Redis 实例把写命令写到内存后,不调用后台线程进行 fsync 操作,就可以直接返回。当然,如果此时实例发生宕机,就会导致数据丢失。反之,如果 no-appendfsync-on-rewrite 设置为 no(默认配置),在 AOF 重写时,Redis 实例仍然会调用后台线程进行 fsync 操作。

如果的确需要高性能,同时也需要高可靠数据保证,建议采用高速的固态硬盘作为 AOF 日志的写入设备。

操作系统:swap

内存 swap 是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制,涉及到磁盘的读写。 所以,一旦触发 swap,无论是被换入数据的进程,还是被换出数据的进程,其性能都会受到慢速磁盘读写的影响。

Redis 是内存数据库,内存使用量大,如果没有控制好内存的使用量,或者和其他内存需求大的应用一起运行,就可能受到 swap 的影响,而导致性能变慢。

正常情况下,Redis 的操作是直接通过访问内存就能完成,一旦 swap 被触发,Redis 的请求操作需要等到磁盘数据读写完成才行。 swap 触发后影响的是 Redis 主 IO 线程,这会极大地增加 Redis 的响应时间。

通常,触发 swap 的原因主要是物理机器内存不足,对于 Redis 而言,有两种常见的情况:

  1. Redis 实例自身使用了大量的内存,导致物理机器的可用内存不足;
  2. 和 Redis 实例在同一台机器上运行的其他进程,在进行大量的文件读写操作。文件读写本身会占用系统内存,这会导致分配给 Redis 实例的内存量变少,进而触发 Redis 发生 swap。

解决思路:增加机器的内存或者使用 Redis 集群(分摊每个实例服务的数据量,进而减少每个实例所需的内存量)。

查看 swap 使用情况

  1. 操作系统本身会在后台记录每个进程的 swap 使用情况,即有多少数据量发生了 swap。可以先通过下面的命令查看 Redis 的进程号:
$ redis-cli info | grep process_id
process_id: 5332
  1. 然后,进入 Redis 所在机器的 /proc 目录下的该进程目录中:
$ cd /proc/5332
  1. 最后,运行 cat smaps | egrep ‘^(Swap|Size)’ 命令,查看该 Redis 进程的使用情况:
$cat smaps | egrep '^(Swap|Size)'
Size: 584 kB
Swap: 0 kB
Size: 4 kB
Swap: 4 kB
Size: 4 kB
Swap: 0 kB
Size: 462044 kB
Swap: 462008 kB
Size: 21392 kB
Swap: 0 kB

每一行 Size 表示的是 Redis 实例所用的一块内存大小,而 Size 下方的 Swap 与之对应,表示这块 Size 大小的内存区域有多少已经被换出到磁盘上。如果这两个值相等,就表示这块内存区域已经完全被换出到磁盘。

注意:当出现百 MB,甚至 GB 级别的 swap 大小时,就表明,此时,Redis 实例的内存压力很大,很有可能会变慢。所以,swap 的大小是排查 Redis 性能变慢是否由 swap 引起的重要指标。

操作系统:内存大页

Linux 内核从 2.6.38 开始支持内存大页机制,该机制支持 2MB 大小的内存页分配,而常规的内存页分配是按 4KB 的粒度来执行的。

虽然内存大页可以给 Redis 带来内存分配方面的收益,但是,当Redis 在持久化数据时,客户端想要修改正在进行持久化的数据,Redis 就会采用写时复制机制。如果采用内存大页,那么,即使客户端请求只修改 100B 的数据,Redis 也需要拷贝 2MB 的大页。相反,如果是常规内存页机制,只用拷贝 4KB。

当客户端请求修改或新写入数据较多时,内存大页机制将导致大量的拷贝,这就会影响 Redis 正常的访存操作,最终导致性能变慢。

解决方案:关闭内存大页。

cat /sys/kernel/mm/transparent_hugepage/enabled

如果执行结果是 always,就表明内存大页机制被启动;如果是 never,就表示内存大页机制被禁止。

在实际生产环境中部署时,建议不要使用内存大页机制,只需要执行下面的命令即可:

echo never /sys/kernel/mm/transparent_hugepage/enabled

小结

Redis(4.0 以后版本) 变慢的原因及应对:

(1)慢查询命令
用其他高效命令代替或聚合操作在客户端完成。

(2)AOF 重写阻塞 fsync
no-appendfsync-on-rewrite 设置为 yes 。

(3)swap 被触发
增加机器的内存或者使用 Redis 集群。

(4)Linux 内存大页机制被启用
关闭内存大页机制。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久违の欢喜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值