探索Redis与MySQL的双写问题

这篇具有很好参考价值的文章主要介绍了探索Redis与MySQL的双写问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文已收录至GitHub,推荐阅读 👉 Java随想录

微信公众号:Java随想录

原创不易,注重版权。转载请注明原作者和原文链接

目录
  • 双写一致问题
  • 缓存读写策略
    • Cache-Aside Pattern(旁路缓存模式)
    • Read/Write Through Pattern(读写穿透模式)
    • Write Behind Pattern(异步缓存写入模式)
  • 旁路缓存模式解析
    • Cache Aside Pattern 的一些疑问
    • Cache Aside Pattern 的缺陷
  • 延时双删

在日常的应用开发中,我们经常会遇到需要使用多种不同类型的数据库管理系统来满足各种业务需求。其中最典型的就是Redis和MySQL的组合使用。

这两者拥有各自的优点,例如Redis为高性能的内存数据库提供了极快的读写速度,而MySQL则是非常强大的关系型数据库,支持事务处理,并且提供了很好的数据一致性。

然而,在实际应用过程中,如何保证Redis和MySQL双写时的数据一致性问题成为了开发者们面临的重要挑战。本文即将针对这个问题进行深入探讨,希望能为广大开发者们提供一些有价值的思路和解决方案。

双写一致问题

双写一致性问题主要是指当我们同时向Redis和MySQL写数据时,由于网络延迟、服务器故障等原因,可能导致数据在两个系统之间产生不一致。

例如,你可能已经更新了MySQL中的数据,但是Redis中的数据还未来得及更新,或者反过来。这样的结果就可能导致用户读到的是旧的、不正确的数据。

比如在现实生活中的购物网站场景:假设用户A在购买一件库存仅剩1件的商品,系统在接收到请求后,先将MySQL中的库存减少1,然后出现了网络延迟或系统故障,Redis中的库存没有减少。此时,用户B看到的是还有1件商品,也发起了购买请求,如果系统又首先更改了MySQL,那么就会出现超卖的情况,即实际库存已经没有,但因为缓存中的信息不准确,导致系统销售了更多的商品。

严格意义上任何非原子操作都不可能保证一致性,除非用阻塞读写实现强一致性,所以对于缓存架构我们追求的目标是最终一致性。

实际上,缓存就是通过牺牲强一致性来提高性能的。这是由CAP理论决定的。缓存系统适用的场景就是非强一致性的场景,它属于CAP中的AP。

缓存读写策略

解决这种问题的常见策略就是“缓存读写策略”。这个策略用于处理先更新数据库还是先更新缓存等场景。

接下来,我们将探讨三种缓存读写策略。这些策略各有优劣,没有绝对的最佳选择。请根据具体的应用场景选择最合适的策略。

Cache-Aside Pattern(旁路缓存模式)

Cache-Aside Pattern,即旁路缓存模式,它的提出是为了尽可能地解决缓存与数据库的数据不一致问题。旁路缓存模式中服务端需要同时维护DBCache,并且是以DB的结果为准。
探索Redis与MySQL的双写问题
:从缓存读取数据,读到直接返回。如果读取不到的话,从数据库加载,写入缓存后,再返回响应。

探索Redis与MySQL的双写问题

:更新的时候,先「更新数据库,然后再删除缓存」。

Read/Write Through Pattern(读写穿透模式)

Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 DB,从而减轻了应用程序的职责。

因为我们经常使用的分布式缓存 Redis 并没有提供 cache 将数据写入DB的功能,所以使用并不多。

探索Redis与MySQL的双写问题

:从 cache 中读取数据,读取到就直接返回 。读取不到的话,先从 DB 加载,写入到 cache 后返回响应。

从流程图中可以看出,读写穿透模式和旁路缓存模式的读取流程几乎相同。不过,在旁路缓存模式中,客户端需要负责将数据写入cache。而在读写穿透模式中,cache服务自行写入缓存,对客户端来说,这个过程是透明的。

探索Redis与MySQL的双写问题

:先查 cache,cache 中不存在,直接更新 DB。cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(同步更新 cache和DB)。

Write Behind Pattern(异步缓存写入模式)

Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 DB 的读写。

但是,两个又有很大的不同:Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB

很明显,这种方式对数据一致性带来了更大的挑战,比如cache数据可能还没异步更新DB的话,cache服务可能就挂掉了,反而会带来更大的灾难。

这种策略在我们平时开发过程中也非常非常少见,但是不代表它的应用场景少,比如消息队列中消息的异步写入磁盘、MySQL 的 InnoDB Buffer Pool 机制都用到了这种策略。

Write Behind Pattern 下 DB 的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量等。

旁路缓存模式解析

Cache Aside Pattern 的一些疑问

旁路缓存模式是我们平时中使用最多的,根据该模式,我们可能会有以下几个疑问。

