Redis异常排查思路
1. 服务排障的通用方法
当线上服务出现异常(如此文中的“访问耗时较长”)时,应遵循一套逻辑清晰的排查流程。
-
明确问题现象:
- 起点:收到告警,发现“数据整体处理时间变慢”。
- 定位:通过监控系统或业务打点,将问题范围缩小到具体的服务模块和处理阶段。
-
两大排查思路:服务本身 vs. 外部因素:
-
思路一:排查模块本身问题(由内而外)
- 横向看基础指标:检查服务运行的基础资源,这是最快、最直接的判断方式。
- CPU:是否飙升或处于高位。
- 内存:是否存在泄漏或达到瓶颈。
- 磁盘 I/O:读写是否异常缓慢。
- 网络 I/O:带宽是否被占满。
- 纵向看代码变更:确认近期是否有相关发布或代码改动,这常常是问题的直接原因。
- 横向看基础指标:检查服务运行的基础资源,这是最快、最直接的判断方式。
-
思路二:排查外部因素(数据或依赖)
- 数据量问题:检查上游请求量或处理的数据量是否激增(文中发现“上报量增加了 5 倍”)。
- 依赖服务问题:检查当前模块所依赖的下游服务(如数据库、缓存、其他微服务)是否出现异常。
-
-
临时应急处理:
在定位到根本原因之前,为了快速恢复服务,可以先采取临时措施。 - 扩容:增加服务实例,分摊压力。 - 限流:限制进入系统的请求量,保护核心服务。 - 服务降级:暂时关闭非核心功能,保证主流程可用。
-
深挖根本原因:
临时解决问题后,必须深入探究根本原因,以防问题再次发生,并寻求性能优化点。 - 精细化定位:利用更精确的工具定位耗时点。 - 业务打点:在代码关键路径增加耗时日志或监控指标,确定问题的大致业务范围。 - 性能分析工具 (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_sys和used_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 被驱逐。
- 持久化:如果开启了 RDB 或 AOF,关注
- CPU (
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_hits和keyspace_misses:通过这两个值可以计算缓存命中率。命中率过低意味着大量请求穿透到后端数据库,增加了整体链路的延迟。
- 热点 Key (Hotkey):
- 现象:某个或某几个 key 被极高频率地访问,导致单线程的 Redis CPU 压力剧增,无法处理其他请求。这是文中案例的最终原因。
- 排查:
- Redis 4.0.3+ 版本,在内存淘汰策略为
allkeys-lfu或volatile-lfu时,可以使用redis-cli --hotkeys命令查找。 - 如果没有原生支持,可以通过客户端抓包、自定义统计或监控工具来发现。
- Redis 4.0.3+ 版本,在内存淘汰策略为
- 根本原因:热点 Key 导致的网络 I/O 和数据处理高度集中在单线程上,即使 OPS 不高,CPU 也会因频繁的数据拷贝和上下文切换而耗尽。
3. 问题复现、测试与解决
-
复现与验证:
- 原则:从最简单的 Demo 开始,逐步模拟线上环境。
- 方法:
- 本地 Demo:编写简单的脚本(如使用 pipeline)来验证理论猜想。
- 线上模拟:搭建简化的压测链路(文中通过模拟 Kafka 消费),使用线上真实数据进行压测,逐步调整压力(并发数、数据量),直到复现问题现象(CPU 高、OPS 低)。
-
解决方案:
根据定位到的根本原因(如热点 Key),选择合适的解决方案。 - 读写分离:如果是多实例集群,将读请求分散到从节点,减轻主节点压力。 - Pipeline 或 Lua 脚本:将多个命令打包一次性发送给 Redis,大幅减少网络 I/O 次数,降低 CPU 消耗。 - 增加间接层(本地缓存):在业务服务侧增加一层本地缓存(如 Caffeine、Guava Cache),将热点 Key 的数据缓存在应用内存中,直接拦截大部分请求,避免访问 Redis。这是文中最终采用的方案。