1 慢查询的原因
1.1 非命令数据相关原因
1.1.1 网络延迟
原因:客户端与 Redis 服务器之间的网络延迟可能导致客户端感知到的响应时间变长。
解决方案:优化网络环境
排查:
1.1.2 CPU 竞争
原因:Redis 是单线程的,如果服务器的 CPU 资源被其他进程占用,可能导致 Redis 命令执行变慢。
解决方案:停掉其它无用的进程,或增加硬件设备
排查:
- info cpu: 查看redis 使用cpu情况
1.1.3 连接数不足
原因:连接数设置过小。如果QPS较大,然后Redis链接池的最大连接数设置的却比较小,那么获取链接的时间就会增加。
解决方案:调整连接数
排查:
- info clients:查看连接数等信息
- client list: 查看所有连接
- client kill ip:port : 杀死指定连接
1.1.4 内存不足
原因:当 Redis 内存达到 maxmemory 后,每次写入新的数据之前,Redis 必须先从实例中踢出一部分数据,让整个实例的内存维持在 maxmemory 之下,然后才能把新数据写进来。这个踢出旧数据的逻辑也是需要消耗时间的,而具体耗时的长短,要取决于配置的淘汰策略。
解决方案:
- 1 增加最大内存限制
- 2 调整过期时间与淘汰策略,让非热点数据及时清除
排查:
- info memory
1.1.5 内存碎片整理耗时开销大
原因:Redis 的数据都存储在内存中,当我们的应用程序频繁修改 Redis 中的数据时,就有可能会导致 Redis 产生内存碎片。内存碎片会降低 Redis 的内存使用率。
具体原因看Redis内存碎片详解中 2
解决方案:
- Redis内存碎片详解中 4
排查:
- info memory 具体看Redis内存碎片详解中 3
1.1.6 Fork耗时严重
原因:当 Redis 开启了后台 RDB 和 AOF rewrite 后,在执行时,它们都需要主进程创建出一个子进程进行数据的持久化。主进程创建子进程,会调用操作系统提供的 fork 函数。而 fork 在执行过程中,主进程需要拷贝自己的内存页表给子进程,如果这个实例很大,那么这个拷贝的过程也会比较耗时。而且这个 fork 过程会消耗大量的 CPU 资源,在完成 fork 之前,整个 Redis 实例会被阻塞住,无法处理任何客户端请求。如果此时你的 CPU 资源本来就很紧张,那么 fork 的耗时会更长,甚至达到秒级,这会严重影响 Redis 的性能。
1.2 命令数据相关的原因
1.2.1 复杂命令
原因:
- KEYS:遍历所有键,时间复杂度为 O(N),可能导致性能瓶颈。
- SORT:对数据进行排序,时间复杂度为 O(N log N),当数据量较大时会显著影响性能。
- SUNION、ZUNIONSTORE:聚合类命令,当操作的数据量较大时会消耗较多 CPU 资源。
- 等
解决方案:
- 尽量不使用 O(N) 以上复杂度过高的命令,对于数据的聚合操作,放在客户端做。 执行 O(N) 命令,保证 N 尽量的小(推荐 N <= 300),每次获取尽量少的数据,让 Redis 可以及时处理返回。比如: 尽量避免使用 KEYS、SORT 等命令,改用 SCAN 或在客户端完成数据聚合。
- 批量操作:减少网络往返,但避免单次批量过大。比如:一次性删除百万级成员的集合;对于多个操作,使用MGET、MSET 等批量命令、管道、分页命令lrange 减少网络往返。
排查:
- CONFIG SET slowlog-log-slower-than 1000
- SLOWLOG GET
- 3 查看相应的key
1.2.2 大KEY
原因:BigKey 是指存储了大量数据的 Key(如大型列表、集合或哈希)。对 BigKey 的操作(如 DEL、SET)可能会导致 Redis 阻塞,因为这些操作需要处理大量的数据。
示例:一个存储了 100 万条数据的列表(List),执行 DEL 命令时可能会阻塞 Redis 服务。
具体原因:Redis 大 Key:别让你的 Redis 变成"胖子"! 中二
解决方案:
Redis 大 Key:别让你的 Redis 变成"胖子"! 中五
排查:
- 1 CONFIG SET slowlog-log-slower-than 1000
- 2 SLOWLOG GET
- 3 查看相应的key, Redis 大 Key:别让你的 Redis 变成"胖子"! 中四
1.2.3 缓存集中过期
原因:为什么集中过期会导致 Redis 延迟变大?这就需要我们了解 Redis 的过期策略是怎样的。Redis 的过期数据采用被动过期 + 主动过期两种策略:
被动过期:只有当访问某个 key 时,才判断这个 key 是否已过期,如果已过期,则从实例中删除;
主动过期:Redis 内部维护了一个定时任务,默认每隔 100 毫秒(1秒10次)就会从全局的过期哈希表中随机取出 20 个 key,然后删除其中过期的 key,如果过期 key 的比例超过了 25%,则继续重复此过程,直到过期 key 的比例下降到 25% 以下,或者这次任务的执行耗时超过了 25 毫秒,才会退出循环。
注意,这个主动过期 key 的定时任务,是在 Redis 主线程中执行的。也就是说如果在执行主动过期的过程中,出现了需要大量删除过期 key 的情况,那么此时应用程序在访问 Redis 时,必须要等待这个过期任务执行结束,Redis 才可以服务这个客户端请求。此时就会出现,应用访问 Redis 延时变大。
解决方案:
- 1 集中过期 key 增加一个随机过期时间,把集中过期的时间打散,降低 Redis 清理过期 key 的压力;
- 2 如果你使用的 Redis 是 4.0 以上版本,可以开启 lazy-free 机制,当删除过期 key 时,把释放内存的操作放到后台线程中执行,避免阻塞主线程。
排查:
复杂命令 和 大key 是一般出现比较多的导致慢查询的原因,但是如果慢查询总是在特定的时间点出现,或者每隔一段时间出现一次,那么就有可能是集中过期,可以通过监控确认下。
1.2.4 lua脚本执行时间过长
原因:可能lua脚本逻辑不够优化
解决方案:优化逻辑
- 1 CONFIG SET slowlog-log-slower-than 1000
- 2 SLOWLOG GET
- 3 查看相应的脚本
3 监控工具
除了上边的各种排查方案中的命令,还可以使用第三方监控工具如 Prometheus 和 Grafana。
4 慢查询配置相关命令
slowlog len
- 获取慢查询日志的长度
slowlog reset
- 命令清理慢查询日志
slowlog get [N]
- 获取最近的 N 条慢查询日志(不填 N 默认返回全部)
config set slowlog-log-slower-than 1000
- 指定命令执行时长的阈值,执行命令的时长超过这个阈值时就会被记录下来
config set slowlog-max-len 1200
- 指定慢查询日志最多存储的条数
config rewrite
- 配置写入配置文件