问题排查与优化
前置工作
如何确定redis真的变慢了?
-
本机测试-实例最大响应延迟(每秒钟得出一次结果)
./src/redis-cli -h 127.0.0.1 -p 6379 --latency-history -i 1 -
拿一台相同配置的正常实例,也做一次测试,两者比较,如果延迟相差一倍以上,代表真变慢了
11种排查方法
-
方法1: 开启
慢日志功能- 设置 超过5ms的操作,就要记入慢日志
CONFIG SET slowlog-log-slower-than 5000 - 设置 最多保留500条记录
CONFIG SET slowlog-max-len 500 - 在客户端执行
SLOWLOG get 5即可输出- 慢日志ID
- 执行的时间戳
- 耗时(微秒)
- 具体执行的命令和参数
- 基本是如下两个原因
- 经常使用o(n)以上复杂度的命令,处理数据很复杂
- 使用o(n)复杂度,但n非常大,取数据特别多
- 设置 超过5ms的操作,就要记入慢日志
-
方法2: 排查是否有
bigkey- 对于简单的set、get、del命令,如果还是慢,很可能是写入的key太长太大了,每次分配内存和释放内存很耗时
- 排查统计命令
./src/redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.01- 这里一定要设置
-i参数,控制扫描频率,防止线上实例QPS暴增 - 对于容器类型的key,只能扫描里面的元素数量,不知道占用内存的情况,需要进一步评估
- 这里一定要设置
- 不建议在redis中存储bigkey,非要用的话,可用下面的优化策略
- 4.0 以上版本,可用 UNLINK 命令替代 DEL,把释放 key 内存的操作,放到后台线程中去执行,从而降低对 Redis 的影响
- 6.0 以上版本,可设置
lazyfree-lazy-user-del yes,在执行 DEL 命令时,释放内存也会放到后台线程中执行
-
方法3: 排查是否存在
集中过期- 某一时间点,突然出现一波延迟,很可能是很多key同一时间过期
- 了解 redis 的过期策略
- 被动过期
- 只有访问某个key,才判断是否过期,如果过期就删除
- 主动过期
- 内部有定时任务,0.1秒从全局的过期哈希表中随机取出20个,删掉其中过期的,如果过期的比例高过25%,就重复此过程,直到过期key比例下降到25%以下,或者任务执行耗时超过25毫秒
- 是在主线程中执行的,会阻塞客户端请求
- 这种耗时操作,不会记录在慢日志中
- 被动过期
- 优化策略
- 4.0 以上版本,可设置
lazyfree-lazy-expire yes,把过期key释放内存的操作,放到后台线程执行 - 设置过期时间时,增加一个随机时间,比如
+ random(300)
- 4.0 以上版本,可设置
-
方法4: 排查是否达到
内存上限- 当 redis 内存超过 maxmemory后,每次写入新数据前,都要先踢出部分数据,才能让控制在内存上限内
- 这个踢出过程长短,取决于配置的
淘汰策略- 一般最常使用的是
- allkeys-lru
- 不管是否设置了过期,淘汰最近最少访问的
- volatile-lru
- 只淘汰那些 设置了过期 且 最近最少访问的
- allkeys-lru
- 一般最常使用的是
- 优化策略
- 4.0 以上版本,可设置
lazyfree-lazy-eviction yes,把淘汰key释放内存的操作,放到后台线程执行
- 4.0 以上版本,可设置
-
方法5: 排查
定时RDB和AOF rewrite- 这两种操作,都会触发fork子进程,如果都是这种时候变慢,很可能是这个原因
- fork子进程时,拷贝自己的内存页表非常耗时,且消耗大量CPU,导致客户端被阻塞
- 如何确认是这个原因呢?
info stats- 找到
latest_fork_usec:这一行- 后面的数字就是上一次fork消耗的时间(单位:微秒)
- 找到
- 优化策略
- 实例内存 尽量控制在 10G 以下
- 最好在 slave 节点执行 RDB 备份,且推荐在低峰期执行
- 对于丢失数据不敏感的业务(例如当做纯缓存使用),可以关闭 AOF 和 AOF rewrite
- 实例不要部署在虚拟机上
- 降低主从库 全量同步 的概率:适当调大 repl-backlog-size 参数,避免主从全量同步
-
方法6: 排查 操作系统是否开启了
内存大页- Linux 内核从 2.6.38 开始,支持了内存大页机制
- 允许应用程序以 2MB 为单位申请内存
- 每次fork时,尽管只有少量写操作,但由于写时复制,还是要每次申请 2MB 的内存,耗时变长
- 如何确认系统是否开启此功能?
cat /sys/kernel/mm/transparent_hugepage/enabled- 如果输出是always,就需要关掉它
echo never > /sys/kernel/mm/transparent_hugepage/enabled
- 如果输出是always,就需要关掉它
- 优化策略
- 不建议在 Redis 机器上开启这个机制
- Linux 内核从 2.6.38 开始,支持了内存大页机制
-
方法7: 排查 AOF 的刷盘机制
- 默认的刷盘配置项是
appendfsync everysec- 每秒一次,放到后台线程执行,兼顾了性能和数据安全
- 最好用这个,如果不小心改成always,会变慢
- 但每秒一次也不能掉以轻心,对IO的操作还是会有阻塞客户端的风险
- 比如 刷盘操作 刚好撞上了 AOF rewrite
- 但可以设置如下配置,避免二者撞上
no-appendfsync-on-rewrite yes - 但如果在rewrite时,发生宕机,会丢失更多数据
- 默认的刷盘配置项是
-
方法8: 排查
绑定CPU- 为了避免 多个CPU核心之间切换上下文 造成性能损耗,会让redis进程绑定CPU
- 这个有可能造成 子进程 和 主进程 争抢CPU同一个核心的资源,会变慢
- 优化策略
- 6.0 版本推出了新功能,让主线程、子进程、后台线程分别绑定在不同的CPU核心上面
# Redis Server 和 IO 线程绑定到 CPU核心 0,2,4,6 server_cpulist 0-7:2 # 后台子线程绑定到 CPU核心 1,3 bio_cpulist 1,3 # 后台 AOF rewrite 进程绑定到 CPU 核心 8,9,10,11 aof_rewrite_cpulist 8-11 # 后台 RDB 进程绑定到 CPU 核心 1,10,11 # bgsave_cpulist 1,10-1 - 除非对性能有极致要求,否则不建议绑定,因为redis性能已经足够优秀
- 6.0 版本推出了新功能,让主线程、子进程、后台线程分别绑定在不同的CPU核心上面
- 为了避免 多个CPU核心之间切换上下文 造成性能损耗,会让redis进程绑定CPU
-
方法9: 排查是否用了
Swap- 当内存不足时,会用磁盘换页的方式形成交换区内存(Swap)
- 这种虚拟内存涉及IO,会变慢
- 如何确认用到了Swap?
- 先找到 进程PID
ps -aux | grep redis-server - 再用如下命令查看
cat /proc/进程PID/smaps | egrep '^(Swap|Size)' - 结果是
- 一行Size
- 一行Swap
- 表示这块Size有多少被换到磁盘上面
- 如果这两个值相等,代表完全用到了Swap
- 先找到 进程PID
- 如果只用到一点,关系不大,但如果几百M换过去了,那就要增加机器内存了
- 当内存不足时,会用磁盘换页的方式形成交换区内存(Swap)
-
方法10: 排查是否有
内存碎片- 当我们频繁修改其中的数据,就会产生内存碎片
- 如何确认内存碎片的多少?
info memory-
找到
used_memory- 表示存储数据的内存大小
-
找到
used_memory_rss- 表示操作系统实际分配的内存大小
-
如果
used_memory_rss / used_memory > 1.5- 说明碎片率超过50%,需要清理
-
- 清理方法
- Redis 4.0 以下版本,只能重启实例
- Redis 4.0 版本,提供了自动碎片整理(但可能导致性能下降,因为是主线程执行的)
# 开启自动内存碎片整理(总开关) activedefrag yes # 内存使用 100MB 以下,不进行碎片整理 active-defrag-ignore-bytes 100mb # 内存碎片率超过 10%,开始碎片整理 active-defrag-threshold-lower 10 # 内存碎片率超过 100%,尽最大努力碎片整理 active-defrag-threshold-upper 100 # 内存碎片整理占用 CPU 资源最小百分比 active-defrag-cycle-min 1 # 内存碎片整理占用 CPU 资源最大百分比 active-defrag-cycle-max 25 # 碎片整理期间,对于 List/Set/Hash/ZSet 类型元素一次 Scan 的数量 active-defrag-max-scan-fields 1000
-
方法11: 排查
网络问题- 应该使用长连接操作redis,避免频繁的短链接
- 应该避免 机器内其他应用 占据过多资源与带宽,最好机器专用
- 尽量写一些监控脚本,用长连接的方式区对info信息做一些采集和告警,注意采集的频率,慎用开源的监控组件