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