Skip to content

排查redis线上命中率暴跌的问题

紧急排查与快速恢复

这个阶段的目标是快速判断问题的严重程度,并尝试用最简单的方式恢复服务。

  1. 确认问题范围

    • 影响面有多大? 是单个业务、单个 Redis 实例,还是所有业务都受影响?
    • 问题发生时间点? 是否与某个版本发布、配置变更或线上活动有关?
  2. 检查应用层监控

    • 应用日志: 查看应用服务器的日志,检查是否有大量的 Redis 连接超时、读取错误或其它异常日志。
    • 接口响应时间 (RT): 观察受影响业务的接口 RT 是否显著增加,数据库的慢查询日志是否增多。
    • 数据库负载: 检查数据库的 CPU、IOPS 和连接数等指标是否飙升。如果数据库压力大,说明大量的请求穿透了缓存,直接打到了数据库。
  3. 检查 Redis 服务本身

    • 服务是否可用? 执行 ping 命令,看 Redis 是否正常响应。
    • 基本信息查看: 使用 INFO 命令查看 Redis 的运行状态,重点关注以下几个部分:
      • keyspace_hits (命中次数) 和 keyspace_misses (未命中次数):计算命中率 (keyspace_hits / (keyspace_hits + keyspace_misses)),确认命中率确实暴跌。
      • connected_clients (客户端连接数):连接数是否异常增高。
      • used_memory (已用内存):内存使用是否达到 maxmemory 上限。如果达到上限,会导致数据被淘汰(Eviction),从而降低命中率。

深入分析原因

如果第一阶段未能解决问题,或者需要找到根本原因,就需要进行更深入的分析。

1. 缓存穿透 (Cache Penetration)

现象: 大量请求查询一个缓存和数据库中都不存在的数据。这导致每次请求都会“穿透”缓存,直接查询数据库,给数据库带来巨大压力。

排查思路:

  • 业务日志分析: 分析应用的访问日志,找出被频繁查询但返回为空的请求。
  • 抓取 Redis 命令: 使用 MONITOR 命令(慎用,会影响性能)或者第三方抓包工具,观察线上实际执行的查询命令,查看是否存在大量查询不存在的 key 的情况。
  • 应用代码审查: 检查代码逻辑,看是否存在恶意攻击或异常逻辑,导致查询不存在的数据。

解决方案:

  • 缓存空对象: 当数据库查询结果为空时,依然在 Redis 中缓存一个特殊值(如 null 或一个约定的字符串),并设置一个较短的过期时间。
  • 布隆过滤器 (Bloom Filter): 在应用层和缓存层之间增加布隆过滤器,对于所有可能存在的数据,都将其存入布隆过滤器。当一个查询请求过来时,先在布隆过滤器中判断该 key 是否可能存在,如果不存在,则直接返回,避免了后续的缓存和数据库查询。

2. 缓存雪崩 (Cache Avalanche)

现象: 在某个时间点,大量的缓存 key 同时失效,导致海量请求瞬间直接涌向数据库,造成数据库崩溃。

排查思路:

  • 检查代码逻辑: 检查业务代码中设置缓存过期时间的逻辑,是否有可能导致大量 key 在同一时间点集中过期。 例如,项目启动时批量加载数据并设置了相同的过期时间。
  • Redis 监控: 查看 Redis 的 expired_keys 指标是否有瞬时突增。

解决方案:

  • 过期时间加随机值: 在设置 key 的过期时间时,增加一个随机数(例如,过期时间 = 基础时间 + 随机秒数),避免 key 在同一时刻集中失效。
  • 高可用架构: 搭建 Redis 集群,保证 Redis 服务的稳定性。
  • 服务降级与熔断: 在应用层设置降级策略,当数据库压力过大时,可以暂时牺牲非核心业务,返回预设值或错误信息,保证核心服务的可用性。

3. 缓存击穿 (Cache Breakdown)

现象: 某一个热点 Key 在失效的瞬间,有大量的并发请求访问这个 Key,这些请求都会穿透缓存,直接打到数据库上。

排查思路:

  • 热点 Key 识别: 通过业务日志、监控或 Redis 的 hotkeys 功能(Redis 4.0+ 提供)来识别哪些 Key 是热点。
  • 失效时间点确认: 确认问题的发生时间点是否与某个热点 Key 的失效时间点吻合。

解决方案:

  • 设置永不过期: 对于一些热点数据,可以考虑设置为永不过期,由后台服务异步更新缓存。
  • 分布式锁: 在查询数据库之前,使用分布式锁(如 Redisson、SetNX)来控制并发。当一个线程获取到锁后,它负责去数据库查询数据并写回缓存,其他线程则等待或直接返回旧数据。

4. 内存达到上限与数据淘汰

现象: Redis 的 used_memory 达到了 maxmemory 的限制,触发了内存淘汰策略,导致部分数据被动删除,命中率下降。

排查思路:

  • INFO 命令: 检查 INFO memory 部分的 used_memorymaxmemory,以及 INFO stats 部分的 evicted_keys (被淘汰的 key 数量) 是否在持续增加。
  • 检查淘汰策略: 使用 CONFIG GET maxmemory-policy 命令查看当前的内存淘汰策略是否合理。常见的策略有 volatile-lru, allkeys-lru, volatile-ttl 等。

解决方案:

  • 增加内存: 如果业务数据量确实很大,最直接的方法是升级 Redis 实例的内存。
  • 优化淘汰策略: 根据业务场景选择更合适的淘汰策略。例如,如果所有 key 都很重要,可以考虑 noeviction(不淘汰,写操作报错);如果希望淘汰最近最少使用的 key,则 allkeys-lru 是不错的选择。
  • 优化数据结构与大 Key: 检查是否存在大 Key(一个 key 存储了过大的 value),大 Key 不仅占用大量内存,而且在淘汰和访问时也可能造成性能问题。可以使用工具进行扫描分析。

预防与监控

问题解决后,为了防止未来再次发生,需要建立完善的监控和预警机制。

  1. 完善监控体系:

    • 命中率监控: 持续监控 Redis 的缓存命中率,并设置告警阈值。
    • 内存使用率: 监控内存使用情况,在接近 maxmemory 时提前告警。
    • 连接数与慢查询: 监控客户端连接数和 Redis 的慢查询日志。
  2. 规范缓存使用:

    • 评估缓存容量: 在新业务上线前,评估所需缓存容量,避免内存不足。
    • 制定缓存策略: 明确不同业务数据的缓存过期策略,避免缓存雪崩。
    • 代码审查: 在代码评审环节,关注缓存的使用是否合理,是否存在缓存穿透等风险。