Redis为什么变慢了?常见延迟问题定位与分析
Redis作为内存数据库,拥有非常高的性能,单个实例的QPS能够达到10W左右。但我们在使用 Redis 时,经常时不时会出现访问延迟很大的情况,如果你不知道 Redis 的内部实现原理,在排查问题时就会一头雾水。
Redis出现访问延迟变大,都与我们的使用不当或运维不合理导致的。
以下这篇文章我们就来分析一下 Redis 在使用过程中,经常会遇到的延迟问题以及如何定位和分析。
链路追踪就是在服务访问外部依赖的出入口,记录下每次请求外部依赖的响应延时。
如果发现确实是操作 Redis 的这条链路耗时变长了,那么此时需要把焦点关注在业务服务到 Redis 这条链路上。
从服务到 Redis 这条链路变慢的原因可能有网络(网络不好,数据包在传输时存在延迟、丢包等),或者是 Redis本身的问题,吸引进一步排查问题的所在。
如果是服务器之间网络存在问题,这时候就要找网络运维同事,让其协助解决网络问题。
简单来讲,基准性能就是指 Redis 在一台负载正常的机器上,其最大的响应延迟和平均响应延迟分别是怎样的?
如果对比曾经上线压测过的正常情况下Redis响应时长,确实是执行命令耗时变长了,那再进行下面的分析操作:
使用redis-cli -h 172.0.0.1 -p 6379 --intrinsic-latency 60,查看Redis在60秒内的最大网络响应延迟(单位:微秒)
这里是说在60秒内的最大响应时长是84微秒(0.084毫秒)
还可以使用redis-cli -h 172.0.0.1 -p 6973 --latency-history -i 1查看redis在一段时间内最大、最小、平均访问延迟。
这里是每1秒钟输出一次采样的redis平均操作耗时,平均耗时这个指标分布在0.08ms和0.13ms之间
如果观察到,这个实例的运行延迟是正常 Redis 基准性能的 2 倍以上,即可认为这个 Redis 实例确实变慢了
接下来我们一步步来分析可能导致 Redis 变慢的原因是什么。
如果在使用Redis时,发现访问延迟突然增大,如何进行排查?
这种情况可以通过增加 Redis 慢日志统计来看
首先设置 Redis的 慢日志阈值,只有超过阈值的命令才会被记录,这里的单位是微秒,例如设置慢日志的阈值为 5 毫秒,同时设置只保留最近 500 条慢日志记录:
//命令执行超过 5 毫秒记录慢日志
CONFIG SET slowlog-log-slower-than 5000
//只保留最近 500 条慢日志
CONFIG SET slowlog-max-len 500
记录慢日志之后,可以使用 slowlog get 5 来查看 top 5 的操作慢的命令:
127.0.0.1:6379> SLOWLOG get 5
1) 1) (integer) 32693 # 慢日志ID
2) (integer) 1593763337 # 执行时间
3) (integer) 5299 # 执行耗时(微秒)
4) 1) "LRANGE" # 具体执行的命令和参数
2) "user_list_2000"
3) "0"
4) "-1"
2) 1) (integer) 32692
2) (integer) 1593763337
3) (integer) 5044
4) 1) "GET"
2) "book_price_1000"
...
通常来说命令执行慢有两种原因:
由于开源版本6.0之前的 Redis 是单线程的,上面两种原因会导致其他命令阻塞,表现为整体 Redis 操作命令都很耗时。
针对这种情况如何解决呢?
如果代码内部都是的一些简单操作命令,例如set、get、hset、hget等,但是慢日志里面仍然能看到这些命令,那就要检查是否操作了大key,这种类型的 key 我们一般称之为 bigkey。因为大 key 在内存分配的时候会比较耗时,释放内存也更耗时,这是导致 Redis 响应慢的原因。
Redis 提供了扫描 bigkey 的命令,我们可以使用redis-cli -h 172.0.01 -p 6379 --bigkeys -i 0.01查看,一个实例中 bigkey 的分布情况:
Sampled 829675 keys in the keyspace!
Total key length in bytes is 10059825 (avg len 12.13)
Biggest string found 'key:291880' has 10 bytes
Biggest list found 'mylist:004' has 40 items
Biggest set found 'myset:2386' has 38 members
Biggest hash found 'myhash:3574' has 37 fields
Biggest zset found 'myzset:2704' has 42 members
36313 strings with 363130 bytes (04.38% of keys, avg size 10.00)
787393 lists with 896540 items (94.90% of keys, avg size 1.14)
1994 sets with 40052 members (00.24% of keys, avg size 20.09)
1990 hashs with 39632 fields (00.24% of keys, avg size 19.92)
1985 zsets with 39750 members (00.24% of keys, avg size 20.03)
这个命令的原理其实是在 Redis 内部执行了 scan 命令,遍历 Redis 里面所有的key,分别执行 strlen、llen、hlen、scard、zcard 命令,来获取 String 类型的长度、容器类型(List、Hash、Set、ZSet)的元素个数。
当执行这个命令时,需要注意一下几点问题:
针对 bigkey 导致延迟的问题。我们可以这么做:
我们是否在操作 Redis 的过程中,每次都在一个时间点就会发生一波延迟。这时候我们需要排查一下是否存在大量的 key 在同一个时间过期呢。
为什么集中过期就会导致 Redis 延迟变大?
集中过期导致 Redis 响应慢的原因是跟 Redis 的过期策略有关,主要有两种:
Redis 设置集中过期的命令是 expireat 和 pexpireat,排查集中过期的时候可以搜索这些关键字。
解决这种集中过期有两种方案:
如果 Redis 实例设置了内存上限 maxmemory,内存使用也达到了 maxmemory,这也可能导致 Redis 响应慢。
Redis 作为缓存使用时,通常会给这个实例设置一个内存上限 maxmemory,然后设置一个数据淘汰策略。
当实例的内存达到了 maxmemory 后,会发现之后每次写入新数据,操作延迟变大了,这是为什么?
Redis 数据达到上限了,每次写入新数据之前,Redis 需要先从实例中踢出一部分数据,让整个实例的内存维持在 maxmemory 之下,然后才能把新数据放进内存中,这个踢出同样是耗时的操作,淘汰数的逻辑跟删除过期key的逻辑一样,所以淘汰会增加 Redis 响应时间。
淘汰策略:
如果此时 Redis 的实例中还存储了 bigkey,那么在淘汰删除 bigkey 释放内存时也会耗时比较久。
如果 Redis 开启了自动生成 RDB 和 AOF 重写功能,那么有可能在后台生成 RDB 和 AOF 重写时导致 Redis 的访问延迟增大,而等这些任务执行完毕后,延迟情况消失。
如果遇到这种情况,一般就是执行生成RDB和AOF重写任务导致的。
生成RDB和AOF都需要父进程fork出一个子进程进行数据的持久化,在fork执行过程中,父进程需要拷贝内存页表给子进程,如果整个实例内存占用很大,那么需要拷贝的内存页表会比较耗时,此过程会消耗大量的CPU资源,在完成fork之前,整个实例会被阻塞住,无法处理任何请求,如果此时CPU资源紧张,那么fork的时间会更长,甚至达到秒级。这会严重影响Redis的性能。
除了因为备份的原因生成 RDB 之外,在主从节点第一次建立数据同步时,主节点也会生成 RDB 文件给从节点进行一次全量同步,这时也会对 Redis 产生性能影响。
可以在 Redis 上面执行 info 命令查看 latest_fork_usec 是多少,单位是微秒。
# Stats
total_connections_received:2
total_commands_processed:4
instantaneous_ops_per_sec:0
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
# 上一次 fork 耗时,单位微秒
latest_fork_usec:0
migrate_cached_sockets:0
要想避免 Redis 在 fork 的时候耗时太久,可以采取一下方案进行:
关于数据持久化方面,还有影响 Redis 性能的因素,这次我们重点来看 AOF 数据持久化。
如果了解 Redis 执行 AOF 命令的原理,就知道为什么开启 AOF 会导致 Redis 响应慢了。
当 Redis 开启 AOF 后,其工作原理如下:
为了保证 AOF 文件数据的安全性,提供了3中刷盘策略,其中各有利弊: