文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Redis与MySQL的双写一致性问题

2023-09-01 06:14

关注

Redis与MySQL的双写一致性问题

Redis与MySQL双写一致性是指在使用缓存和数据库同时存储数据的场景下( 主要是存在高并发的情况)如何保证两者的数据一致性(内容相同或者尽可能接近)

正常业务流程
在这里插入图片描述

读没什么问题,关键就在于写(更新)操作,这就会出现几个问题了,这里是先更新数据库,然后对缓存操作。但对于缓存操作,是更新缓存还是删除缓存呢?或者为什么不是先操作(删除、更新)缓存在更新数据库呢

总结一下就是到底先操作缓存再操作数据库,还是先操作数据库再操作缓存

带着这几个问题接着往下讲。

首先讲一下操作缓存,包括两种:更新缓存删除缓存,如何选择?


更新缓存? 删除缓存?

假设都先更新数据库(因为先操作缓存再操作数据库问题较大,后面会讲)

在这里插入图片描述

从上面看出现脏数据的要求要比更新缓存的要求更多,必须满足以下几个条件:

  1. 缓存失效
  2. 读请求 + 写请求并发
  3. 更新数据库 + 删除缓存的时间要比读数据库 + 写缓存时间

前面两个很好满足,我们再看看第三点,这个真的会出现吗?

数据库在更新时一般是加锁的,读操作的速度远快于写操作的,所以第三点发生概率极低(当然也可能发生)

注:这里我其实不是很理解,单纯看确实发生概率低,但如果出现网络延迟等情况呢,不也会发生吗?希望好心人解惑,我反正没理解。

因此,在选择删除缓存时,还需要结合其他技术来优化性能和一致性。例如:

对比

​ 在更新缓存中, 每次去更新缓存,但是缓存中的数据不一定会被马上读取,这就会导致缓存中可能存放了很多不常访问的数据,浪费缓存资源。而且很多情况下,写到缓存中的值,并不是与数据库中的值一一对应的,很有可能是先查询数据库,再经过一系列「计算」得出一个值,才把这个值才写到缓存中。

​ 由此可见,这种更新缓存的方案,不仅缓存利用率不高,还会造成机器性能的浪费。所以我们一般考虑删除缓存


先更新缓存再更新数据库

在更新数据时,先将新数据写入缓存(Redis),再将新数据写入数据库(MySQL)

但其存在一下问题:

上面说了一堆,其实总结就是缓存更新成功了,数据库没更新(更新失败),导致缓存存的是最新值,数据库存的是旧值。如果缓存失效了,就会拿到数据库中的旧值。

​ 后面我自己也搞疑惑了,既然是因为数据库更新失败导致的问题,那我是不是只要保证数据库更新成功就可以解决数据不一致的问题,当数据库更新失败时,不停的重试更新数据库,直到数据库更新完成。

后面发现自己太天真,其中存在很多问题,比如:

所以,这种方法并不是一个很好的解决方案。


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

当有一个更新操作时,先更新数据库数据,然后再更新对应的缓存数据

但是,这种方案也有一些问题和风险,比如:

因此,在使用更新缓存操作时,无论谁先谁后,但凡后者发生异常,就会对业务造成影响。(还是上面那张图)

)

那么如何处理异常情况来保证数据一致性呢

​ 这些问题的源头都是多线程并发所导致的,所以最简单的方法就是加锁(分布式锁)。两个线程要修改同一条数据,每个线程在改之前,先去申请分布式锁,拿到锁的线程才允许更新数据库和缓存,拿不到锁的线程,返回失败,等待下次重试。这么做的目的,就是为了只允许一个线程去操作数据和缓存,避免并发问题。

​ 但加锁费时费力,肯定不推荐。并且,每次去更新缓存,但是缓存中的数据不一定会被马上读取,这就会导致缓存中可能存放了很多不常访问的数据,浪费缓存资源。而且很多情况下,写到缓存中的值,并不是与数据库中的值一一对应的,很有可能是先查询数据库,再经过一系列「计算」得出一个值,才把这个值才写到缓存中。

​ 由此可见,这种更新数据库 + 更新缓存的方案,不仅缓存利用率不高,还会造成机器性能的浪费。

所以此时我们需要考虑另外一种方案:删除缓存


先删除缓存再更新数据库

当有一个更新操作时,先删除对应的缓存数据,然后再更新数据库数据

但是,这种方案也有一些问题和风险,比如:

在这里插入图片描述


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

当有一个更新操作时,先更新数据库数据,再删除缓存

上面其实讲过了,我再重复一遍吧

会有这样一种情况:缓存刚好失效,请求B从数据库中查询数据,得到旧值。此时请求A更新数据库,将新值写入数据库,并删除缓存。而请求B又将旧值写入缓存中,导致脏数据

在这里插入图片描述

从上面看出现脏数据的要求要比更新缓存的要求更多,必须满足以下几个条件:

  1. 缓存失效
  2. 读请求 + 写请求并发
  3. 更新数据库 + 删除缓存的时间要比读数据库 + 写缓存时间

前面两个很好满足,我们再看看第三点,这个真的会出现吗?

数据库在更新时一般是加锁的,读操作的速度远快于写操作的,所以第三点发生概率极低

所以,解决双写问题更适合的方法是先更新数据库,再删除缓存,当然具体场景具体分析,不定说一定就是这个。

讲解了这些操作后会出现的问题,那么为了避免这些问题,如何做呢?

