缓存和DB的一致性(深度思考)
Posted 帅东
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了缓存和DB的一致性(深度思考)相关的知识,希望对你有一定的参考价值。
打发时间-东
如何保证缓存和DB的一致性问题
在公司还碰见有人竟然不知道这个问题,或者是想的太简单了。
简单的提几个问题,如果你都知道了就不用往下看了
- 一定要保证缓存和DB一致性么?
- 缓存一定要设置超时时间么?
- 更新数据,先更新缓存还是DB?
- 查询数据,先查缓存,没有就去DB查,然后就写入缓存就ok了?
什么时候需要使用缓存?
- 减少DB压力(高可用)
- 减少RT(高性能)
为什么是高可用?
不知道你有没有想过,有一天DB挂了(压力大 或者 被其他业务/程序打垮了)缓存还能保你一命;不过也取决于你缓存多久,DB修复其实也很快,DBA就是干这个事情的,肯定有很多技术保障DB快速恢复的(扩容、限流),如果你缓存1h,平均下来如果半个小时能修复,就不会太影响你的业务
针对第一个问题:一定要保证缓存和DB一致性?
我的回答是:其实没必要一定保证缓存和DB的一致性,还是取决于你的业务是什么样的,大多数的时候保证最终一致性就可以了
什么时候清理缓存?
如果你使用的是不过期的缓存(现在几乎看不到有人这样使用,如果有人见到这样的代码肯定会“被人骂”)
针对第二个问题:缓存一定要设置超时时间?
我的回答是:不一定需要清理。
还是看具体业务;如果你缓存的东西很明显不会太多,那么久不需要清理,比如:中国城市名词、全球国家二字码、地址库id…
不过大多数的业务还是需要设置超时时间的,如果你简单用map来做缓存,很容易导致内存溢出。
定时清理缓存
现在java提供很多工具包都支持设置key的超时时间,没必要你自己去写一个定时清理的缓存。我经常用的是Guava cache。
如果你要去写(可能你的业务特殊,需要定时部分清理),无非就是两种思路
- 写入key的时候注册回调函数,到时间调用回调函数去清理
- 启动一个定时器,定时去扫描缓存
DB数据有修改
针对第三个问题:更新数据,先更新缓存还是DB?
我的回答是:都可以
有部分人肯定会不加思考的就说,先更新db啊,不然数据有可能不一致啊!
这个上面我就说过了,数据不一定要保证一致性。
案例:在我做实际做一个业务的时候,当初是为了更新“分拣码(不用理解)”,我就选择了先更新缓存,再去更新DB,原因主要有4点:
- 新数据和老数据都能用,实时性要求不高,只是说用新数据对于实操会有所提高(也是提需求更新分拣码的原因)
- 每次更新数据量太大了,最长的可能超过半小时,那么我觉得提前半个小时提高业务操作效率也算划得来
- 更新数据比较简单,没有其他逻辑,就是简单更新一个db的数据,所以失败的概率也比较低
- 就算失败了,我只需要重新消费一次数据,或者重新推一份数据就好了(操作简单)
当然了,一般还是先更新DB,我这种场景比较特殊,不过我想表达的是,作为一个程序员思想不能太固话了,一点都不知道变通,没有什么事情是绝对的坏
如何正确使用缓存(今天的重点)
接下来说的都是对一致性要求比较高的系统,需要先更新DB再更新缓存
针对第四个问题:查询数据,先查缓存,没有就去DB查,然后就写入缓存就ok了?
我的回答是:不ok
思考一个问题:更新缓存失败了怎么办?
凉凉了兄弟,DB数据和缓存不一致了,如果一个业务没有用缓存,那么两个业务查出来的结果不一致了。不过一般不会失败,更新缓存失败这里主要指的是分布式的缓存,不是本地缓存,什么网络延迟啊,还是有可能导致缓存失败的
下面方案1、方案2都是网上看到的,也是惊呆了我,绕来绕去把人搞蒙了
方案1(先删除缓存,再去修改DB)
思考一下,是不是先删除缓存,再去修改DB就好了?
并没有完美解决
线程2抢先更新了数据,这样还是会导致数据不一致
注意:这里第3步和第4步还有可能先后不一样,查询后还得去检查缓存是否是否已存在
方案2(队列+锁)
这种方案确实解决了问题,但是会导致另一个问题:修改的时候,不能读
这对于高性能的系统来说是不可接受的
方案3(DB行锁)
数据库加锁嘛,查询的时候for update行锁不就好了么,哪来方案2那么多事?
不过存在和方案2一样的问题
方案4(半事务)
数据库操作和缓存操作加一个事务?
其实我说的事务是“业务事务”,和数据库的概念一样,主要就是保存之前的快照,异常时执行回滚操作。
更新DB -> 更新缓存 -> 出现异常(如果value很大,那么网络有可能抖动而导致失败) -> 回滚(value大回滚原来的value可能还是失败,所以把value改成空会更好)
其实做到这里已经很完美了,不过还有一种情况是业务事务没办法解决的:应用层挂了的问题;比如我刚好更新完DB,应用挂了,数据还是会不一致
方案5(自研)
如果还有人钻牛角尖,有没有方案可以解决方案4的问题?
想一下,更新缓存的时候到底要不要提前删除缓存?
不提前删除缓存是一定会导致数据不一致的(特殊场景),但是提前删除缓存还是可能导致数据不一致;我的思路就是用锁+提前删除缓存来解决一致性问题,拿不到锁就走DB
所以应用挂了,锁还在,那就走DB呗,等到锁没有了,会自动更新缓存,缓存更新失败还是会一直走db,所以db压力会很大。
优点:解决了一致性问题,无论在什么情况下面,都保证了数据强一致性
缺点:1.如果是分布式系统,锁会变得很重(分布式锁) 2.可能导致缓存击穿
如果你要保证高性能 + 强一致性,那么在极端情况下,那么必然会去数据库查询,也就会导致数据库压力大
总结
没有完美的方案
复杂度低 + 强一致性:方案3(性能差)
复杂度低 + 高性能:方案4(弱一致性)
复杂度高 + 强一致性:方案5(击穿问题)
其实一般没有那么高的一致性要求,而且缓存更新失败概率太低了,通常情况下不会有方案12345;只是在网上看到这样的问题,顺便思考了一下,觉得还挺有意思,有些问题不能细想,一细想问题好多。
以上是关于缓存和DB的一致性(深度思考)的主要内容,如果未能解决你的问题,请参考以下文章