Skip to content

Redis异常排查思路

1. 服务排障的通用方法

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

  1. 明确问题现象

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

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

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

      • 数据量问题:检查上游请求量或处理的数据量是否激增(文中发现“上报量增加了 5 倍”)。
      • 依赖服务问题:检查当前模块所依赖的下游服务(如数据库、缓存、其他微服务)是否出现异常。
  3. 临时应急处理

    在定位到根本原因之前,为了快速恢复服务,可以先采取临时措施。 - 扩容:增加服务实例,分摊压力。 - 限流:限制进入系统的请求量,保护核心服务。 - 服务降级:暂时关闭非核心功能,保证主流程可用。

  4. 深挖根本原因

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

2. Redis 服务排障的核心方法

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

2.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.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 在删除操作上,引发周期性的性能抖动。
    • 排查:检查业务逻辑中是否有可能导致集中过期的场景(如缓存预热、批处理任务),并观察监控是否有周期性延迟。

2.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 也会因频繁的数据拷贝和上下文切换而耗尽。

3. 问题复现、测试与解决

  1. 复现与验证

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

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