zoukankan      html  css  js  c++  java
  • 分布式系统知识点十二:更新数据时,是先删除缓存再更新DB,还是先更新DB再删除缓存?(转载)

    本系列为网上收集转载分布式相关知识点系列文章,并非原创。如果侵权,请联系我删除!!!

    当数据出现变化的时候,DB和redis的一致性就显得非常重要!

    结论:并发的情况下,主要是看场景,和代价,进行选择。

    目前有两种(基本思路)策略:

    1.先删除缓存再更新DB

    结论:产生脏数据的概率较大(若出现脏数据,则意味着再不更新的情况下,查询得到的数据均为旧的数据)

    比如:两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。

    2.先更新DB再删除缓存(使用场景多)

    结论:产生脏数据的概率较小,但是会出现一致性的问题;若更新操作的时候,同时进行查询操作,若hit,则查询得到的数据是旧的数据。但是不会影响后面的查询。(代价较小)

    ( 一个是查询操作,一个是更新操作的并发,首先,没有了删除cache数据的操作了,而是先更新了数据库中的数据,此时,缓存依然有效,所以,并发的查询操作拿的是没有更新的数据,但是,更新操作马上让缓存的失效了,后续的查询操作再把数据从数据库中拉出来。而不会像文章开头的那个逻辑产生的问题,后续的查询操作一直都在取老的数据。)

    该设计模式产生脏数据的可能情况:

    一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。

    该情况出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

    强一致性 实现:通过2PC或是Paxos协议保证一致性;

    生产过程中要么保持强一致性,要么拼命的降低并发时脏数据的概率,而Facebook使用了这个降低概率的玩法,因为2PC太慢,而Paxos太复杂。当然,最好还是为缓存设置上过期时间。

    详细可参考:https://blog.csdn.net/zzh920625/article/details/78027534

    拓展优化:

    1.可以使用阿里的方法:CANAL,通过数据库的binlog异步淘汰key,以mysql为例
    可以使用阿里的canal将binlog日志采集发送到MQ队列里面,然后通过ACK机制
    确认处理
    这条更新消息,删除缓存,保证数据缓存一致性。

    2.或者可以直接更新redis数据,先不入db,再某个时间段,再异步的方式通过redis更新DB;


    来源 链接:https://blog.csdn.net/qq_33999844/java/article/details/81531461

    3.或者采用下面方案:

    (1)先淘汰缓存

    (2)再写数据库(这两步和原来一样)

    (3)休眠1秒,再次淘汰缓存.时长不定1s,根据情况,具体设定。要再提高性能,也可以自己起一个线程,异步删除.

    如果怕第二步删除失败,可采用下面方案:

    提供一个保障的重试机制即可,这里给出两套方案。

    方案一:

    如下图所示

    流程如下所示

    (1)更新数据库数据;

    (2)缓存因为种种问题删除失败

    (3)将需要删除的key发送至消息队列

    (4)自己消费消息,获得需要删除的key

    (5)继续重试删除操作,直到成功

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

    方案二:

    流程如下图所示:

    (1)更新数据库数据

    (2)数据库会将操作信息写入binlog日志当中

    (3)订阅程序提取出所需要的数据以及key

    (4)另起一段非业务代码,获得该信息

    (5)尝试删除缓存操作,发现删除失败

    (6)将这些信息发送至消息队列

    (7)重新从消息队列中获得该数据,重试操作。

    备注说明:上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。至于oracle中,博主目前不知道有没有现成中间件可以使用。另外,重试机制,博主是采用的是消息队列的方式。如果对一致性要求不是很高,直接在程序中另起一个线程,每隔一段时间去重试即可,这些大家可以灵活自由发挥,只是提供一个思路。

     
    来源 链接:https://zhuanlan.zhihu.com/p/98909029

  • 相关阅读:
    转:.net面试题及答案(一)
    高兴!
    游标中LOCAL的意思
    九度 1333
    在进程槽中为进程分配一个空闲位置并分配一个进程号
    USACO Section 1.2 Milking Cows
    九度 1334
    USACO Section 1.3 Mixing Milk
    USACO Section 1.3 Calf Flac
    USACO Section 1.3 Prime Cryptarithm
  • 原文地址:https://www.cnblogs.com/yylingyao/p/12767300.html
Copyright © 2011-2022 走看看