【每日一题】如何保证缓存和数据库的一致性?

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

缓存是许多现代Web应用程序的重要组成部分,因为它可以大大提高系统的性能和可靠性。由于缓存和数据库是分开的,无法做到原子性的同时进行数据修改,所以如何保证数据库和缓存之间的数据一致性是一个非常重要的问题,尤其是在高并发和大数据量的情况下。以下是一些常见的方法和策略,可以帮助确保数据库和缓存之间的数据一致性。

Cache Aside Pattern

Cache Aside Pattern(缓存备用模式) 是一种常见的缓存策略,主要用于保证缓存和数据库数据的一致性。它的核心思想是:当需要从缓存中获取数据时,先从缓存中获取,如果缓存中没有,则从数据库中获取,同时将数据缓存起来,然后再返回给调用方。当需要更新数据时,先更新数据库,然后再将缓存中的数据删除,以保证下次访问时从数据库中获取最新数据。

Cache Aside Pattern 保证缓存和数据库的一致性的步骤是先更新数据库,再删缓存中的数据 ,一定程度上它的确是能解决数据不一致的问题。但是在高并发场景下它也会造成数据不一致的情况:A线程操作数据,在A线程更新数据库之后,未删除缓存中的数据之前,此时B线程就会读取到旧数据;又或者缓存因为某种原因删除失败,那么B线程读取到的仍然是旧数据。

还有一种方式和Cache Aside Pattern操作步骤相反,是先删除缓存中的数据,再更新数据库 ,这种方式能改善Cache Aside Pattern的数据不一致的情况,但是它也会造成数据不一致的情况出现:A线程操作删除缓存之后,未更新数据库之前,B线程读取数据发现缓存中没有,就从数据库中读取数据到缓存,然后A线程更新数据库,此时再读取数据的话就会读取到旧数据。

总的来说,上述两种方式虽然实现简单,但是都会造成数据不一致的情况,如果对数据一致性的要求没那么高或者并发量不是很大的情况下是可以使用这两种方式的。

延时双删

延时双删是对上述两种方式的优化版本,它能够降低缓存不一致的概率,其中的‘双删’指的是删除两次缓存。它的具体操作步骤如下:

  • 读取数据:先读取缓存中的数据,如果没有则从数据库中获取数据,有数据则将数据写入缓存中,没有数据就返回。
  • 写入数据:先更新数据库,再删除缓存,休眠一定时间或者通过延时队列,最后再次删除缓存。

延时双删这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。更新数据库,再删除缓存会造成脏数据产生,即使是延迟一定时间后之后再删除缓存,在这延迟的时间内脏数据还是会存在的,还是会有缓存和数据库不一致的情况,并且这个延迟到底要延迟多长时间也是一个问题很难把握。

订阅binlog

可以利用canal中间件模拟成数据库的salve来接收binlog,接收到binlog之后再解析内容然后更新或删除缓存。这种方式还可以使用canal+mq消息队列的形式,canal接受到binlog放入消息队列进行消费,之后再更新或删除缓存。

这种方式的好处是在更新数据库时,不需要去做删除缓存操作,之后接收到binlog进行消费更新或删除缓存即可。从接收到提交更新数据库操作,到接收到binlog并解析等等操作,中间是需要消耗一定时间的,虽然可能时间不长,但这个时间窗口也是会有脏数据产生的。

不过因为多了一个canal中间件系统的复杂性也就提高了,还需要维护canal的稳定。

更多binlog的介绍可以查看【MySQL系列】- binlog预防删库跑路

串行化

串行化就是将访问串行化。串行化典型的实现就是加锁或者通过消息队列将请求串行化。这种方式能保证缓存和数据库的强一致性,但是我们使用缓存不就是为了提高性能吗?串行化明显降低了性能所以还不如不用,也不推荐。

总结

总结一下本文介绍了5种保证缓存和数据库一致性的方案:

  1. 先更新数据库,再删缓存中的数据
  2. 先删除缓存中的数据,再更新数据库
  3. 延时双删:先删除数据库,再删除缓存,休眠一定时间或者通过延时队列,最后再次删除缓存
  4. 订阅binlog:通过canal中间件接收binlog再删除缓存。
  5. 串行化:不推荐

如果系统并发量不高或者对一致性要求不是很高的情况下,前两种方案基本上就可以满足要求了。
延时双删在很大程度上也能解决缓存不一致的情况,但是延时时间不好控制。
订阅binlog也是一种不错的方案,也能有效地降低缓存和数据库不一致的概率,这也是目前采用最多的方案之一了。

这几个方案只是常用的方案,对一致性如果要求很高的话可以将这几个方案进行组合使用,比如延时双删+订阅binlog方案也是可以的。

你可能感兴趣的:(java基础,数据库,缓存,java,缓存一致性)