为什么写操作是删除缓存,而不是更新缓存

:假设线程A先发起一个写操作,第一步先更新数据库。线程B再发起一个写操作,紧接着也更新了数据库。由于网络等原因,线程B比线程A先更新了缓存,然后线程A更新缓存。

这时候,缓存保存的是A的数据(老数据),而数据库保存的是B的数据(新数据),数据就不一致了,脏数据出现啦。如果是「删除缓存取代更新缓存」则不会出现这个脏数据问题。

实际上要写操作的时候更新缓存也是可以的,不过我们需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题。

在写数据的过程中,为什么要先更新DB再删除缓存

:假设请求1 是写操作,要是先删除缓存A,这时候来了请求2,请求2是读操作,先读缓存A,发现缓存被删除了(被请求1删除了),然后去读数据库,但是此时请求1还没来得及把数据及时更新,那么请求2读的就是旧数据,并且请求2还会把读到的旧数据放到缓存中,造成了数据的不一致。

其实要先删缓存,再更新数据库也是可以,如采用「延时双删策略」。

休眠一段时间,再次淘汰缓存。这么做,可以将这段时间内所造成的缓存脏数据,再次删除。

注意sleep休眠的时间不能小于修改数据库数据的时间小,基本上1秒就够了。

在写数据的过程中,先更新DB,后删除cache就没有问题了么?

答: 理论上来说还是可能会出现数据不一致性的问题,不过概率非常小。

假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生:

  1. 缓存刚好失效。
  2. 请求A查询数据库,得一个旧值。
  3. 请求B将新值写入数据库。
  4. 请求B删除缓存。
  5. 请求A将查到的旧值写入缓存 ok,如果发生上述情况,确实是会发生脏数据。

然而,发生这种情况的概率并不高

发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。

可是,仔细想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。

还有其他造成不一致的原因么?

答: 如果删除缓存过程中失败了就会造成不一致问题。可以使用Canal去订阅数据库的binlog,获得需要操作的数据。另起一个程序,获得这个订阅程序传来的信息,进行删除缓存操作。

Cache Aside Pattern 的缺陷

Cache Aside Pattern是一种常见的缓存更新策略,主要在读取数据时用于处理缓存的失效和更新。尽管它有很多优点,但也存在一些缺陷:

缺陷1:首次请求数据一定不在 cache 的问题

解决办法:可以将热点数据提前放入cache 中。

缺陷2:写操作比较频繁的话导致cache中的数据会被频繁被删除,这样会影响缓存命中率 。

  • 数据库和缓存数据强一致场景 :更新DB的时候同样更新cache,不过我们需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题。
  • 可以短暂地允许数据库和缓存数据不一致的场景 :更新DB的时候同样更新cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。

延时双删

Redis的延时双删策略主要用于解决分布式系统当中的缓存与数据库数据一致性问题。以下是其基本步骤:

  1. 先删除缓存。
  2. 再更新数据库。
  3. 最后延时再次删除缓存。

该策略的理念是:如果有其他线程在步骤1和步骤2之间查询到旧的数据并写入了缓存,那么步骤3可以保证这部分旧的数据被清除,从而尽可能维持数据库和缓存之间的数据一致性。

以下是使用Java实现的样例代码:

import redis.clients.jedis.Jedis;

public class RedisDoubleDelStrategy {
    private Jedis jedis;
    private static final long DELAY_MILLIS = 1000L; // 设置为你需要的延时时间

    public RedisDoubleDelStrategy(String host, int port) {
        this.jedis = new Jedis(host, port);
    }

