Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

问题排查与优化

前置工作

如何确定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非常大,取数据特别多
  • 方法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: 排查是否达到内存上限

    • 当 redis 内存超过 maxmemory后,每次写入新数据前,都要先踢出部分数据,才能让控制在内存上限内
    • 这个踢出过程长短,取决于配置的淘汰策略
      • 一般最常使用的是
        • allkeys-lru
          • 不管是否设置了过期,淘汰最近最少访问的
        • volatile-lru
          • 只淘汰那些 设置了过期 且 最近最少访问的
    • 优化策略
      • 4.0 以上版本,可设置lazyfree-lazy-eviction yes,把淘汰key释放内存的操作,放到后台线程执行
  • 方法5: 排查 定时RDBAOF 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
        
    • 优化策略
      • 不建议在 Redis 机器上开启这个机制
  • 方法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性能已经足够优秀
  • 方法9: 排查是否用了 Swap

    • 当内存不足时,会用磁盘换页的方式形成交换区内存(Swap)
      • 这种虚拟内存涉及IO,会变慢
    • 如何确认用到了Swap?
      • 先找到 进程PID
        ps -aux | grep redis-server
        
      • 再用如下命令查看
        cat /proc/进程PID/smaps | egrep '^(Swap|Size)'
        
      • 结果是
        • 一行Size
        • 一行Swap
          • 表示这块Size有多少被换到磁盘上面
        • 如果这两个值相等,代表完全用到了Swap
    • 如果只用到一点,关系不大,但如果几百M换过去了,那就要增加机器内存了
  • 方法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信息做一些采集和告警,注意采集的频率,慎用开源的监控组件