Skip to content

缓存是把双刃剑:三大问题和一致性难题 ​

属于 S2 Redis 深入 · 第三篇 上一篇:持久化与高可用 下一篇:分布式锁与场景

Redis 最常见的角色是"缓存"——放在数据库前面,扛住读流量。但缓存带来收益的同时,也引入了新问题:请求会绕过缓存打爆数据库,以及"缓存里的数据和数据库不一致"。这一篇就讲这四件事。

穿透、击穿、雪崩:三个词,三种病 ​

这三个词名字像,但病因完全不同,面试最爱考的就是它们的区别。

缓存穿透:查的是根本不存在的数据。既然不存在,缓存里也不会有,于是每个请求都穿过缓存打到数据库——恶意攻击者可以用海量不存在的 key 把数据库打垮。解法有两个:一是缓存空值(查不到也缓存个 null,短 TTL),二是布隆过滤器(把所有存在的 key 存进去,请求先判断,不存在的直接拦)。布隆的特点是"不存在的 key 一定被拦,但可能误判存在"。

缓存击穿:某个热点 key 过期的一瞬间,海量请求同时打到数据库。因为热点 key 平时扛着巨量流量,一过期就像堤坝决口。解法核心是"只让一个请求回源":互斥锁(SETNX 抢锁,只有一个线程去查库重建)或逻辑过期(key 不真过期,值里带逻辑过期时间,过期了后台异步重建,其他人先用旧值)。

缓存雪崩:大量 key 同时过期(或 Redis 宕机),请求全涌向数据库。解法是让过期时间加随机抖动、上多级缓存、以及限流降级兜底。

一句话区分:穿透是"查不存在的数据",击穿是"一个热点过期",雪崩是"一堆 key 一起过期"。

缓存一致性:缓存和数据库怎么对齐 ​

只要用了缓存,就有"缓存和数据库数据不一致"的风险。最常用的策略是 Cache Aside(旁路缓存):

  • 读:先读缓存,miss 再读数据库,回填缓存。
  • 写:先更新数据库,再删缓存。

写操作的顺序很关键。为什么不是"先删缓存再更新库"?因为那样会有个窗口:删了缓存 → 别的线程读到旧数据回填 → 你才更新库,结果缓存里是旧数据。而"先更新库再删缓存",配合删(而不是更新)缓存,能最大程度避开并发写覆盖。

但删除缓存可能失败,导致永久不一致。更强的方案是订阅 binlog(比如用 Canal):数据库一变更,binlog 事件触发异步删缓存,失败还能重试,保证最终一致。

要认清一个现实:缓存和数据库天然做不到强一致(中间总有时间窗口),工程上接受"最终一致",用"先更新库再删缓存 + 失败重试/订阅 binlog"兜底。


串起来 ​

缓存带来的四个问题,本质都是"缓存和数据库之间的错位":穿透/击穿/雪崩是"请求怎么绕过缓存打爆库",一致性是"两边的数据怎么对齐"。理解了病因,解法就都是对症下药——拦截无效请求、控制回源并发、错开过期时间、保证删缓存可靠。

下一篇讲分布式锁与场景:多台机器同时执行一段代码,怎么保证只有一个成功?Redis 能做分布式锁,但它真的可靠吗?

持续学习,持续构建。