MySQL和Redis缓存一致性

一共有六种方案

不好的方案:
  • 先写MySQL,再写Redis

  • 先写Redis,再写MySQL

  • 先删除Redis,再写MySQL

好的方案:
  • 先删除Redis,再写Mysql,再删除Redis

  • 先写MySQL,再删除Redis

  • 先写MySQL,通过Binlog,异步更新Redis

(1)先写MySQL,再写Redis

请求 A、B 都是先写 MySQL,然后再写 Redis,在高并发情况下,如果请求 A 在写 Redis 时卡了一会,请求 B已经依次完成数据的更新,就会出现图中的问题。 不过这里有个前提,就是对于读请求,先去读 Redis,如果没有,再去读 DB,但是读请求不会再回写 Redis。 大白话说一下,就是读请求不会更新 Redis。

(2)先写Redis,再写MySQL

同"先写MySQL,再写Redis"

(3)先删除Redis,再写MySQL

这幅图和上面有些不一样,前面的请求 A和 B都是更新请求,这里的请求 A 是更新请求,但是请求 B 是读请求,且请求 B的读请求会回写 Redis。

请求 A 先删除缓存,可能因为卡顿,数据一直没有更新到 MySQL,导致两者数据不一致。这种情况出现的概率比较大,因为请求 A 更新 MySQL 可能耗时会比较长,而请求 B的前两步都是查询,会非常快

(4)先删除Redis,再写MySQL,再删除Redis

对于“先删除 Redis,再写 MySQL” 如果要解决最后的不一致问题,其实再对 Redis 重新删除即可,这个也是常说的”缓存双删“

为了便于大家看图,对于蓝色的文字,“删除缓存 10”必须在“回写缓存10”后面,那如何才能保证一定是在后面呢?

网上给出的第一个方案是,让请求 A的最后一次删除,等待 500ms。 对于这种方案,看看就行,反正我是不会用,太 Low 了,风险也不可控

还有一种方案就是建议异步串行化删除,即删除请求入队列

异步删除对线上业务无影响,串行化处理保障并发情况下正确删除。 如果双删失败怎么办,网上有给 Redis 加一个缓存过期时间的方案,这个不敢苟同。个人建议整个重试机制,可以借助消息队列的重试机制,也可以自己整个表,记录重试次数,方法很多。

简单小结

  • ”缓存双删“不要用无脑的sleep 500ms

  • 通过消息队列的异步执行,实现最后一次缓存删除

  • 缓存删除失败,增加重试机制

缓存双删后缓存为空,可以通过以下两种方式实现

解决方案:缓存重建

为了确保缓存和数据库的数据保持一致,我们需要在删除缓存后重新填充缓存。具体来说,就是在执行完双删操作后,应该有一个机制去 重新填充缓存。

重新填充缓存的两种常见做法:
  1. 懒加载(Lazy Loading):

  2. 同步更新缓存(主动刷新缓存)

(5)先写MySQL,再删除Redis
对于上面这种情况,对于第一次查询,请求 B查询的数据是 10,但是 MySQL的数据是 11,只存在这一次不致的情况,对于不是强一致性要求的业务,可以容忍。(那什么情况下不能容忍呢,比如秒杀业务、库存服务 等。) 当请求 B进行第二次查询时,因为没有命中 Redis,会重新查一次 DB,然后再回写到 Reids。

这里需要满足 2 个条件:

  • 缓存刚好自动失效;

  • 请求 B从数据库査出 10,回写缓存的耗时,比请求 A写数据库,并且删除缓存的还长

对于第二个条件,我们都知道更新 DB 肯定比查询耗时要长,所以出现这个情况的概率很小,同时满足上述条件的情况更小。

(6)先写MySQL,通过BinLog,异步更新Redis

这种方案,主要是监听 MVSQL的 Binlog,然后通过异步的方式,将数据更新到 Redis,这种方案有个前提,查询的请求,不会回写 Redis。

这个方案,会保证 MySQL和 Redis 的最终一致性,但是如果中途请求 B需要查询数据,如果缓存无数据,就直接查 DB;如果缓存有数据,查询的数据也会存在不一致的情况。 所以这个方案,是实现最终一致性的终极解决方案,但是不能保证实时性。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