1、什么是缓存一致性

分为两种情况,如果缓存中存在数据,那么缓存中的数据与数据库数据一致,如果缓存中没有数据 ,那么数据库中的数据就要是最新的。可能针对第二点有的人会有一些疑问,我的理解是,如果数据的修改是在缓存中进行,当缓存满后这些数据被淘汰时才写入数据库,然后清除这部分数据。如果写入数据库发生问题,而缓存又被清除,那么这里就发生了缓存不一致的问题,因为数据库中的数据不是最新的。

2、缓存不一致的原因

以下几种情况会导致缓存不一致:

1、如果业务采用异步写回策略,即上面提到的,修改数据时只将数据在缓存中修改,并不去修改数据库,等到数据从缓存中淘汰,再写入数据库。这种策略下如果写入数据库异常,就会导致不一致。

2、删除数据时(不考虑并发)。假设应用先删除缓存中的数据,然后再删除数据库中的数据,此时如果缓存删除成功,然后数据库删除失败,那么下次访问,会先走缓存,缓存未命中,然后去数据库查询,但是数据库查到的是旧值,导致不一致。

如果是先删除数据库中的数据,再删除缓存,此时一种情况数据库删除成功,缓存删除失败,那么会导致数据库中的值是新的,而缓存中的值是旧的,而查询又先走缓存,也是不一致的情况。

3、考虑并发的情况。

​ 1)、先更数据库,再更缓存,写+读并发。假如线程A删除缓存值后,还没来得及写数据库(假设遇到网络延迟或者执行被中断),线程B又读取数据,此时会遇到缓存未命中,那么线程B会去数据库进行查询,然后将数据写入缓存当中(注意,这里写入缓存的数据是旧的值)。当B写入缓存成功后,线程A删除数据库的值。虽然线程A和线程B的操作都成功了,但是此时却发生了缓存不一致的问题。

​ 2)、先更缓存,再更数据库,写+读并发。如果线程A删除了数据库,还没来得及删除缓存,线程B进行了读取,那么线程B会直接从缓存中读取到旧的数据。这种情况下,会有短暂的数据不一致问题,因为线程A很快会删除缓存中的值,此时后续线程会发生缓存未命中,然后去数据库查询最新的值。这种情况会出现短暂的不一致,但是对业务影响不大。

​ 3)、先更新数据库,再更新缓存,写+写并发。线程A和B同时更新一条数据,更新数据库顺序是先A后B,但是更新缓存数据因一些原因变成了先B后A,就会导致不一致。

​ 4)、先更新缓存,再更新数据库,写+写并发。与场景3类似,更新缓存的顺序和更新数据库的顺序不一样,就会导致数据不一致。

3、如何解决缓存不一致

如果不考虑高并发,我们可以采用重试机制

具体来说,可以把要删改的数据先存入消息队列(如rabbitmq)当中,当应用删除失败,则取队列中取出要删除的数据进行重新尝试,如果成功,则从队列中移除元素。但高并发场景下这种解决方法不行,原因下文会解释。

在高并发场景下,分两种情况:

1、如果业务设计先删除缓存,再更新数据库

参考上边并发情况第一条,先更数据库,再更缓存,写+读并发的情况。

针对这种情况,一种解决方案就是延迟双删。即让线程A在操作完数据库之后,过一小段时间再去删除一次缓存。

但这种情况不可避免的会导致一小段时间内,用户读到的数据是错误的。

2、业务设计是先删除数据库数据,再删除缓存。

参考上述并发情况第二条,先更缓存,再更数据库,写+读并发。

结合以上两种方案,其实都会出现短暂的缓存不一致,而方案一不加处理则会导致很长时间的不一致,而且延迟多久进行删除时间也不好确定,所以如果不是特别的场景,个人更倾向于采用第二种方案。

这种解决方案适合于那些数据不敏感的场景。

3、针对写+写的并发,需要采用加锁的方式,保证一个线程修改完成后,再执行第二个线程的操作。也可以将修改数据库的操作以及修改redis的操作放入一个队列当中,保证他们的执行顺序。但是使用队列可能会导致中间过程会出现部分时刻的数据不一致,但是可以保证最终的一致性。