Skip to content

Redis异常排查思路

服务排障的通用方法

当线上服务出现异常(如此文中的“访问耗时较长”)时,应遵循一套逻辑清晰的排查流程。

  1. 明确问题现象

    • 起点:收到告警,发现“数据整体处理时间变慢”。
    • 定位:通过监控系统或业务打点,将问题范围缩小到具体的服务模块和处理阶段。
  2. 两大排查思路:服务本身 vs. 外部因素

    • 思路一:排查模块本身问题(由内而外)

      • 横向看基础指标:检查服务运行的基础资源,这是最快、最直接的判断方式。
        • CPU:是否飙升或处于高位。
        • 内存:是否存在泄漏或达到瓶颈。
        • 磁盘I/O:读写是否异常缓慢。
        • 网络I/O:带宽是否被占满。
      • 纵向看代码变更:确认近期是否有相关发布或代码改动,这常常是问题的直接原因。
    • 思路二:排查外部因素(数据或依赖)

      • 数据量问题:检查上游请求量或处理的数据量是否激增(文中发现“上报量增加了5倍”)。
      • 依赖服务问题:检查当前模块所依赖的下游服务(如数据库、缓存、其他微服务)是否出现异常。
  3. 临时应急处理(三板斧): 在定位到根本原因之前,为了快速恢复服务,可以先采取临时措施。

    • 扩容:增加服务实例,分摊压力。
    • 限流:限制进入系统的请求量,保护核心服务。
    • 服务降级:暂时关闭非核心功能,保证主流程可用。
  4. 深挖根本原因: 临时解决问题后,必须深入探究根本原因,以防问题再次发生,并寻求性能优化点。

    • 精细化定位:利用更精确的工具定位耗时点。
      • 业务打点:在代码关键路径增加耗时日志或监控指标,确定问题的大致业务范围。
      • 性能分析工具 (Profiler):使用如 pprof (Go)、JProfiler (Java) 等工具,可以精确到函数级别的CPU和内存消耗,找到性能瓶颈(文中通过 pprof 发现Redis相关命令耗时占大头)。
    • 问题转换:将表层问题(“数据量突增”)转化为更深层次的技术问题(“Redis可用性问题”),从而进入专项排查。

Redis服务排障的核心方法

当问题明确指向Redis后,可以从三个维度进行系统性排查:Redis服务本身 -> Redis数据存储 -> Redis访问方式

1. Redis服务本身的问题

这个维度关注Redis实例及其运行环境。

  • 网络延迟确认
    • 业务服务器到Redis服务器的网络:排查是否存在网络抖动、丢包或带宽瓶颈。在内网环境中,若数据量下降后耗时减少,可初步排除固定网络问题。
    • Redis自身响应延迟
      • redis-cli --intrinsic-latency <duration>:在Redis服务器上直接运行,测试其固有的、最低的响应延迟,排除外部干扰。
      • redis-cli --latency-history -i <interval>:持续监控一段时间内的延迟分布(最小、最大、平均值),观察是否存在周期性或瞬时的延迟高峰。
  • 吞吐量 (OPS)
    • INFO stats 命令中的 instantaneous_ops_per_sec 指标可以显示Redis当前的QPS。
    • 关键分析:将OPS与CPU使用率结合分析。如果 CPU占用率高而OPS很低,说明Redis可能在执行效率低下的操作,或者被某些操作阻塞,而不是在高效地处理请求。
  • 主从同步问题 (多实例)
    • INFO replication 命令查看主从状态。
    • 关注 master_link_status (是否为up)、master_last_io_seconds_ago (距离上次与主节点通信的秒数,过大则异常) 等指标,排查主从延迟或断连问题。
  • 核心资源:CPU、内存、磁盘I/O
    • CPU (INFO cpu):
      • used_cpu_sysused_cpu_user:分别代表内核态和用户态的CPU消耗。如果持续接近100%,说明CPU成为瓶颈。
      • 补充:Redis 6.0 以后引入了I/O多线程,可以将网络I/O的压力分摊到多个核心上,但命令执行依然是单线程。
    • 内存 (INFO memory):
      • used_memory_rss:操作系统实际分配给Redis的内存,如果远大于 used_memory,说明内存碎片化严重。
      • mem_fragmentation_ratio:碎片率(used_memory_rss / used_memory)。理想值略大于1。大于1.5表示碎片过多;小于1则表示操作系统物理内存不足,可能发生了Swap(内存交换),会导致性能急剧下降。
    • 磁盘I/O (INFO persistence):
      • 持久化:如果开启了RDB或AOF,关注 latest_fork_usec (最近一次fork操作的耗时)。fork操作会消耗CPU和内存,如果耗时过长,会阻塞主线程。
      • 内存交换 (Swap):检查 maxmemory 设置。如果没有设置,当物理内存不足时,操作系统会进行Swap,将部分内存数据换到磁盘上,这对Redis是致命的。通过 evicted_keys 查看是否有key被驱逐。