……

下面讲几种常见的方法以保证双写一致性


解决方案

1. 重试

​ 上面也提到过,当第二步操作失败时,我就重试嘛,尽可能地补救,但重试的成本太大,上面讲过就不重复了。

2. 异步重试

​ 既然重试方法占用资源,那我就做异步。在删除或更新缓存时,如果操作失败,不立即返回错误,而是通过一些机制(如消息队列、定时任务、订阅binlog等)来触发缓存的重试操作。这样可以避免同步重试缓存时的性能损耗和阻塞问题,但也可能导致缓存和数据库的数据不一致的时间较长。

2.1 使用消息队列实现重试

使用消息队列异步重试缓存的情况是指,当信息发生变化时,先更新数据库,然后删缓存,如果删除成功就皆大欢喜,如果删除失败,则将需要删除的key发送到消息队列。另外有一个消费者线程从消息队列中获取要删除的key,并根据key删除或更新Redis中的缓存。如果操作失败,则重新发送到消息队列中进行重试。

注:也可以不先尝试删除,直接发送给消息队列,让消息队列

举例来说,假设有一个用户信息表,需要将用户信息缓存在Redis中。如果采用使用消息队列异步重试缓存的方案,可以有以下几个步骤:

2.2 Binlog实现异步重试删除

使用binlog实现一致性的基本思路是利用binlog日志来记录数据库的变更操作然后通过主从复制或者增量备份的方式来同步或者恢复数据

举例来说,如果我们有一个主数据库和一个从数据库,我们可以在主数据库上开启binlog日志,并设置从数据库作为它的复制节点。这样,当主数据库上发生任何变更操作时,它会将对应的binlog日志发送给从数据库,从数据库则会根据binlog日志来执行相同的操作,从而保证数据一致性。

另外,如果我们需要恢复某个时间点之前的数据,我们也可以利用binlog日志来实现。首先,我们需要找到对应时间点之前的最近一个全量备份文件,并将其恢复到目标数据库。然后,我们需要找到对应时间点之前的所有增量备份文件(即binlog日志文件),并按照顺序将其应用到目标数据库。这样,我们就可以恢复出目标时间点之前的数据状态了。

在这里插入图片描述

注:binlog日志是MySQL的二进制日志,它记录了对数据库的变更操作,比如插入、更新、删除等。 binlog日志有两个主要作用,一个是主从复制,另一个是增量备份。

主从复制是指在一个主数据库和一个或多个从数据库之间实现数据的同步。主数据库会将自己的binlog日志发送给从数据库,从数据库则会根据binlog日志来执行相同的操作,从而保证数据一致性。这样可以提高数据的可用性和可靠性,也可以实现负载均衡和故障转移。

增量备份是指在全量备份的基础上,定期备份数据库的变更操作。全量备份是指将整个数据库的数据完整地备份到一个文件中。增量备份则是指将每次变更操作对应的binlog日志文件备份到另一个文件中。这样可以减少备份所占用的空间和时间,也可以实现灵活地恢复数据到任意时间点。

至此,我们可以得出结论,想要保证数据库和缓存一致性,推荐采用「先更新数据库,再删除缓存」方案,并配合「消息队列」或「订阅变更日志」的方式来做

3. 延时双删

我们重点在将先更新数据库,在删除缓存。那如果我要先删除缓存,再更新数据库呢?

回顾之前讲的先删除缓存,再更新数据库,它会出现旧值覆盖缓存的问题,那好办,我们直接把这个旧值给删了不就完了吗,延时双删就是这个原理,它的基本思路是:

  1. 先删除缓存
  2. 再更新数据库
  3. 休眠一段时间(根据系统情况确定)
  4. 再次删除缓存

这样做的目的是为了防止在更新数据库后,有其他线程读取到旧的缓存数据,并将其写回缓存,导致数据不一致

举个例子:假设有一个用户信息表,其中有一个字段是用户积分。现在有两个线程A和B同时对用户积分进行操作:

如果使用延时双删策略,那么线程A和B的执行过程可能如下:

这样最终结果就是:数据库中的用户积分为1050,缓存中没有该用户信息。当下次有请求查询该用户信息时,就会从数据库中读取并写入到缓存中。这样就保证了数据一致性。

延时双删适用于高并发场景,特别是对数据的修改操作比较频繁,而查询操作比较少的情况。这样可以减轻数据库的压力,提高性能,同时保证数据的最终一致性。延时双删也适用于数据库有主从同步延迟的场景,因为它可以避免在更新数据库后,从库还没有同步完成时,读取到旧的缓存数据,并将其写回缓存。

注: 这个休眠时间 = 读业务逻辑数据的耗时 + 几百毫秒。 为了确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据。


总结

好了,总结一下这篇文章的重点。

Redis与MySQL的双写一致性问题是指在使用缓存和数据库同时存储数据的场景下,如何保证两者的数据一致性。这个问题主要涉及到以下几个方面:

为了解决这些问题 , 可以采用以下几种方法 :

总之,根据场景选择适合自己的方案


参考文章

Redis与MySQL双写一致性方案解析 - 知乎 (zhihu.com)

美团二面:Redis与MySQL双写一致性如何保证? - 掘金 (juejin.cn)

缓存和数据库一致性问题,看这篇就够了 - 知乎 (zhihu.com)


终于水完了这篇文章,得去背面经了!!!

来源地址:https://blog.csdn.net/x_xhuashui/article/details/129657086

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