缓存:策略与三大问题
缓存是面试深挖的「默认考点」:出了名地每场必聊,且三大问题(穿透/击穿/雪崩)的追问几乎是必答题。工作中它也是你最常调优的组件——命中率 1% 的波动就是真金白银。本课配合中文世界最好的免费资料(小林 coding)[2]。
§1 · 基本盘缓存旁路模式
应用层缓存的事实标准是 Cache-Aside(旁路缓存)——缓存不由存储层管理,由应用代码自己维护[1]:
读:先查缓存 ──命中──▶ 直接返回
│ 未命中
▼
查数据库 ──▶ 写入缓存(带 TTL)──▶ 返回
写:更新数据库 ──▶ 删除缓存(而不是更新缓存!)
写路径为什么要「删缓存」而不是「更新缓存」?
两个理由:① 并发写会交错——两个写请求 A 先 B 后,但 B 的缓存更新先落地、A 后落地,缓存里留下 A 的旧值;② 很多缓存值是算出来的(聚合、多表拼接),写时更新了也未必被读到,白算。删除后下次读时再重建(lazy),简单且正确。
配套参数:TTL(过期时间)控制脏数据窗口;淘汰策略常用 LRU(最近最少使用);容量按 80/20 法则估——缓存约 20% 的热数据即可挡住大部分读流量(Lesson 0002 的训练器算过这笔账)。
§2 · 三大问题穿透、击穿、雪崩
三者的共同本质:缓存没挡住,压力全砸到数据库。区分它们的关键是「为什么没挡住」。
| 问题 | 场景 | 解法 |
|---|---|---|
| 穿透 Penetration |
查询根本不存在的数据(恶意扫 ID),缓存永远未命中,每次都打 DB | ① 空值也缓存(短 TTL 如 60s);② 布隆过滤器(Bloom filter)前置:一定不存在的 key 直接拒绝 |
| 击穿 Breakdown |
某个热点 key 过期的瞬间,海量请求同时打到 DB 重建 | ① 互斥锁/single-flight:只放一个请求去重建,其余等待;② 逻辑过期:不真删,值里带过期标记异步刷新;③ 热点 key 永不过期 |
| 雪崩 Avalanche |
大量 key 同时过期(或缓存集群整体宕机),DB 瞬间被打垮 | ① TTL 加随机抖动(jitter)错峰;② 缓存集群高可用;③ 熔断降级兜底(Lesson 0010) |
先一句话区分:「穿透是查不存在的数据,击穿是单个热 key 失效,雪崩是大面积失效」;再按「预防 → 兜底」给解法;最后主动补一句权衡:「布隆过滤器有误判率且不支持删除,空值缓存实现简单但占内存——看误报规模选。」
§3 · 两个进阶话题(面试加分项)
缓存一致性的缝隙
「先更新 DB 再删缓存」也有窗口:删完缓存、下一个读还没重建时,又来一个并发写……经典的修补是延迟双删(写后延迟几百毫秒再删一次)。要诚实地说:缓存与 DB 之间只有最终一致,没有免费的一致性——面试里说破这一点就是加分。
热点 key 本身
某个明星的资料、某场直播的房间信息,单个 key 的 QPS 就能打满一个 Redis 分片。解法:本地缓存二级结构(进程内存挡一层)或 key 复制多份打散(key#1、key#2 随机读一个)。这和 Lesson 0006 的数据倾斜是同一类问题的不同表现。
§4 · 随堂检测
§5 · 检索练习
凭记忆默写:三大问题的「为什么没挡住」+ 各两个解法。然后回答:写路径为什么是删缓存而不是更新缓存?
核对参考答案
穿透=数据不存在(空值缓存/布隆过滤器);击穿=单热 key 过期(互斥重建/逻辑过期);雪崩=大面积失效(TTL 抖动/集群 HA/熔断兜底)。删缓存:并发更新会交错写入旧值,且写时更新的值未必被读,lazy 重建简单正确。完整表见 缓存模式速查表。
§6 · 本周行动
审计你公司(或熟悉项目)的缓存:
① 用的什么模式(Cache-Aside?写路径是删还是更新?);
② TTL 是统一固定值吗?有没有抖动?(雪崩风险)
③ 有没有「按不存在 ID 查询」的接口?(穿透风险)
④ 缓存命中率多少?从监控里找出来。
§7 · 延伸资源
- 首选精读:小林 coding · Redis 缓存篇——三大问题的中文最佳讲解[2]
- Cache-Aside Pattern(Microsoft Azure 架构模式文档)[1]
- 参考文档:缓存模式速查表(模式对比 + 三大问题对照,打印版)
- 下一课 → Lesson 0006 分片与一致性哈希