18 Redis 自身操作特性对性能的影响

  • 前言
  • 一、Redis 变慢的判定方法
  • 二、Redis 变慢的应对方法
  • 三、Redis 自身操作特性的影响
  • 总结



前言

Redis 突然变慢了不仅会直接影响用户的使用体验,还可能会影响和 Redis 在同一个业务系统中的其他系统,比如说数据库。

例如在秒杀场景下,Redis 变慢了,大量的用户下单请求就会被拖慢,用户提交了下单申请,却没有收到任何响应,给用户带来非常糟糕的使用体验,可能会导致用户流失。

实际生产环境中,Redis 往往是业务系统中的一个环节(例如作为缓存或是作为数据库)。 Redis 上的请求延迟增加,就可能引起业务系统中的一串儿“连锁反应”。

应用服务器(App Server)要完成一个事务性操作,包括在 MySQL 上执行一个写事务, 在 Redis 上插入一个标记位,并通过一个第三方服务给用户发送一条完成消息。

这三个操作都需要保证事务原子性,此时 Redis 的延迟增加,会拖累 App Server 端整个事务的执行。这个事务一直完成不了,会导致 MySQL 上写事务占用的资源无法释放,进而导致访问 MySQL 的其他请求被阻塞。

redis yuan cheng_缓存


前篇文章介绍了 Redis 变慢的潜在阻塞点的解决方案:异步线程机制和 CPU 绑核。但是还有一些因素会导致 Redis 变慢。

一、Redis 变慢的判定方法

查看 Redis 的响应延迟: 大部分时候,Redis 延迟很低,但是在某些时刻,有些 Redis 实例会出现很高的响应延 迟,甚至能达到几秒到十几秒,不过持续时间不长,这也叫延迟“毛刺”。发现 Redis 命令的执行时间突然就增长到了几秒,基本就可以认定 Redis 变慢了。

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

基于当前环境下的 Redis 基线性能做判断: 基线性能指的是一个系统在低压力、无干扰下的基本性能,只由当前的软硬件配置决定。

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

运行下面的命令打印 120 秒内监测到的最大延迟是 119 微秒,也就是基线性能为 119 微秒。运行 120 秒就足够监测到最大延迟了,把参数设置为 120。

./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 pe Worst run took 36x longer than the average latency

基线性能和当前的操作系统、硬件配置相关。把它和 Redis 运行时的延迟结合起来,进一步判断 Redis 性能是否变慢了。 把运行时延迟和基线性能进行对比,如果观察到的 Redis 运行时延迟是其基线性能的 2 倍及以上,就可以认定 Redis 变慢了。

虚拟化环境(例如虚拟机或容器)中,增加了虚拟化软件层,与物理机相比,虚拟机或容器本身就会引入一定的性能开销,所以基线性能会高一些。下面的测试结果,显示的 就是某一个虚拟机上运行 Redis 时测的基线性能。

$ ./redis-cli --intrinsic-latency 120 
Max latency so far: 692 microseconds. 
Max latency so far: 915 microseconds. 
Max latency so far: 2193 microseconds. 
Max latency so far: 9343 microseconds. 
Max latency so far: 9871 microseconds.

由于虚拟化软件本身的开销,基线性能已经达到了 9.871ms。如果该 Redis 实例的运行时延迟为 10ms,这并不能算作性能变慢,因为运行时延迟只比基线性能增加了 1.3%。如果不了解基线性能,一看到较高的运行时延迟,很有可能误判 Redis 变慢了。

通常是通过客户端和网络访问 Redis 服务,为了避免网络对基线性能的影响, 这个命令需要在服务器端直接运行,只考虑服务器端软硬件环境的影响。

如果想了解网络对 Redis 性能的影响,用 iPerf 这样的工具测量从 Redis 客户端到服务器端的网络延迟。延迟有几十毫秒甚至是几百毫秒,Redis 运行的网络环境中很可能有大流量的其他应用程序在运行,导致网络拥塞了。需要协调网络运维,调整网络的流量分配了。

二、Redis 变慢的应对方法

要基于对 Redis 本身的工作原理的理解,并且结合和它交互的操作系统、存储以及网络等外部系统关键机制,再借助一些辅助工具来定位原因,并制定行之有效的解决方案。

红色模块影响 Redis 性能的三大要素: Redis 自身的操作特性、文件系统和操作系统。

redis yuan cheng_redis yuan cheng_02

三、Redis 自身操作特性的影响

Redis 提供的键值对命令操作对延迟性能的影响。两类关键操作:慢查询命令和过期 key 操作

慢查询命令:指在 Redis 中执行速度慢的命令,导致 Redis 延迟增加。并不是所有命令都慢,这和命令操作的复杂度有关。

Redis 的不同命令的复杂度:Value 类型为 String 时,GET/SET 操作主要就是操作 Redis 的哈希表索引。操作复杂度基本是固定的 O(1)。 Value 类型为 Set 时,SORT、 SUNION/SMEMBERS 操作复杂度分别为 O(N+M*log(M)) 和 O(N)。N 为 Set 中 的元素个数,M 为 SORT 操作返回的元素个数。复杂度就增加了很多。

Redis 官方文档中对每个命令的复杂度都有介绍,需要了解某个命令的复杂度时,可以直接查询。 Redis 性能变慢时,通过 Redis 日志或者是 latency monitor 工具,查询变慢的请求,根据请求对应的具体命令以及官方文档,确认下是否采用了复杂度高的慢查询命令。

大量的慢查询命令的两种处理方式:

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

业务逻辑要求使用慢查询命令,得考虑采用性能更好的 CPU,更快地完成查询命令,避免慢查询的影响。

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

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

KEYS 命令需要遍历存储的键值对,所以操作延时高。不了解它的实现而使用了它,会导致 Redis 性能变慢。KEYS 命令一般不用于生产环境中。

过期 key 的自动删除机制: Redis 用来回收内存空间的常用机制,本身就会引起 Redis 操作阻塞,导致性能变慢,该机制对性能有影响。

Redis 键值对的 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 会被删除。对清除过期 key、释放内存空间很有帮助。每秒钟删除 200 个过期 key,并不会对 Redis 造成太大影响。

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

算法的第二条被触发的来源是频繁使用带有相同时间参数的 EXPIREAT 命令设置过期 key,会导致在同一秒内有大量的 key 同时过期。

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

要根据实际业务的使用需求,决定 EXPIREAT 和 EXPIRE 的过期时间参数。如果一批 key 的确是同时过期,可以在 EXPIREAT 和 EXPIRE 的过期时间参数上,加上一个一定大小范围内的随机数,既保证了 key 在一个邻近时间范围内被删除,又避免了同时过期造成的压力。

总结

Redis 性能变慢带来的重要影响。

判断 Redis 变慢的方法,一个是看响应延迟,一个是看基线性能。

两种排查和解决 Redis 变慢的方法:

  1. 从慢查询命令开始排查,并且根据业务需求替换慢查询命令;
  2. 排查过期 key 的时间设置,并根据实际使用需求,设置不同的过期时间。