Java教程

缓存和DB的一致性问题

本文主要是介绍缓存和DB的一致性问题,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

打发时间-东

  • 如何保证缓存和DB的一致性问题
    • 什么时候需要使用缓存?
          • 为什么是高可用?
    • 什么时候清理缓存?
      • 定时清理缓存
      • DB数据有修改
    • 如何正确使用缓存(今天的重点)
      • 方案1(先删除缓存,再去修改DB)
      • 方案2(队列+锁)
      • 方案3(DB行锁)
      • 方案4(半事务)
      • 方案5(自研)
    • 总结

如何保证缓存和DB的一致性问题

在公司还碰见有人竟然不知道这个问题,或者是想的太简单了。

简单的提几个问题,如果你都知道了就不用往下看了

  1. 一定要保证缓存和DB一致性么?
  2. 缓存一定要设置超时时间么?
  3. 更新数据,先更新缓存还是DB?
  4. 查询数据,先查缓存,没有就去DB查,然后就写入缓存就ok了?

什么时候需要使用缓存?

  1. 减少DB压力(高可用)
  2. 减少RT(高性能)
为什么是高可用?

不知道你有没有想过,有一天DB挂了(压力大 或者 被其他业务/程序打垮了)缓存还能保你一命;不过也取决于你缓存多久,DB修复其实也很快,DBA就是干这个事情的,肯定有很多技术保障DB快速恢复的(扩容、限流),如果你缓存1h,平均下来如果半个小时能修复,就不会太影响你的业务

针对第一个问题:一定要保证缓存和DB一致性?

我的回答是:其实没必要一定保证缓存和DB的一致性,还是取决于你的业务是什么样的,大多数的时候保证最终一致性就可以了

什么时候清理缓存?

如果你使用的是不过期的缓存(现在几乎看不到有人这样使用,如果有人见到这样的代码肯定会“被人骂”)

针对第二个问题:缓存一定要设置超时时间?

我的回答是:不一定需要清理。
还是看具体业务;如果你缓存的东西很明显不会太多,那么久不需要清理,比如:中国城市名词、全球国家二字码、地址库id…
不过大多数的业务还是需要设置超时时间的,如果你简单用map来做缓存,很容易导致内存溢出。

定时清理缓存

现在java提供很多工具包都支持设置key的超时时间,没必要你自己去写一个定时清理的缓存。我经常用的是Guava cache。
如果你要去写(可能你的业务特殊,需要定时部分清理),无非就是两种思路

  1. 写入key的时候注册回调函数,到时间调用回调函数去清理
  2. 启动一个定时器,定时去扫描缓存

DB数据有修改

针对第三个问题:更新数据,先更新缓存还是DB?

我的回答是:都可以
有部分人肯定会不加思考的就说,先更新db啊,不然数据有可能不一致啊!
这个上面我就说过了,数据不一定要保证一致性。
案例:在我做实际做一个业务的时候,当初是为了更新“分拣码(不用理解)”,我就选择了先更新缓存,再去更新DB,原因主要有4点:

  1. 新数据和老数据都能用,实时性要求不高,只是说用新数据对于实操会有所提高(也是提需求更新分拣码的原因)
  2. 每次更新数据量太大了,最长的可能超过半小时,那么我觉得提前半个小时提高业务操作效率也算划得来
  3. 更新数据比较简单,没有其他逻辑,就是简单更新一个db的数据,所以失败的概率也比较低
  4. 就算失败了,我只需要重新消费一次数据,或者重新推一份数据就好了(操作简单)

当然了,一般还是先更新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的一致性问题的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!