2. Redis数据存储的问题

这个维度关注存储在Redis中的数据本身是否存在问题。

  • Key总数 (INFO keyspace):
    • 实例中的key数量过多(如超过千万)可能导致过期key回收不及时,增加管理负担。
  • 大Key (Bigkey)
    • 危害:操作(读写、删除)大Key会阻塞Redis主线程,造成内存分配不均,过期删除时也可能引发阻塞。
    • 排查redis-cli --bigkeys 命令可以扫描并找出实例中的大Key。
    • 补充知识点 - 内存大页 (Huge Pages):Linux的内存大页机制(如2MB一页)与写时复制(COW)机制结合时,对大Key的写操作会导致更大的复制开销,加剧阻塞。
  • Key集中过期
    • 现象:大量key在同一时间点集中过期,会导致Redis花费大量CPU在删除操作上,引发周期性的性能抖动。
    • 排查:检查业务逻辑中是否有可能导致集中过期的场景(如缓存预热、批处理任务),并观察监控是否有周期性延迟。
3. 访问Redis的问题

这个维度关注客户端与Redis的交互方式。

  • 客户端连接数 (INFO clients):
    • connected_clients:检查连接数是否超过Redis配置的 maxclients 限制。
    • blocked_clients:检查是否有客户端因执行 BLPOP, BRPOP 等阻塞命令而被挂起。
  • 慢命令 (Slow Command)
    • 排查slowlog get <N> 命令可以查看最近N条执行耗时超过 slowlog-log-slower-than 配置阈值的命令。这有助于直接定位是哪些具体命令导致了延迟。
  • 缓存命中率 (INFO stats):
    • keyspace_hitskeyspace_misses:通过这两个值可以计算缓存命中率。命中率过低意味着大量请求穿透到后端数据库,增加了整体链路的延迟。
  • 热点Key (Hotkey)
    • 现象:某个或某几个key被极高频率地访问,导致单线程的Redis CPU压力剧增,无法处理其他请求。这是文中案例的最终原因。
    • 排查
      • Redis 4.0.3+ 版本,在内存淘汰策略为 allkeys-lfuvolatile-lfu 时,可以使用 redis-cli --hotkeys 命令查找。
      • 如果没有原生支持,可以通过客户端抓包、自定义统计或监控工具来发现。
    • 根本原因:热点Key导致的网络I/O和数据处理高度集中在单线程上,即使OPS不高,CPU也会因频繁的数据拷贝和上下文切换而耗尽。

问题复现、测试与解决

  1. 复现与验证

    • 原则:从最简单的Demo开始,逐步模拟线上环境。
    • 方法
      • 本地Demo:编写简单的脚本(如使用pipeline)来验证理论猜想。
      • 线上模拟:搭建简化的压测链路(文中通过模拟Kafka消费),使用线上真实数据进行压测,逐步调整压力(并发数、数据量),直到复现问题现象(CPU高、OPS低)。
  2. 解决方案: 根据定位到的根本原因(如热点Key),选择合适的解决方案。

    • 读写分离:如果是多实例集群,将读请求分散到从节点,减轻主节点压力。
    • Pipeline或Lua脚本:将多个命令打包一次性发送给Redis,大幅减少网络I/O次数,降低CPU消耗。
    • 增加间接层(本地缓存):在业务服务侧增加一层本地缓存(如Caffeine、Guava Cache),将热点Key的数据缓存在应用内存中,直接拦截大部分请求,避免访问Redis。这是文中最终采用的方案。