3. 缓存更新策略全景
缓存一致性问题的本质,是同一份数据同时存在于数据库与缓存两个位置,而任何一次写操作都必须在两者之间做出“谁先谁后、谁负责同步”的取舍。围绕这个取舍,工程上形成了三类主流更新策略:Cache Aside(旁路缓存)、Read/Write Through(读写穿透)与Write Behind(回写/写后写)。它们并不是互斥的“对错方案”,而是面向不同读写比例、一致性容忍度和实现复杂度的不同边界选择。
一、Cache Aside:应用层主导的双写编排
Cache Aside 是最常见的策略,其核心特征是:缓存不主动感知数据库,所有读写编排由应用代码完成。读路径为“先查缓存,命中则返回;未命中则查数据库,再回填缓存”。写路径的经典做法是“先更新数据库,再删除缓存”,而不是更新缓存。
为什么写路径推荐“删除”而非“更新”?因为更新缓存需要应用自己计算新值,若并发写导致计算顺序与落库顺序不一致,缓存可能被写入旧值;而删除缓存把“重新加载”的责任交给下一次读,天然避免了这一风险。下面是一个典型的读回填与写删除示例:
// 读:Cache Aside
String get(String key) {
String v = redis.get(key);
if (v != null) return v; // 命中
v = db.query(key); // 未命中,回源
if (v != null) redis.setex(key, 300, v); // 回填,TTL 300s
return v;
}
// 写:先更新库,再删除缓存
void update(String key, String val) {
db.update(key, val); // 1. 落库
redis.del(key); // 2. 删除缓存
}
Cache Aside 的边界在于:它不保证强一致。在“读未命中回填”与“写删除”并发时,可能出现旧值被回填的窗口。缓解手段包括:给缓存设置合理 TTL 作为兜底、对同一 key 的读写加分布式锁、或采用“延迟双删”(更新库后删一次,短暂延迟后再删一次)。它适合读多写少、能接受最终一致、且希望实现简单的业务,例如商品详情、用户资料。
二、Read/Write Through:缓存层代理数据库访问
Read/Write Through 把同步责任从应用下沉到缓存层。应用只与缓存交互,缓存组件自身负责在未命中时从数据库加载(Read Through),以及在写入时同步更新数据库(Write Through)。应用代码不再出现“先查库再回填”的编排逻辑。
两者的关键差异是:Cache Aside 中“回源”由应用完成,缓存是被动存储;Read/Write Through 中“回源与落库”由缓存代理完成,缓存是主动的数据访问层。这带来更干净的调用方代码,但要求缓存组件具备数据库连接与映射能力,常见于本地缓存框架(如 Guava/Caffeine 配合自定义 Loader)或部分托管缓存服务。
Write Through 的写路径是“写缓存的同时写数据库”,因此写延迟包含数据库写入耗时,吞吐受数据库限制。它适合读多写少、且希望应用层逻辑统一的场景,但实现成本高于 Cache Aside。
三、Write Behind:异步批量回写换取吞吐
Write Behind(又称 Write Back)在 Write Through 基础上进一步解耦:写操作只更新缓存并标记为“脏”,由后台线程异步、批量地将脏数据刷回数据库。它的优势是写延迟极低、可合并同一 key 的多次写,显著提升写吞吐;代价是一致性最弱——缓存已更新而数据库尚未落盘,此时若缓存节点宕机,未刷回的数据会丢失。
因此 Write Behind 的适用边界非常明确:适合写密集、可容忍短暂数据丢失、且能接受最终一致的场景,例如计数器、点赞数、会话状态、监控指标聚合。使用时通常需要配合持久化队列或 WAL,降低宕机丢数据风险。
四、如何选择:从一致性、延迟与复杂度三个维度判断
三种策略的取舍可以归纳为一张判断表。需要强调的是,一致性越强,通常写延迟越高、实现越复杂;反之,吞吐越高,一致性越弱。
| 策略 | 写路径 | 一致性 | 写延迟 | 实现复杂度 | 典型场景 |
|---|---|---|---|---|---|
| Cache Aside | 更新库 → 删缓存 | 最终一致 | 低 | 低 | 商品详情、用户资料 |
| Read/Write Through | 缓存同步写库 | 较强 | 中(含库耗时) | 中 | 统一访问层、读多写少 |
| Write Behind | 写缓存 → 异步刷库 | 最弱 | 极低 | 高 | 计数器、点赞、会话状态 |
理解这三种策略的流程与边界,是后续讨论延迟双删、分布式锁、消息队列补偿等具体一致性方案的基础。实际工程中,同一系统内不同数据往往采用不同策略:核心交易数据用 Cache Aside 加锁保证,统计类数据用 Write Behind 提升吞吐,这正是“策略全景”真正的价值所在。