Three Read/Write Patterns
| 模式 | 读路径 | 写路径 | 特点 |
|---|
| 旁路缓存 Cache-Aside | miss 后回查 DB 并回写 | 先更 DB 再删缓存 | 最常用,需处理不一致 |
| 读穿透 Read-Through | 缓存层同步回源 | 缓存层写入并同步到 DB | 逻辑集中在缓存层 |
| 异步写 Write-Behind | 同 Read-Through | 只更缓存,异步刷 DB | 写最快,容忍丢数据 |
Write deletes the cache instead of updating it: concurrent updates make an overwritten value stick, and most caches lack fine-grained write APIs. Deletion is idempotent and simpler to reason about.
The Classic Inconsistency Window
线程 A 更新 DB → 删缓存
线程 B 读:缓存 miss → 查 DB → 回写旧值
兜底:
1) 缓存过期(防爆 + 最终一致)
2) 延迟双删:删缓存 → 更新 DB → 延迟 1s 再删一次
3) 订阅 binlog 异步删缓存(Canal/Debezium)——最干净
问答:先删缓存再更新 DB?
更差:删完到 DB 更新之间,别的读把旧值又写回缓存
强一致场景别硬拆缓存:直接查库或用「版本号 + 读时校验」
Penetration, Breakdown, Avalanche
| 问题 | 现象 | 解法 |
|---|
| 穿透 | 查不存在的 key,每次打到 DB | 空值缓存(短 TTL)/ 布隆过滤器 |
| 击穿 | 热点 key 过期瞬间高并发回源 | 互斥锁重建 / 永不过期 + 异步更新 |
| 雪崩 | 大量 key 同一时刻过期 | TTL 加随机抖动 / 多级缓存 / 限流降级 |
互斥重建(GO 伪码):
v = GET(k)
if v == nil:
if TRYLOCK(k): # SET nx px 拿到重建权
v = DB(k); SET(k, v, ttl + rand); UNLOCK
else:
sleep(50ms); 重试 # 没抢到的稍等再读
布隆过滤器(Redisson Bloom / 本地位图):
写入时加位;查询前先判存在
可删的用「布谷鸟过滤器」,普通布隆不能删元素
Multi-Level and Local Caches
- JVM/进程内本地缓存(Caffeine)挡 9 成流量,Redis 兜第二级,DB 最后。
- Local caches get stale: size them small, set short TTLs, and listen to invalidation events (Redis pub/sub or MQ).
- Size with Caffeine: maximumSize + expireAfterWrite + recordStats for hit rate.
- Remember the effective metric is DB pressure, not cache hit rate alone.
三种读写模式
| 模式 | 读路径 | 写路径 | 特点 |
|---|
| 旁路缓存 Cache-Aside | miss 后回查 DB 并回写 | 先更 DB 再删缓存 | 最常用,需处理不一致 |
| 读穿透 Read-Through | 缓存层同步回源 | 缓存层写入并同步到 DB | 逻辑集中在缓存层 |
| 异步写 Write-Behind | 同 Read-Through | 只更缓存,异步刷 DB | 写最快,容忍丢数据 |
写操作删缓存而不是更新缓存:并发更新时旧值可能覆盖新值,且大多数缓存没有细粒度更新 API。删除是幂等的,推理更简单。
经典不一致窗口
线程 A 更新 DB → 删缓存
线程 B 读:缓存 miss → 查 DB → 回写旧值
兜底:
1) 缓存过期(防爆 + 最终一致)
2) 延迟双删:删缓存 → 更新 DB → 延迟 1s 再删一次
3) 订阅 binlog 异步删缓存(Canal/Debezium)——最干净
问答:先删缓存再更新 DB?
更差:删完到 DB 更新之间,别的读把旧值又写回缓存
强一致场景别硬拆缓存:直接查库或用「版本号 + 读时校验」
穿透、击穿、雪崩
| 问题 | 现象 | 解法 |
|---|
| 穿透 | 查不存在的 key,每次打到 DB | 空值缓存(短 TTL)/ 布隆过滤器 |
| 击穿 | 热点 key 过期瞬间高并发回源 | 互斥锁重建 / 永不过期 + 异步更新 |
| 雪崩 | 大量 key 同一时刻过期 | TTL 加随机抖动 / 多级缓存 / 限流降级 |
互斥重建(GO 伪码):
v = GET(k)
if v == nil:
if TRYLOCK(k): # SET nx px 拿到重建权
v = DB(k); SET(k, v, ttl + rand); UNLOCK
else:
sleep(50ms); 重试 # 没抢到的稍等再读
布隆过滤器(Redisson Bloom / 本地位图):
写入时加位;查询前先判存在
可删的用「布谷鸟过滤器」,普通布隆不能删元素
多级缓存与本地缓存
- JVM/进程内本地缓存(Caffeine)挡九成流量,Redis 兜第二级,DB 最后。
- 本地缓存会脏:容量小、TTL 短、监听失效广播(Redis 发布订阅或 MQ)主动清理。
- Caffeine 配置:maximumSize + expireAfterWrite + recordStats 观察命中率。
- 衡量指标看 DB 压力,而不只是缓存命中率。