Redis实现缓存一致性的原理深度解析

Posted 刘Java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis实现缓存一致性的原理深度解析相关的知识,希望对你有一定的参考价值。

详细介绍了Redis实现缓存一致性的三种方式,以及他们的优缺点。


首先要明白,缓存和数据库数据之间没有绝对的一致性,如果要绝对一致,那就不能使用缓存,我们只能保证数据的最终一致性,以及尽量保证缓存不一致的时间最短。

另外,为了避免极端条件下造成的缓存与数据库之间的数据不一致,缓存需要设置一个失效时间。时间到了,缓存自动被清理,这样才能达到缓存和数据库数据的“最终一致性”。

如果不是很高的并发的情况下,无论选择先删缓存还是后删缓存的方式,都几乎很少能产生问题。

1 先更新数据库,然后再删除缓存

如果先更新了数据库成功,删除缓存的时候失败,那么数据库中是新数据,缓存中是老数据,此时就会出现数据不一致的情况。

如果在高并发的场景下,还有一种更加极端的数据库与缓存数据不一致的情况:

  1. 缓存刚好失效;
  2. 请求A查询数据库,得一个旧值;
  3. 请求B将新值写入数据库;
  4. 请求B删除缓存;
  5. 请求A将查到的旧值写入缓存;

上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据,除非下一次更新数据库数据。

2 先删除缓存,然后再更新数据库

先删除缓存,后更新数据库,即使后面更新数据库失败了,缓存是空的,读的时候会从数据库中重新读取,虽然都是旧数据,但数据是一致的。

如果在高并发的场景下,还有一种更加极端的数据库与缓存数据不一致的情况:

  1. 请求A进行写操作,删除缓存;
  2. 请求B查询发现缓不存在;
  3. 请求B查询数据库,得一个旧值;
  4. 请求B将查到的旧值写入缓存;
  5. 请求A将新值写入数据库;

3 采用延时双删策略

上面的两种方法,不管是先写库,再删除缓存;还是先删缓存,再写库,都有可能出现数据不一致的情况。相对来说,第二种更保险,因此,如果只用这两种简单的方法,建议使用第二种。

更好较好的办法是采用一种“延时双删”的策略!在写库前后都进行一次redis.del(key)操作,另外为了避免更新数据库的时候,其他线程从缓存中读取不到数据然后读取旧数据写入缓存,就在更新完数据库之后,再sleep一段时间,然后再次删除缓存。

这个sleep时间要考虑大于另一个请求读取数据库旧数据+写缓存的时间,以及如果有redis主从同步、数据库分库分表,还要考虑数据同步的耗时,在Sleep之后再次尝试删除缓存(无论新的还是旧的)。这样,虽然不能保证一定不会出现缓存一致性的问题,但是能够保证仅在sleep时间内缓存不一致,降低了缓存不一致的时间。

当然这种策略由于需要休眠一定时间,这样毫无疑问又增加了写请求的耗时,导致服务器请求的吞吐量降低,这也是一个问题。为此,可以将第二次删除作为异步的删除。这样,业务线程的请求就不用sleep一段时间之后再返回,这么做,可以加大吞吐量。

万一删除缓存失败怎么办呢?此时又需要重试机制,那么此时可以利用消息队列,将需要删除的key发送到消息队列中,异步的消费消息,获得需要删除的key的值对比数据库的值!不一致则删除,还是删除失败则从新消费直到成功,或者当失败一定次数的时候一般就是Redis服务器出现问题了!

实际上引入消息中间件之后,问题变得更复杂了,我们需要保证消息中间件不出现各种问题,比如生产者发送消息成功或者失败。因此,我们可以借助监听数据库binlog日志的消息队列来做删除缓存的操作,好处是不需要自己往消息队列放消息了,通过一个中间件监听数据库的binlog日志,然后自动向队列中放消息,我们不再需要编程生产者代码,只需要编写消费者的代码。这种监听数据库binlog+消息队列的方式也是目前比较流行的一种方式。

4 为什么是删除缓存

为什么上面的方法都是删除缓存而不是更新缓存呢?

假如先更新数据库,然后更新缓存,那么可能出现如下情况:如果数据库1小时内更新了1000次,那么缓存也要更新1000次,但是这个缓存可能在1小时内只被读取了1次。如果是删除的话,就算数据库更新了1000次,那么也只是做了1次有效的缓存删除,后续的删除操作会立即返回,只有当缓存真正被读取的时候才去数据库加载缓存。这样减轻了Redis的负担。

相关文章:

  1. https://redis.io

如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

以上是关于Redis实现缓存一致性的原理深度解析的主要内容,如果未能解决你的问题,请参考以下文章

Docker安装canalmysql进行简单测试与实现redis和mysql缓存一致性

redis实现分布式锁原理解析

Docker安装canalmysql进行简单测试与实现redis和mysql缓存一致性

Redis缓存双写一致性

Redis与Mysql双写一致性方案解析

Redis与Mysql双写一致性方案解析