[转]缓存构架经验总结 – 5.2. Cache Aside Pattern
在《5.1 究竟先操作缓存,还是数据库?》,有同学在评论提出,相关方案违背了“Cache Aside Pattern”的原则,故今天聊一聊Cache Aside Pattern。
另外,在讨论技术方案时,尽量不说:
“你是错的,应该怎么样”
“facebook不是这样,所以你是错的”
画外音:凭什么facebook就是真理?它的方案只是适合它的业务而已。
说明适用场景,说明来龙去脉,说明前因后果,比具体使用什么方案更重要。
什么是“Cache Aside Pattern”?
答:旁路缓存方案的经验实践,这个实践又分读实践,写实践。
对于读请求
- 先读cache,再读db
- 如果,cache hit,则直接返回数据
- 如果,cache miss,则访问db,并将数据set回缓存
如上图:
(1)先从cache中尝试get数据,结果miss了
(2)再从db中读取数据,从库,读写分离
(3)最后把数据set回cache,方便下次读命中
画外音:这一点上,与《5.1 究竟先操作缓存,还是数据库?》说的是一致的。
对于写请求
- 淘汰缓存,而不是更新缓存
- 先操作数据库,再淘汰缓存
如上图:
(1)第一步要操作数据库,第二步操作缓存
画外音:这一点上,与《5.1 究竟先操作缓存,还是数据库?》说的不一致,也是评论反驳比较激烈的地方。
(2)缓存,采用delete淘汰,而不是set更新
画外音:这一点上,与《 4. 缓存,究竟是淘汰,还是修改?》说的是一致的。
Cache Aside Pattern为什么建议淘汰缓存,而不是更新缓存?
答:如果更新缓存,在并发写时,可能出现数据不一致。
如上图所示,如果采用set缓存。
在1和2两个并发写发生时,由于无法保证时序,此时不管先操作缓存还是先操作数据库,都可能出现:
(1)请求1先操作数据库,请求2后操作数据库
(2)请求2先set了缓存,请求1后set了缓存
导致,数据库与缓存之间的数据不一致。
所以,Cache Aside Pattern建议,delete缓存,而不是set缓存。
面对缓存,有哪些问题需要思考 一文中,使用 @CacheDeleteTransactional 注解后,AutoloadCache 会先使用 ThreadLocal 缓存要删除缓存 key,等事务提交后再去执行缓存删除操作。其实不能说是“解决不一致问题”,而是 缓解 而已。
Cache Aside Pattern为什么建议先操作数据库,再操作缓存?
答:如果先操作缓存,在读写并发时,可能出现数据不一致。
如上图所示,如果先操作缓存。
在1和2并发读写发生时,由于无法保证时序,可能出现:
(1)写请求淘汰了缓存
(2)写请求操作了数据库(主从同步没有完成)
(3)读请求读了缓存(cache miss)
(4)读请求读了从库(读了一个旧数据)
(5)读请求set回缓存(set了一个旧数据)
(6)数据库主从同步完成
导致,数据库与缓存的数据不一致。
所以,Cache Aside Pattern建议,先操作数据库,再操作缓存。
Cache Aside Pattern方案存在什么问题?
答:如果先操作数据库,再淘汰缓存,在原子性被破坏时:
(1)修改数据库成功了
(2)淘汰缓存失败了
导致,数据库与缓存的数据不一致。
如何解决这类问题呢?
答:详见《5.1 究竟先操作缓存,还是数据库?》。
任何技术方案的设计,都是折衷。
只有适合的方案,未必有最优的方案。
技术人,不是被动接受,而要主动思考。