连载中 2/20

Cache Aside:最经典的读写模式

2026-05-25 · 8263 阅读 · 0 评论 · 0 赞

读路径:先缓存,未命中再回源

Cache Aside(旁路缓存)的读路径三步走:先读缓存,命中直接返回;未命中就查库;查到之后顺手写回缓存,再返回。下次同样的请求就在缓存层解决了。

public ItemDetail getItem(Long id) {
    String key = "item:detail:" + id;
    ItemDetail d = redis.get(key);              // 1. 先读缓存
    if (d != null) return d;                    //    命中:直接返回
    d = itemDao.loadDetail(id);                 // 2. 未命中:回源查库
    redis.setex(key, 1800, d);                  // 3. 写回缓存(带 TTL)
    return d;
}

读路径简单,坑都在写路径。写的时候缓存怎么办?是删掉还是更新?先删还是先更库?四个组合里只有一个是最优解。

为什么是「删」而不是「更新」

直觉上「更新库的时候顺便把缓存也更新」天经地义,实际有两个坑:并发写乱序——线程 A 更新库 x=1,线程 B 更新库 x=2,若 B 的缓存更新先到、A 的后到,缓存里留下 1,库里是 2,从此永远差一截;写放大——缓存值往往是多表数据的聚合结果,写一次库要重算整个聚合,写多了读得少就是白算。

「删除」则把难题变简单:删掉之后,下次读自然回源到最新的库——懒加载哲学,用到才算,不提前算。并发的两个写,谁先删谁后删无所谓,删了就是干净的。

为什么「先更库,再删缓存」

剩下的问题是先后顺序。先删缓存再更库:删完到库更新完成之间有个窗口,并发的读请求会回源到旧库值并写回缓存——脏数据要在缓存里住到 TTL 过期,窗口明显、后果持久。先更库再删缓存:理论上也有缝隙——读请求在缓存未命中后回源拿到了旧值,此时写请求恰好完成更库与删缓存,随后读请求把旧值写回。但这个缝隙要求「读比写还慢地跨过整个写过程」,概率极小,且有 TTL 兜底。

所以标准答案是:先更新数据库,再删除缓存。这不是完美解,是概率与代价权衡后的最优解,下篇还会专门算这笔账。

三个变体,各自的舞台

模式做法取舍
Cache Aside应用自己管缓存读写简单可控,主流选择
Read/Write Through缓存层代理读写,应用只面对缓存业务干净,但缓存服务要可靠
Write Behind只写缓存,异步批量刷库写性能极高,宕机可能丢数据

Write Behind 看着诱人——点赞计数、浏览量这类高频写、偶尔丢一点也无所谓的场景是它的主场;但订单、库存碰都别碰。绝大多数业务用 Cache Aside 就够了,把注意力放到它管不住的地方去:缓存里永远查不到的数据——穿透,下一篇的主角。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#CacheAside#旁路缓存#读写模式#懒加载#WriteBehind

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