2. Redis缓存架构回顾
在讨论热点数据治理与缓存穿透之前,有必要把 Redis 在业务系统中的位置重新梳理一遍。很多穿透问题并不是 Redis 本身“失效”,而是缓存模式选型、读写路径设计、过期与淘汰策略之间没有形成闭环,导致请求在某个环节绕过了缓存,直接压到数据库。本节把缓存模式、读写路径、过期策略、淘汰策略、热点数据与缓存穿透放在同一张关系图里理解,为后续治理手段建立判断依据。
一、两种主流缓存模式决定了读写路径
业务中最常见的是 Cache-Aside(旁路缓存):应用代码自己负责读写缓存和数据库。读路径是“先查缓存,命中则返回;未命中则查数据库,再回写缓存”。写路径通常是“先更新数据库,再删除缓存”,而不是更新缓存,因为删除更简单,也能避免并发写导致的脏数据。另一种是 Read/Write-Through,由缓存组件或中间层代理数据库读写,应用只面对缓存。它简化了业务代码,但对 Redis 来说需要额外组件支撑,实际项目里 Cache-Aside 占比更高。
读写路径一旦确定,穿透的入口也就确定了。Cache-Aside 下,未命中回源是正常逻辑,但如果大量请求查询的是数据库中根本不存在的 key,每次都会“未命中→查库→回写空值或什么都不写”,缓存形同虚设。这就是缓存穿透的典型形态。
二、过期策略与淘汰策略是两套不同机制
需要区分两个容易混淆的概念。过期策略针对设置了 TTL 的 key:Redis 采用惰性删除加定期删除。惰性删除是访问 key 时才检查是否过期;定期删除是每秒若干次随机抽查部分设置了过期时间的 key。两者结合意味着:一个已过期的 key 可能在过期后仍短暂占用内存,直到被访问或抽查到。淘汰策略则在内存达到 maxmemory 时触发,决定淘汰哪些 key,常见的有 noeviction、allkeys-lru、allkeys-lfu、volatile-lru、volatile-lfu、volatile-ttl 等。
这两套机制直接影响热点数据。若使用 volatile-* 策略,只有设置了过期时间的 key 才可能被淘汰;若热点 key 没有 TTL,内存打满后可能触发写入失败或淘汰掉其他 key。若使用 allkeys-lru,长期不被访问但重要的 key 也可能被淘汰,随后引发回源。热点数据被淘汰的瞬间,大量并发请求同时未命中,就会形成“缓存击穿”式的数据库压力,它与穿透的区别在于:击穿查的是存在的数据,穿透查的是不存在的数据。
三、热点数据、过期、淘汰与穿透的关联点
把上述机制串起来看,穿透的发生往往同时具备几个条件:查询的 key 在数据库中不存在;缓存没有对空结果做短期占位;过期或淘汰让本可拦截的请求失去缓存保护;热点访问放大了未命中的并发量。反过来,治理思路也来自这些关联点:对空结果缓存短 TTL、对热点 key 延长或刷新 TTL、合理选择淘汰策略避免误淘汰、在入口层做参数校验和布隆过滤器预判。
下面这张图把缓存模式、读写路径、过期/淘汰策略与穿透、击穿的关系放在一起,便于对照判断请求在哪个环节绕过了缓存。
四、从关联点出发的排查顺序
当线上出现数据库压力骤增时,可以按下面的顺序快速定位属于哪类问题。先看请求的 key 是否在数据库中真实存在:如果大量 key 查不到,优先怀疑穿透,检查是否对空结果做了缓存、是否缺少参数校验或布隆过滤器。如果 key 存在但缓存命中率骤降,检查热点 key 的 TTL 是否集中到期、是否被淘汰策略误删、Redis 内存是否达到 maxmemory。再看淘汰策略配置:volatile-lru 下没有 TTL 的 key 不会被淘汰,可能导致内存写满后写入失败;allkeys-lru 下热点 key 可能被冷 key 挤出。最后确认过期与淘汰是否与业务 TTL 设计匹配,例如热点数据 TTL 过短、批量 key 同一时刻过期,都会制造周期性回源高峰。
把这一节的关系记牢,后续讨论布隆过滤器、空值缓存、热点 key 续期、多级缓存和限流降级时,就能清楚每个手段作用在链路的哪一段、解决的是哪一类失效,而不是把所有问题都笼统地归为“缓存没命中”。