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