BG:后端秋招选手,个人公众号TccodeT,欢迎关注,一起交流!
本篇文章参考自http://kaito-kidd.com/2021/09/08/how-to-keep-cache-and-consistency-of-db/,写写自己的理解,欢迎留言发表你的看法
没有完美的解决方案,技术方案的考虑需要服务于具体的场景和架构才有意义
1️⃣ 我们可以先看看缓存和DB一致性问题出现的场景源于什么:提升性能!
,说明仅靠DB的读写已经无法满足当前并发的需求,所以考虑加入缓存。
2️⃣加入缓存后DB如何将数据同步至缓存呢?最简单的方案是使用一个**定时任务
,全量刷新
**到缓存当中
问题1:定时任务,数据的**
不一致性
程度取决于更新时间频率
问题2:全量同步,但其实并非所有数据都会被经常访问,缓存利用率不高
**
3️⃣针对缓存**利用率不高
的问题,我们可以考虑查数据时再进行构建缓存
,而并非把所有数据
**都放入缓存。同时对缓存添加过期时间,这样缓存中的数据就是咱们常用的数据
了
4️⃣关于一致性问题的话,就需要考虑写数据时同时更新DB和缓存:是先写DB再更新缓存
,还是说先更新缓存再写DB
呐?我们从阶段异常
和并发
两个角度考/虑
- 若不考虑并发情况,两步都成功的话两个方案都能保证一致。关键是若有一步失败了呢?假设a-先写DB[成功] b-再写缓存[失败]<
另一种情况同理,在异常情况下都会有不一致问题
- 若不考虑异常情况,两步都成功的话,我们考虑下并发时可能产生的问题
解决方案可以是加分布式锁,但这样性能又降低了。并且**
每次更新数据
时,都无脑更新缓存
,但其实这个数据可能并不常用
。总和以上两点:【更新缓存+更新DB的方式并不适用,所以考虑更新DB
+删除缓存
**的方案】
5️⃣那对于优化方案,我们再从**阶段异常
和并发
**两个角度考虑其一致性情况.此时其实同样有两个方案,是先删缓存再更新DB,还是先更新DB再删缓存?
-
阶段异常:对于先删再更新,如果更新失败,
没有数据不一致
吧[都是旧值];先更新再删除,会有短暂不一致,直到数据过期才可
-
并发问题:
先删再更新
,线程1来写数据(将A准备改为B),刚删除缓存A,线程2来读并构建缓存,读的就是旧的数据A而DB已经是B了;会有数据不一致问题;先更新再删除
,会有问题吗?也有,线程1来读数据{且刚好数据没有缓存},则需要从DB中读取构建缓存;线程2再其构建缓存完成之前
先更新DB在删除缓存,此时线程1缓存刚构建成功{旧值}
,但DB是新值
。
仔细分析下会发现上述情况发生的概率极小,需要同时满足: 1.刚好没有缓存,需要重新构建 2.且线程1构建缓存的时间 《慢于》线程2更新DB和删除缓存的时间 但缓存操作一般都比数据库要快很多
6️⃣所以说**先更新DB再删除缓存
**是可以解决并发
时一致性问题的,那对于异常的情况
,如何保证其两步都成功呢——重试
!这个思维逻辑要建立起来。
重试我们也尽量考虑异步重试
,因为同步很容易阻塞主线程同时消耗资源。引入消息队列来进行异步重试,同时也可以利用消息队列自身的可靠性来保障一定会成功执行。此时的方案就如下所示
注意:因为引入了异步操作,所以短时间内是会有不一致性问题的。
7️⃣总结:一致性的接受程度取决于项目的性质,我个人认为大部分项目对于强一致性的追求并没多少,反而是关注最终一致性通过妥协短暂的不一致来大幅提升性能。这个和我们的分布式事务原理也比较类似[2PC,TCC,事务消息],也是同样的权衡逻辑。引用一句原作者的话,我比较赞同**“掌握缓存和数据库一致性问题,核心问题有 3 点:缓存利用率、并发、缓存 + 数据库一起成功问题”
**