Lesson 0005 · 系统设计 · Phase 2 数据层

缓存:策略与三大问题

⏱ 预计 30 分钟 🎯 面试深挖出场率最高的组件,没有之一 📖 前置:Lesson 0002 · Lesson 0004
与你的 Mission 的关系

缓存是面试深挖的「默认考点」:出了名地每场必聊,且三大问题(穿透/击穿/雪崩)的追问几乎是必答题。工作中它也是你最常调优的组件——命中率 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#1key#2 随机读一个)。这和 Lesson 0006 的数据倾斜是同一类问题的不同表现。

§4 · 随堂检测

§5 · 检索练习

凭记忆默写:三大问题的「为什么没挡住」+ 各两个解法。然后回答:写路径为什么是删缓存而不是更新缓存?

核对参考答案

穿透=数据不存在(空值缓存/布隆过滤器);击穿=单热 key 过期(互斥重建/逻辑过期);雪崩=大面积失效(TTL 抖动/集群 HA/熔断兜底)。删缓存:并发更新会交错写入旧值,且写时更新的值未必被读,lazy 重建简单正确。完整表见 缓存模式速查表

§6 · 本周行动

行动任务(约 30 分钟)

审计你公司(或熟悉项目)的缓存:
① 用的什么模式(Cache-Aside?写路径是删还是更新?);
② TTL 是统一固定值吗?有没有抖动?(雪崩风险)
③ 有没有「按不存在 ID 查询」的接口?(穿透风险)
④ 缓存命中率多少?从监控里找出来。

§7 · 延伸资源

💬 你公司缓存的审计结果有什么意外发现?贴出来一起看。