★ 多级缓存架构怎么搭?本地缓存与 Redis 各自扮演什么角色?
结论先行:典型三级是 L1 进程内缓存(Caffeine/Guava)→ L2 Redis → L3 MySQL。本地缓存命中是微秒级且零网络开销,承担最热数据的挡板;Redis 承载大规模共享缓存;数据库兜底。核心难点不在读,而在写后如何让各级缓存及时、不过度地失效。
一、为什么分三级
| 层级 | 延迟量级 | 容量 | 一致性代价 | 定位 |
|---|---|---|---|---|
| L1 Caffeine | 微秒级 | 小(几百 MB) | 各实例不一致,需广播失效 | 挡住大部分热点读 |
| L2 Redis | 毫秒级 | 大 | 全局基本一致 | 共享回源兜底 |
| L3 MySQL | 毫秒~十毫秒 | 无限 | 强一致 | 最终数据源 |
读:L1 未命中 → L2 未命中 → L3 查询 → 回填 L2、L1
写:先更新 DB → 删/更 Redis → 广播清 L1(见第 40 章)
二、L1 的选型与配置
Caffeine.newBuilder()
.maximumSize(10_000) // 容量上限
.expireAfterWrite(30, TimeUnit.SECONDS) // 过期兜底
.recordStats() // 统计命中率
.build(key -> loadFromRedis(key)); // 未命中回源
三、各级缓存职责分工
- L1 只放大颗粒热点数据(商品详情、配置、用户基础信息),容量要克制,防止 GC 压力。
- L2 是所有实例共享的权威缓存,承担穿透保护:空值缓存、布隆过滤器挡不存在的 key。
- 回填防击穿:L2 回源加互斥锁(分布式锁),只让一个请求查库。
四、一致性与失效
- 变更路径:更新 DB → 删除/更新 Redis → 通过 Redis Pub/Sub 或 MQ 广播删除各节点 L1。
- L1 删除失败兜底:短 TTL(30~60 秒),让不一致窗口可控。
- 一致性是性能换来的:接受秒级窗口,不追求缓存强一致。
常见追问与记忆点
- 追问:本地缓存会导致缓存漂移吗?会,节点间 L1 不同步,所以 L1 只放可容忍短暂不一致的数据并配短 TTL。
- 追问:为什么要 Caffeine 而不是自己写 Map?容量/过期/统计/并发控制都要自造,Caffeine 内置 W-TinyLFU 淘汰与异步加载。
- 记忆点:L1 挡热点(微秒)、L2 保共享(毫秒)、DB 兜底;读防击穿穿透,写靠删除 + 广播 + 短 TTL 收敛。