← 返回分布式系统
⚡ 分布式缓存 · 面试必问

分布式缓存

缓存是提升性能的"第一杠杆",但"缓存与数据库一致性"和"穿透 / 击穿 / 雪崩"三大问题是面试几乎必考的送命题。

📝 一、缓存更新策略

策略读写评价
Cache Aside(旁路)先查缓存,未命中查 DB 再回写先更新 DB,再删缓存最常用,简单可控
Read/Write Through应用只操作缓存,由缓存层同步落库对应用透明,但实现复杂
Write Behind—只写缓存,异步批量落库性能极高,但可能丢数据
✅
业界主流 = Cache Aside:读"缓存 miss → 查 DB → 回填";写"更新 DB → 删除缓存"。为什么删而不是更新缓存? 避免并发写导致缓存脏数据,且懒加载更高效。

🕳️ 二、缓存穿透:查不存在的数据

请求的数据在缓存和数据库里都不存在(如恶意刷 id=-1),每次都穿透到 DB,缓存形同虚设,DB 被打爆。

解法
① 缓存空值:把"查无此物"也缓存一个短 TTL 的空结果,挡住重复请求;② 布隆过滤器(Bloom Filter):在缓存前加一层,快速判断"这个 key 一定不存在就直接拒绝";③ 接口层做参数校验/限流。
💡
布隆过滤器用位数组+多个哈希,说"不存在"就一定不存在,说"存在"可能误判——用极小空间挡住绝大多数穿透攻击。

💥 三、缓存击穿:热点 key 突然过期

某个极端热点 key 在失效瞬间,海量并发同时打到 DB——一个 key 的"过期"引发雪崩式冲击(区别于全局雪崩)。

解法
① 互斥锁 / 分布式锁:只允许一个线程去查 DB 并回写,其余等待重试;② 逻辑过期:缓存不真设 TTL,value 里带过期时间,发现"逻辑过期"时异步重建、旧值先返回;③ 热点 key 设不过期(靠后台刷新)。

🌨️ 四、缓存雪崩:大量 key 同时失效

大量缓存 key 在同一时刻集中过期,或缓存服务整体宕机,请求全部涌向 DB,导致 DB 瞬间过载甚至连锁崩溃。

解法
① 过期时间加随机抖动(如 base±random),避免集体到点;② 多级缓存(本地 + Redis + DB)降低对单点的依赖;③ 高可用:Redis 集群 + 限流熔断降级;④ 服务层做限流/熔断保护 DB。
⚠️
三者的区别:穿透=查"不存在"的数据;击穿=一个"热点"过期;雪崩=大量 key "同时"过期/缓存挂掉。一个针对非法 key,后两个针对过期与集群失效。

🔄 五、缓存与数据库双写一致性

先更新 DB 还是先删缓存?经典答案:先更新数据库,再删除缓存(Cache Aside)。但仍存在极小窗口问题。

为什么"先删缓存再更 DB"更糟
A 删缓存 → B 读 miss 读旧 DB 回填旧值 → A 更 DB。结果缓存是旧值,长期不一致。
"先更 DB 再删缓存"的残留窗口
读请求在"DB 更完、缓存未删"之间读到旧值并回填。概率极低(要求读比写慢),但存在。
加强:延迟双删 / 订阅 binlog
写后延迟再删一次;或 canal 订阅 MySQL binlog,异步删除/更新缓存,最终一致更稳。
💡
现实取舍:绝大多数业务接受"Cache Aside + 最终一致"。要求强一致时,让读请求也走 DB 或加锁,但会损失性能。

🎯 六、面试高频追问

穿透、击穿、雪崩分别怎么答?
先点明区别(非法 key / 单点过期 / 集体过期),再各给对策:空值+布隆过滤 / 互斥锁+逻辑过期 / 随机 TTL+多级缓存+限流熔断。
先更新 DB 还是先删缓存?
先更新 DB,再删缓存(Cache Aside)。原因:反序会造成"删缓存→读旧值回填→更新DB"的脏数据;正序窗口更小。
布隆过滤器误判怎么办?
误判只发生在"说存在其实可能不存在"——此时会放行进 DB 一次,但绝不会漏掉"真正不存在"的。配合空值缓存即可兜底。

延伸阅读:分布式锁 · 数据复制