Redis缓存和数据库一致性问题

73 阅读5分钟

Redis缓存和数据库一致性问题

随着系统流量的增加,数据库的瓶颈成为影响系统性能的直接因素,这时候一般给服务器加上Redis,让其作为数据库的缓存。这样客户端请求数据时,会优先从缓存中获取,如果能在缓存中命中数据,那就不用再去查询数据库,从而减轻数据库的压力,提高服务器的性能。

在更新数据时,涉及到更新数据库和缓存。这两个更新操作存在先后顺序的问题。

先更新数据库,再更新缓存

企业微信截图_16776743621786.png

如上图,两个请求同时更新一条数据,则可能出现上述的情况,A请求先将数据库更新为1,在A请求更新缓存前,请求B将数据库更新为2,紧接着也把缓存更新为2,最后请求A把缓存更新为1。

此时,数据库中的值是2,而缓存中的数据却是1,出现缓存和数据库中的数据不一致的现象。

先更新缓存,再更新数据库

1677674707935.png

如果换成先更新缓存,再更新数据库,还会出现缓存不一致问题吗?依然会存在并发问题。

假设请求A和请求B两个请求同时更新同一条数据,则可能出现上述顺序:

请求A先将缓存更新为1,此时请求B将缓存更新为2,紧接着将数据库更新为2,然后请求A将数据库更新为1。

此时数据库中的值为1,而缓存中的数据却是2,出现了缓存和数据库中的数据不一致的现象。

所以,无论是先更新数据库,再更新缓存,还是先更新缓存,再更新数据库,这两个方案都存在并发问题,当两个请求并发更新同一条数据的时候,可能会出现缓存和数据库的数据不一致现象。

旁路缓存策略(Cache Aside)

如果我们不更新缓存,而改为删除缓存数据呢?

假设我们在更新数据时,不更新缓存,而是删除缓存中的数据。然后到读取数据的时候,如果数据命中了缓存则直接返回数据;如果没有命中缓存,则从数据库中读取数据,然后将数据更新到缓存中。

那么我们同样有两种顺序:①先删除缓存,再更新数据库;②先更新数据库,再更新缓存

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

1678103594792.png

如上图,假设数据库中的值为10,此时请求A想把数据库中的年龄改为20,先删除缓存中的数据,紧接着请求B过来,发现缓存未命中,如是会读取数据库中的数据,并把他加载到缓存中,此时缓存中的值为10,最后请求A更新数据库中的值为20。

最终缓存中的值为还是为10,数据库中的值为20,缓存和数据库的数据不一致。

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

1678104408437.png

如图,假设某个用户数据在缓存中不存在,此时请求A过来,发现缓存未命中,则会去数据库中读取改用户数据,此时还未来得及写到缓存,请求B过来了,它更新数据库中的值为20,并马上删除缓存。最后A请求将缓存更新为10。

最终缓存中的为10,数据库中的值为20,缓存和数据库不一致。

从理论上讲,先更新数据库,再删除缓存也会出现缓存不一致问题,但是这个问题出现的概率并不高,因为缓存写入的速度通常要远远高于数据库写入速度。实际情况中很难出现B请求更新了数据库再删除缓存后,请求A才更新完缓存。

所以,先更新数据库,再删除缓存,在绝大多数情况下是可以保持一致性的。(如果害怕不一致,还可以给缓存家加上过期时间,这样就算数据存在不一致情况,也会在缓存过期后,再次读取数据库中的值更新到缓存)

但是,如果删除缓存失败,那么缓存中的值就会一直是旧值,那么我们此时要保证的是,先更新数据库,再删除缓存这两个操作能执行成功。

  • 可以放到mq中,这样一段缓存删除失败,mq会自动帮我们重试。
  • 订阅MySQL binlong,

话说回来了,先更新数据库,再删除缓存,虽然能保证数据库与缓存的一致性,但是会对缓存命中率带来影响。所以如果业务对缓存命中率有很高的要求,我们可以采用更新数据库+更新缓存的方案,因为更新缓存不会出现缓存未命中的情况。

但是前面也说到,先更新数据库,再更新缓存会出现数据不一致问题。为了解决这个问题,以下提供了两种方案:

  • redis分布式锁
  • 更新完缓存后,给缓存加上一个较短过期时间,这样即使出现数据不一致的情况,缓存也会很快过期,对业务还是能接受的。