    public void updateDBAndCache(String key, String value) {
        // Step 1: 删除缓存
        jedis.del(key);

        // Step 2: 更新数据库,此处以打印输出代替
        System.out.println("Update DB with: " + value);

        // 延迟任务来完成第二次删除
        new Thread(() -> {
            try {
                Thread.sleep(DELAY_MILLIS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            // Step 3: 延时后再次删除缓存
            jedis.del(key);
        }).start();
    }
}

这段代码实现了延时双删策略,但请注意它仍然不能完全保证数据库和缓存之间的一致性。

在某些情况下(比如大量并发情况下),可能仍然会出现不一致的问题。例如,在步骤3之后,如果还有其他线程查询到了旧数据并写入了缓存,那么数据库和缓存的数据就会不一致。因此,在使用该策略时,需要根据你的系统特性和一致性需求来进行权衡。

本篇文章到这就结束了,在探讨Redis与MySQL双写问题的过程中,我们分析了各种可能的场景和解决方案。双写系统不仅考验我们对数据库原理的理解,也展示了协同工作的复杂性。最终,解决这个问题的关键是理解你的用例并根据实际需求选择适当的策略和工具。

而在实际应用中,再完美的方案也可能会遇到挑战和困难。因此,持续监控,频繁测试和及时调整策略都至关重要。希望本文能为你在处理Redis与MySQL双写问题上提供一些思路和灵感,同时,我们也期待在未来看到更多精妙的解决方案诞生。


感谢阅读,如果本篇文章有任何错误和建议,欢迎给我留言指正。

老铁们,关注我的微信公众号「Java 随想录」,专注分享Java技术干货,文章持续更新,可以关注公众号第一时间阅读。

一起交流学习,期待与你共同进步!文章来源地址https://www.toymoban.com/news/detail-710716.html

到了这里,关于探索Redis与MySQL的双写问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis缓存(双写一致性问题)

    前言 : 什么是缓存? 缓存就像自行车,越野车的避震器 举个例子:越野车,山地自行车,都拥有\\\"避震器\\\", 防止 车体加速后因惯性,在酷似\\\"U\\\"字母的地形上飞跃,硬着陆导致的 损害 ,像个弹簧一样; 同样,实际开发中,系统也需要\\\"避震器\\\",防止过高的数据访问猛冲系统,导致其操作线程无法

    2024年02月02日
    浏览(46)
  • [优雅的面试]MySQL与Redis双写一致性方案

    前言 由于缓存的高并发和高性能已经在各种项目中被广泛使用,在读取缓存这方面基本都是一致的,大概都是按照下图的流程进行操作: 但是在更新缓存方面,是更新完数据库再更新缓存还是直接删除缓存呢?又或者是先删除缓存再更新数据库?在这一点上就值得探讨了。

    2024年01月21日
    浏览(33)
  • MySQL与Redis数据双写一致性工程落地案例

    多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。 其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。 后面的线程进来发现已经有缓存了,就直接走缓存。   canal [kə\\\'næl],中文翻译

    2024年02月03日
    浏览(74)
  • 04_Redis与mysql数据双写一致性案例

    是什么 canal[ka’nel,中文翻译为水道/管道/沟渠/运河,主要用途是用于MySQL数据库增量日志数据的订阅、消费和解析,是阿里巴巴开发并开源的,采用Java语言开发; 历史背景是早期阿里巴巴因为杭州和美国双机房部署,存在跨机房数据同步的业务需求,实现方式主要是基野业务

    2024年02月12日
    浏览(31)
  • Redis缓存问题:穿透,击穿,雪崩,双写一致性等

    在高并发场景下,数据库往往是最薄弱的环节,我们通常选择使用 redis 来进行缓存,以起到缓冲作用,来降低数据库的压力,但是一旦缓存出现问题,也会导致数据库瞬间压力过大甚至崩溃,从而导致整个系统崩溃.今天就聊聊常见的 redis 缓存问题. 缓存击穿 缓存击穿一般指redis中的一

    2024年04月27日
    浏览(41)
  • Redis与MySQL双写一致性:一场速度与稳定性的较量

    摘要: 本文将深入探讨Redis与MySQL双写一致性的问题,并详细介绍如何通过Java代码实现这一过程。我们将从理论入手,阐述双写一致性的重要性,然后结合实际案例,展示如何确保在Redis和MySQL之间的数据同步。 一、Redis与MySQL双写一致性的重要性 随着分布式系统的普及,数据

    2024年01月16日
    浏览(34)
  • 美团二面:如何保证Redis与Mysql双写一致性?连续两个面试问到了!

    Redis作为一款高效的内存数据存储系统,凭借其优异的读写性能和丰富的数据结构支持,被广泛应用于缓存层以提升整个系统的响应速度和吞吐量。尤其是在与关系型数据库(如MySQL、PostgreSQL等)结合使用时,通过将热点数据存储在Redis中,可以在很大程度上缓解数据库的压力

    2024年04月16日
    浏览(35)
  • Redis - 做缓存时高并发问题:缓存穿透、击穿、雪崩,数据库缓存双写不一致

    当用户访问的数据既不在缓存也不在数据库中时,就会导致每个用户查询都会“穿透” 缓存“直抵”数据库。这种情况就称为缓存穿透。当高度发的访问请求到达时,缓存穿透不 仅增加了响应时间,而且还会引发对 DBMS 的高并发查询,这种高并发查询很可能会导致 DBMS 的崩

    2024年02月04日
    浏览(37)
  • Redis双写一致性?

    双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。 Redis作为缓存,mysql的数据如何与redis进行同步呢?(双写一致性) 1.我们当时做排行榜业务时,把历史榜单数据存储到了缓存中。这个虽然也是热点数据,但是实时要求性不高。

    2024年02月16日
    浏览(34)
  • 面试redis篇-05双写一致

    双写一致性 :当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致 读操作:缓存命中,直接返回;缓存未命中查询数据库,写入缓存,设定超时时间 写操作:延迟双删 方案一 :分布式锁,一致性要求高

    2024年02月22日
    浏览(22)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包