缓存设计
缓存的本质:用一致性换性能。设计题主线:分几层、每层放什么、怎么更新、怎么防热点。
缓存分层
两级缓存: 本地快, 分布式共享
| 层 | 载体 | 特点 | 适合 |
|---|---|---|---|
| 本地缓存 | 进程内(Caffeine、dict) | 最快(无网络)、每实例独立 | 不变配置、热 key |
| 分布式缓存 | Redis | 跨实例共享、容量大 | 业务数据、会话 |
| 数据库缓冲 | Buffer Pool | 最底层 | 兜底 |
本地 + 分布式两级是生产标配:本地挡热点,分布式挡穿透。代价是本地缓存一致性更差(每实例一份),适合变化慢的数据。
更新策略
| 策略 | 机制 | 一致性 | 场景 |
|---|---|---|---|
| Cache Aside(旁路) | 读 miss 查库回填,写先更库再删缓存 | 最终一致 | 默认选择 |
| Read Through | 缓存层自己查库回填 | 最终一致 | 缓存封装完整 |
| Write Through | 写库同时写缓存,同步 | 强一致,写放大 | 极少用 |
| Write Back | 只写缓存,异步刷库 | 弱一致,有丢失风险 | 写频繁、容忍丢 |
生产默认 Cache Aside,细节见 Redis 分类的缓存一致性篇(延迟双删、Canal 订阅)。
三大问题预防
穿透、击穿、雪崩的完整解法见 Redis 分类的缓存穿透/击穿/雪崩篇,设计时直接引用:
- 穿透:布隆过滤器或空值缓存
- 击穿:互斥锁重建或逻辑过期
- 雪崩:TTL 随机化、多级缓存
Key 与 TTL 设计
- Key 规范:
业务:对象:ID(user:123:profile),可读、可批量、可分区 - TTL 分层:热点数据短 TTL 或逻辑过期(防击穿),静态数据长 TTL
- 热点识别:统计访问频率,热 key 本地缓存兜一层(见 Redis 大 key/热 key 篇)
- 容量规划:单 key 别过大(大 key 问题),估算总容量,配淘汰策略
- 缓存粒度:细粒度(单字段)灵活但请求多;粗粒度(整对象)简单但浪费。按读写模式权衡
面试追问
- 缓存分几层? 本地 + 分布式两级。本地最快但有实例间一致性问题,分布式共享但多一次网络
- Cache Aside 为什么默认? 简单、可控性强。写多读少或强一致场景考虑其他策略或绕过缓存
- 本地缓存的一致性怎么保证? 靠版本号/失效通知(Redis pub/sub 或 MQ 广播失效),或只放容忍延迟的数据
- TTL 怎么设计? 防雪崩随机化、防击穿热点用逻辑过期、静态数据长 TTL。TTL 是权衡:太短命中率低,太长一致性差
- 缓存设计的最重要原则? 一致性换性能,明确业务能容忍多旧的数据,再决定层数和策略