缓存的双写一致性解决方案 解决redis与mysql数据一致性 看不懂的你来打我~

前言

此篇章为分析如何将redis的缓存与mysql数据同步的解决方案,本人参照各路大神的解决方案汇聚而成,难免会有错误的地方,肯定各路大神评论区无情鞭挞~~

一. 何谓双写一致性

你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?

我想,已经有小伙伴有了好的方案了,为什么我们不能直接将其中读请求与写请求串行化,全部整到一个队列中去消费,不就一定不会出现不一致的情况了吗?确实,但是这样做会严重降低系统的高可用型,你想想你的程序每秒能跑多少,你放到队列中又能跑多少呢

二. 解决方案

目前,已知三种解决方案,让我们来好好看看这其中的区别

  • 1. 先更新数据库,再更新缓存
  • 2. 先删除缓存,再更新数据库
  • 3. 先更新数据库,再删除缓存

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

首先,当我们一个改数据的请求进来,我们先更新数据库的值,然后再把缓存中的值去更新,当我们下一次请求进来的时候保证数据是最新值

这其中有问题吗?

2.1.1 问题1 每秒一万次改请求,一次读请求?

试想如上情况,每秒一万次改请求,那我数据库会一直更新,而且当我们数据库更新的时候也会更新缓存值,那我其实只有一次读请求,那我更新这一万次缓存是不是没意义? 而且会造成我们系统很大开销,而缓存也没被访问几次

在这里插入图片描述
那不如我们…

删除缓存如何? 试想一下,要是我们直接每次更新数据库的时候,不去更新缓存,而是直接删除缓存,是不是情况就简单很多,每秒一万次请求,改数据库然后删除缓存,那我们的缓存并没有持续更新,也不存在大开销,并且下次我们查请求进来,也只不过多了一次生成缓存的操作

这点其实用了懒加载的思想,我们只有用到的时候采取生成,不用的时候就一直不用,是不是很好? 下面我们来讨论这种方案

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

唉,既然上面这样分析了,其实还是又出现了新的问题,那我们是先删除缓存还是先更新数据库呢?

在这里插入图片描述
我们设想有如下情景

  • (1)请求A进行写操作,删除缓存
  • (2)请求B查询发现缓存不存在
  • (3)请求B去数据库查询得到旧值
  • (4)请求B将旧值写入缓存
  • (5)请求A将新值写入数据库

如上,我们来分析一下,当请求A进行写操作,刚刚删除缓存还没来得及更新数据库,突然cpu任务调度停了,B请求查询后没缓存,就去数据库查询旧值,查到后缓存这个旧值,那我们这时A请求又进行了,去把数据库的值更新了,这时候会出现什么情况?缓存是旧值,但是数据库是新值,出现数据不一致,而且每次有新请求进来,在缓存没失效的时候,永远查的都是旧值.

这样会一直出现脏数据,那我们怎么解决呢?

延时双删策略

例如在我们A请求进行写操作的时候,第一次不是删除了缓存吗,但容易被B请求更新成脏数据,那我们就等一段时间(休眠一段时间)再进行删除缓存,是不是可以把之前B请求的脏数据删除掉,下次新请求进来查询的时候查不到缓存,会去数据库缓存最新值

但是,又抛出一些问题,如何确定这个睡眠时间是多少呢?根据自己的业务可以调整休眠时间,也有不确定性,其次要是你的数据库是读写分离架构会出现如下问题

  • (1)请求A进行写操作,删除缓存
  • (2)请求A将数据写入数据库了,
  • (3)请求B查询缓存发现,缓存没有值
  • (4)请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值
  • (5)请求B将旧值写入缓存
  • (6)数据库完成主从同步,从库变为新值

这样也会出现数据不一致的情况,解决的策略还是延时双删策略,适当将休眠时间延长,在主从同步后进行双删,于是这样加的时间值以及牺牲系统吞吐量来解决是不可取的,所以我们开另外一个线程进行异步删除,不用再休眠系统

但是问题最终解决了吗?

在这里插入图片描述
如果我们删除失败怎么办?

比如在上面延时双删的情况下,我们第二次删除失败,那前面一切不都是白做了?瓶颈口就在这里,只要第二次删除缓存失败,一切都白给了~~

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

老外提出了一个缓存更新套路,名为 Cache-Aside pattern 。

另外,知名社交网站facebook也在论文《Scaling Memcache at Facebook》中提出,他们用的也是先更新数据库,再删缓存的策略。

更新:先把数据存到数据库中,成功后,再让缓存失效。
在这里插入图片描述

首先,我们再来分析一下这种模式会出现什么问题

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

也会出现缓存与数据库不一致的情况,但其实这种情况出现的概率很低

首先,这种情况出现在读请求穿插写请求中,且读请求用时比写请求长,从上面情况来分析不难得出.其次数据库写操作时长是远远大于读操作时长的,这也是为什么要数据库读写分离的原因,所以出现上述条件是非常苛刻的,再来说说为什么先删除缓存再写数据库这种情况的意外发生概率高,主要是因为写操作中穿插读操作,而读操作时长非常短, 极易造成数据不一致

毕竟我们是优秀的程序员嘛,但其实如果要是真发生上述情况的话
在这里插入图片描述

开玩笑~ 那我们还是采用延时双删策略,保证读请求后删除缓存即可

2.4 最终问题

那我们就是会出现删除缓存失败的时候嘛,那你怎么解决呢?

在这里插入图片描述

这里借鉴孤独烟大神的做法,只能怪自己太菜了唉~

方案一
在这里插入图片描述
流程如下所示
(1)更新数据库数据;
(2)缓存因为种种问题删除失败
(3)将需要删除的key发送至消息队列
(4)自己消费消息,获得需要删除的key
(5)继续重试删除操作,直到成功

然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

方案二
在这里插入图片描述
流程如下图所示:
(1)更新数据库数据
(2)数据库会将操作信息写入binlog日志当中
(3)订阅程序提取出所需要的数据以及key
(4)另起一段非业务代码,获得该信息
(5)尝试删除缓存操作,发现删除失败
(6)将这些信息发送至消息队列
(7)重新从消息队列中获得该数据,重试操作。

借鉴大神操作 附上大神博客

制作不易,转载都请标注 ~ 你看不懂,你也打不着我啊

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/kingtok/article/details/106689121