CategoryResourceRepost/极客时间专栏/Redis核心技术与实战/实践篇/18 | 波动的响应延迟:如何应对变慢的Redis?(上).md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

163 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="18 | 波动的响应延迟如何应对变慢的Redis" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b0/51/b09fe89d84d83719596a2ab088477a51.mp3"></audio>
你好,我是蒋德钧。
在Redis的实际部署应用中有一个非常严重的问题那就是Redis突然变慢了。一旦出现这个问题不仅会直接影响用户的使用体验还可能会影响到“旁人”也就是和Redis在同一个业务系统中的其他系统比如说数据库。
举个小例子在秒杀场景下一旦Redis变慢了大量的用户下单请求就会被拖慢也就是说用户提交了下单申请却没有收到任何响应这会给用户带来非常糟糕的使用体验甚至可能会导致用户流失。
而且在实际生产环境中Redis往往是业务系统中的一个环节例如作为缓存或是作为数据库。一旦Redis上的请求延迟增加就可能引起业务系统中的一串儿“连锁反应”。
我借助一个包含了Redis的业务逻辑的小例子简单地给你解释一下。
应用服务器App Server要完成一个事务性操作包括在MySQL上执行一个写事务在Redis上插入一个标记位并通过一个第三方服务给用户发送一条完成消息。
这三个操作都需要保证事务原子性所以如果此时Redis的延迟增加就会拖累App Server端整个事务的执行。这个事务一直完成不了又会导致MySQL上写事务占用的资源无法释放进而导致访问MySQL的其他请求被阻塞。很明显Redis变慢会带来严重的连锁反应。
<img src="https://static001.geekbang.org/resource/image/58/64/58555bc098b518e992136f1128430c64.jpg" alt="">
我相信,不少人遇到过这个问题,那具体该怎么解决呢?
这个时候,切忌“病急乱投医”。如果没有一套行之有效的应对方案,大多数时候我们只能各种尝试,做无用功。在前面的[第16讲](https://time.geekbang.org/column/article/285000)、[第17讲](https://time.geekbang.org/column/article/286082)中我们学习了会导致Redis变慢的潜在阻塞点以及相应的解决方案即异步线程机制和CPU绑核。除此之外还有一些因素会导致Redis变慢。
接下来的两节课我再向你介绍一下如何系统性地应对Redis变慢这个问题。我会从问题认定、系统性排查和应对方案这3个方面给你具体讲解。学完这两节课以后你一定能够有章法地解决Redis变慢的问题。
## 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 per run).
Worst run took 36x longer than the average latency.
```
需要注意的是基线性能和当前的操作系统、硬件配置相关。因此我们可以把它和Redis运行时的延迟结合起来再进一步判断Redis性能是否变慢了。
一般来说你要把运行时延迟和基线性能进行对比如果你观察到的Redis运行时延迟是其基线性能的2倍及以上就可以认定Redis变慢了。
判断基线性能这一点对于在虚拟化环境下运行的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则是一位病人。在给病人看病时你要知道人体的机制还要知道可能对身体造成影响的外部因素比如不健康的食物、不好的情绪等然后要拍CT、心电图等找出病因最后再确定治疗方案。
在诊断“Redis变慢”这个病症时同样也是这样。你要基于自己对Redis本身的工作原理的理解并且结合和它交互的操作系统、存储以及网络等外部系统关键机制再借助一些辅助工具来定位原因并制定行之有效的解决方案。
医生诊断一般都是有章可循的。同样Redis的性能诊断也有章可依这就是影响Redis的关键因素。下面这张图你应该有印象这是我们在[第一节课](https://time.geekbang.org/column/article/268262)画的Redis架构图。你可以重点关注下我在图上新增的红色模块也就是Redis自身的操作特性、文件系统和操作系统它们是影响Redis性能的三大要素。
<img src="https://static001.geekbang.org/resource/image/cd/06/cd026801924e197f5c79828c368cd706.jpg" alt="">
接下来我将从这三大要素入手结合实际的应用场景依次给你介绍从不同要素出发排查和解决问题的实践经验。这节课我先给你介绍Redis的自身操作特性的影响下节课我们再重点研究操作系统和文件系统的影响。
### Redis自身操作特性的影响
首先我们来学习下Redis提供的键值对命令操作对延迟性能的影响。我重点介绍两类关键操作慢查询命令和过期key操作。
**1.慢查询命令**
慢查询命令就是指在Redis中执行速度慢的命令这会导致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官方文档](https://redis.io/commands/)中对每个命令的复杂度都有介绍,当你需要了解某个命令的复杂度时,可以直接查询。
那该怎么应对这个问题呢?在这儿,我就要给你排查建议和解决方法了,这也是今天的第一个方法。
当你发现Redis性能变慢时可以通过Redis日志或者是latency monitor工具查询变慢的请求根据请求对应的具体命令以及官方文档确认下是否采用了复杂度高的慢查询命令。
如果的确有大量的慢查询命令,有两种处理方式:
1. **用其他高效命令代替**。比如说如果你需要返回一个SET中的所有成员时不要使用SMEMBERS命令而是要使用SSCAN多次迭代返回避免一次返回大量数据造成线程阻塞。
1. **当你需要执行排序、交集、并集操作时可以在客户端完成而不要用SORT、SUNION、SINTER这些命令以免拖慢Redis实例**
当然如果业务逻辑就是要求使用慢查询命令那你得考虑采用性能更好的CPU更快地完成查询命令避免慢查询的影响。
还有一个比较容易忽略的慢查询命令就是KEYS。它用于返回和输入模式匹配的所有key例如以下命令返回所有包含“name”字符串的keys。
```
redis&gt; KEYS *name*
1) &quot;lastname&quot;
2) &quot;firstname&quot;
```
**因为KEYS命令需要遍历存储的键值对所以操作延时高**。如果你不了解它的实现而使用了它就会导致Redis性能变慢。所以**KEYS命令一般不被建议用于生产环境中**。
**2.过期key操作**
接下来我们来看过期key的自动删除机制。它是Redis用来回收内存空间的常用机制应用广泛本身就会引起Redis操作阻塞导致性能变慢所以你必须要知道该机制对性能的影响。
Redis键值对的key可以设置过期时间。默认情况下Redis每100毫秒会删除一些过期key具体的算法如下
1. 采样ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP个数的key并将其中过期的key全部删除
1. 如果超过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. 从慢查询命令开始排查,并且根据业务需求替换慢查询命令;
1. 排查过期key的时间设置并根据实际使用需求设置不同的过期时间。
性能诊断通常是一件困难的事所以我们一定不能毫无目标地“乱找”。这节课给你介绍的内容就是排查和解决Redis性能变慢的章法你一定要按照章法逐一排查这样才可能尽快地找出原因。
当然要真正把Redis用好除了要了解Redis本身的原理还要了解和Redis交互的各底层系统的关键机制包括操作系统和文件系统。通常情况下一些难以排查的问题是Redis的用法或设置和底层系统的工作机制不协调导致的。下节课我会着重给你介绍文件系统、操作系统对Redis性能的影响以及相应的排查方法和解决方案。
## 每课一问
这节课我提到了KEYS命令因为它的复杂度很高容易引起Redis线程操作阻塞不适用于生产环境。但是KEYS命令本身提供的功能是上层业务应用经常需要的即返回与输入模式匹配的keys。
请思考一下在Redis中还有哪些其他命令可以代替KEYS命令实现同样的功能呢这些命令的复杂度会导致Redis变慢吗
欢迎在留言区写下你的思考和答案,我们一起讨论,共同学习进步。如果你觉得有所收获,欢迎你把今天的内容分享给你的朋友。