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

2024-04-20 21:32

本文主要是介绍18 - 19 | 波动的响应延迟:如何应对变慢的Redis?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • Redis核心技术与实战
    • 实践篇
      • 18 - 19 | 波动的响应延迟:如何应对变慢的Redis?
        • Redis 真的变慢了吗?
          • 方法一:查看 Redis 的响应延迟。
          • 方法二:基于当前环境下的 Redis 基线性能做判断
        • 如何应对 Redis 变慢?
          • Redis 自身操作特性的影响
        • 文件系统:AOF 模式
        • 操作系统:swap
        • 操作系统:内存大页
      • 小结


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 内存大页机制被启用
关闭内存大页机制。

这篇关于18 - 19 | 波动的响应延迟:如何应对变慢的Redis?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/921370

相关文章

redis群集简单部署过程

《redis群集简单部署过程》文章介绍了Redis,一个高性能的键值存储系统,其支持多种数据结构和命令,它还讨论了Redis的服务器端架构、数据存储和获取、协议和命令、高可用性方案、缓存机制以及监控和... 目录Redis介绍1. 基本概念2. 服务器端3. 存储和获取数据4. 协议和命令5. 高可用性6.

Redis的数据过期策略和数据淘汰策略

《Redis的数据过期策略和数据淘汰策略》本文主要介绍了Redis的数据过期策略和数据淘汰策略,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录一、数据过期策略1、惰性删除2、定期删除二、数据淘汰策略1、数据淘汰策略概念2、8种数据淘汰策略

SpringBoot定制JSON响应数据的实现

《SpringBoot定制JSON响应数据的实现》本文主要介绍了SpringBoot定制JSON响应数据的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录前言一、如何使用@jsonView这个注解?二、应用场景三、实战案例注解方式编程方式总结 前言

Redis存储的列表分页和检索的实现方法

《Redis存储的列表分页和检索的实现方法》在Redis中,列表(List)是一种有序的数据结构,通常用于存储一系列元素,由于列表是有序的,可以通过索引来访问元素,因此可以很方便地实现分页和检索功能,... 目录一、Redis 列表的基本操作二、分页实现三、检索实现3.1 方法 1:客户端过滤3.2 方法

Python中操作Redis的常用方法小结

《Python中操作Redis的常用方法小结》这篇文章主要为大家详细介绍了Python中操作Redis的常用方法,文中的示例代码简洁易懂,具有一定的借鉴价值,有需要的小伙伴可以了解一下... 目录安装Redis开启、关闭Redisredis数据结构redis-cli操作安装redis-py数据库连接和释放增

redis防止短信恶意调用的实现

《redis防止短信恶意调用的实现》本文主要介绍了在场景登录或注册接口中使用短信验证码时遇到的恶意调用问题,并通过使用Redis分布式锁来解决,具有一定的参考价值,感兴趣的可以了解一下... 目录1.场景2.排查3.解决方案3.1 Redis锁实现3.2 方法调用1.场景登录或注册接口中,使用短信验证码场

Redis 多规则限流和防重复提交方案实现小结

《Redis多规则限流和防重复提交方案实现小结》本文主要介绍了Redis多规则限流和防重复提交方案实现小结,包括使用String结构和Zset结构来记录用户IP的访问次数,具有一定的参考价值,感兴趣... 目录一:使用 String 结构记录固定时间段内某用户 IP 访问某接口的次数二:使用 Zset 进行

解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)

《解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)》该文章介绍了使用Redis的阻塞队列和Stream流的消息队列来优化秒杀系统的方案,通过将秒杀流程拆分为两条流水线,使用Redi... 目录Redis秒杀优化方案(阻塞队列+Stream流的消息队列)什么是消息队列?消费者组的工作方式每

Redis如何使用zset处理排行榜和计数问题

《Redis如何使用zset处理排行榜和计数问题》Redis的ZSET数据结构非常适合处理排行榜和计数问题,它可以在高并发的点赞业务中高效地管理点赞的排名,并且由于ZSET的排序特性,可以轻松实现根据... 目录Redis使用zset处理排行榜和计数业务逻辑ZSET 数据结构优化高并发的点赞操作ZSET 结

Redis的Zset类型及相关命令详细讲解

《Redis的Zset类型及相关命令详细讲解》:本文主要介绍Redis的Zset类型及相关命令的相关资料,有序集合Zset是一种Redis数据结构,它类似于集合Set,但每个元素都有一个关联的分数... 目录Zset简介ZADDZCARDZCOUNTZRANGEZREVRANGEZRANGEBYSCOREZ