排查redis线上命中率暴跌的问题
紧急排查与快速恢复
这个阶段的目标是快速判断问题的严重程度,并尝试用最简单的方式恢复服务。
-
确认问题范围
- 影响面有多大? 是单个业务、单个 Redis 实例,还是所有业务都受影响?
- 问题发生时间点? 是否与某个版本发布、配置变更或线上活动有关?
-
检查应用层监控
- 应用日志: 查看应用服务器的日志,检查是否有大量的 Redis 连接超时、读取错误或其它异常日志。
- 接口响应时间 (RT): 观察受影响业务的接口 RT 是否显著增加,数据库的慢查询日志是否增多。
- 数据库负载: 检查数据库的 CPU、IOPS 和连接数等指标是否飙升。如果数据库压力大,说明大量的请求穿透了缓存,直接打到了数据库。
-
检查 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_memory和maxmemory,以及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 不仅占用大量内存,而且在淘汰和访问时也可能造成性能问题。可以使用工具进行扫描分析。
预防与监控
问题解决后,为了防止未来再次发生,需要建立完善的监控和预警机制。
-
完善监控体系:
- 命中率监控: 持续监控 Redis 的缓存命中率,并设置告警阈值。
- 内存使用率: 监控内存使用情况,在接近
maxmemory时提前告警。 - 连接数与慢查询: 监控客户端连接数和 Redis 的慢查询日志。
-
规范缓存使用:
- 评估缓存容量: 在新业务上线前,评估所需缓存容量,避免内存不足。
- 制定缓存策略: 明确不同业务数据的缓存过期策略,避免缓存雪崩。
- 代码审查: 在代码评审环节,关注缓存的使用是否合理,是否存在缓存穿透等风险。