精华内容
下载资源
问答
  • Redis主要解决了关系型数据库并发量低的问题,有助于缓解关系型数据库在高并发场景下的压力,提高系统的吞吐量(具体Redis是如何提高系统的性能、吞吐量,后面会专门讲)。 而我们在Redis的实际使用过程中,...
        

    欢迎关注专栏:Java架构技术进阶。里面有大量batj面试题集锦,还有各种技术分享,如有好文章也欢迎投稿哦。

    Redis是企业级系统高并发、高可用架构中非常重要的一个环节。Redis主要解决了关系型数据库并发量低的问题,有助于缓解关系型数据库在高并发场景下的压力,提高系统的吞吐量(具体Redis是如何提高系统的性能、吞吐量,后面会专门讲)。

    而我们在Redis的实际使用过程中,难免会遇到缓存与数据库双写时数据不一致的问题,这也是我们必须要考虑的问题。如果还有同学不了解这个问题,可以搬小板凳来听听啦。

    一、数据库+缓存双写不一致问题引入

    要讲数据库+缓存双写不一致的问题,就需要先讲一下这个问题是怎么发生的。我们选择电商系统中要求数据实时性较高的库存服务来举例讲讲这个问题。

    库存可能会修改,每次修改数据库的同时也都要去更新这个缓存数据;;每次库存的数据,在缓存中一旦过期,或者是被清理掉了,前端对库存数据的请求都会发送给库存服务,去获取相应的数据。

    库存这一块,写数据库的时候,直接更新redis缓存吗?实际上不是,因为没有这么简单。这里,其实就涉及到了一个问题,数据库与缓存双写,数据不一致的问题。围绕和结合实时性较高的库存服务,把数据库与缓存双写不一致问题以及其解决方案,给大家分享一下。

    二、各种级别的不一致问题及解决方案

    1.最初级的缓存不一致问题及解决方案

    问题

    如果是先修改数据库,再删除缓存的方案,会有问题,试想,如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,出现数据不一致的情况。

    解决思路

    反过来,先删除缓存,再修改数据库。读缓存读不到,查数据库更新缓存的时候就拿到了最新的库存数据。如果删除缓存成功了,而修改数据库失败了,那么数据库中依旧是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。

    2.比较复杂的数据不一致问题分析

    当库存数据发生了变更,我们先删除了缓存,然后要去修改数据库。

    设想一下,如果这个时候修改数据库的操作还没来及完成,突然一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。

    数据变更的操作完成后数据库的库存被修改成了新值,但缓存中又变成了旧数据。那么这个时候是不是还会出现缓存和数据库不一致的情况?

    3.为何上亿流量高并发时会出现该问题?

    上述问题,只有在对一个数据在并发的进行读写的时候,才可能会出现。

    其实如果并发量很低的话,特别是读并发很低,每天访问量就1万次,那么很少的情况下,会出现刚才描述的那种不一致的场景。

    但是问题是,高并发了以后,问题是很多的。如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况。

    怎么解决?

    4.更新与读取操作进行异步串行化

    这里说一种解决方案。

    不就是还没更新数据库的就查数据库读到旧数据吗?不就是因为读在更新前面了吗?那我就让你排队执行呗。

    4.1 异步串行化

    我在系统内部维护n个内存队列,更新数据的时候,根据数据的唯一标识,将该操作路由之后,发送到其中一个jvm内部的内存队列中(对同一数据的请求发送到同一个队列)。读取数据的时候,如果发现数据不在缓存中,并且此时队列里有更新库存的操作,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也将发送到同一个jvm内部的内存队列中。然后每个队列对应一个工作线程,每个工作线程串行地拿到对应的操作,然后一条一条的执行。

    这样的话,一个数据变更的操作,先执行删除缓存,然后再去更新数据库,但是还没完成更新的时候,如果此时一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,排在刚才更新库的操作之后,然后同步等待缓存更新完成,再读库。

    4.2 读操作去重

    多个读库更新缓存的请求串在同一个队列中是没意义的,因此可以做过滤,如果发现队列中已经有了该数据的更新缓存的请求了,那么就不用再放进去了,直接等待前面的更新操作请求完成即可,待那个队列对应的工作线程完成了上一个操作(数据库的修改)之后,才会去执行下一个操作(读库更新缓存),此时会从数据库中读取最新的值,然后写入缓存中。

    如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回; 如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。(返回旧值不是又导致缓存和数据库不一致了么?那至少可以减少这个情况发生,因为等待超时也不是每次都是,几率很小吧。这里我想的是,如果超时了就直接读旧值,这时候仅仅是读库后返回而不放缓存)

    5.高并发的场景下,该方案要注意的问题

    高并发的场景下,该解决方案其实还是有一些问题需要特别注意的。

    5.1 读请求长时阻塞

    由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。

    该解决方案,最大的风险点在于,数据更新很频繁的情况下导致队列中积压了大量更新操作在里面,然后读请求会发生大量的超时,最后导致大量的请求直接走数据库取到了旧值。所以,务必通过一些模拟真实的测试,看看更新数据频繁的场景下是怎样的。

    另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,确定一个实例中创建多少个内存队列,且可能需要部署多个服务,每个服务分摊一些数据的更新操作。

    如果一个内存队列里积压100个商品的库存修改操作,每个库存修改操作要耗费10ms去完成,那么最后一个商品的读请求,可能等待10 * 100 = 1000ms = 1s后,才能得到数据。

    这个时候就导致读请求的长时阻塞。

    一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会hang多少时间。如果读请求在200ms返回,而且你计算过后,哪怕是最繁忙的时候,积压10个更新操作,最多等待200ms,那还可以的。

    如果一个内存队列可能积压的更新操作特别多,那么你就要加机器,让每个机器上部署的服务实例处理更少的数据,那么每个内存队列中积压的更新操作就会越少。

    Tips:
    其实根据之前的项目经验,一般来说数据的写频率是很低的,因此实际上正常来说,在队列中积压的更新操作应该是很少的。

    针对读高并发,读缓存架构的项目,一般写请求相对读来说,是非常非常少的,每秒的QPS能到几百就不错了。

    假如一秒500的写操作,可以看成5份,每200ms就100个写操作。对于单机器,如果又20个内存队列,每个内存队列,可能就积压5个写操作,每个写操作性能测试后,一般在20ms左右就完成。

    那么针对每个内存队列中的数据的读请求,也就最多hang一会儿,200ms以内肯定能返回了。

    假如写QPS扩大10倍,但是经过刚才的测算,就知道,单机支撑写QPS几百没问题,那么就扩容机器,扩容10倍的机器,10台机器,每个机器20个队列,200个队列。

    大部分的情况下,应该是这样的:大量的读请求过来,都是直接走缓存取到数据的。少量情况下,可能遇到读和数据更新冲突的情况。如上所述,那么此时更新操作如果先入队列,之后可能会瞬间来了对这个数据大量的读请求,但是因为做了去重的优化,所以也就一个更新缓存的操作跟在它后面。

    等数据更新完了,读请求触发的缓存更新操作也完成,然后临时等待的读请求全部可以读到缓存中的数据。

    5.2 读请求并发量过高

    这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时hang在服务上,看服务能不能抗的住,需要多少机器才能抗住最大的极限情况的峰值。

    但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。

    Tips:
    如果按1:99的比例计算写和读的请求,那么每秒5万的读QPS,可能只有500次更新操作。
    如果一秒有500的写QPS,那么要测算好,可能写操作影响的数据有500条,这500条数据在缓存中失效后,可能导致多少读缓存的请求,发送读请求到库存服务来,要求更新缓存。

    一般来说,按1:2的话,每秒钟有1000个读请求去读这500个正在更新库的数据,就会有1000个请求hang在库存服务上,如果规定每个请求200ms就返回,那么每个读请求最多hang多少时间,这个一定要测算好。

    在同一时间最多hang住的可能也就是单机200个读请求,最坏的同时hang住,单机hang200个读请求,还是ok的。

    但是假如1:20,每秒更新500条数据,这500秒数据对应的读请求,会有20 * 500 = 1万,1万个读请求全部hang在库存服务上,就死定了。

    5.3 多服务实例部署的请求路由

    可能这个库存服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,对于同一商品的读写请求全部路由到同一台机器上。可以自己去做服务间的按照某个请求的参数做hash路由,也可以通过nginx服务器hash路由的功能路由到相同的服务实例上。

    5.4 热点商品的路由问题,导致请求的倾斜

    万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能造成某台机器的压力过大。

    但是因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以更新频率不是太高的话,这个问题的影响并不是特别大。

    但是的确可能某些机器的负载会高一些。

    三、总结

    一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,那最好不要上述的串行化的这个方案,因为读请求和写请求串行化,串到一个内存队列里去,这样是可以保证一定不会出现不一致的情况。但是,串行化之后,就会导致系统的吞吐量会大幅度的降低,你就需要用比正常情况下多几倍的机器去支撑线上的一个请求。

    另外,不是说,讲课的、写文章的就是超人,万能的。就跟写书一样,很可能会写错,也可能有些方案里的一些地方,没考虑到。或者有些方案只是适合某些场景,在某些场景下,可能需要你进行方案的优化和调整才能适用于你自己的项目。

    如果大家觉得对这些方案有什么疑问或者见解,都可以沟通交流。如果的确觉得是讲解的不对,或者有些地方考虑不周,那么都是可以交流的,多多包涵。

    https://www.cnblogs.com/ibigboy/p/12149089.html?utm_source=tuicool&utm_medium=referral

    展开全文
  •    本文由以下三个部分组成(特别说明:此文参考今日...这种方案下,我们可以对存入缓存的数据设置过期时间,所有的操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败...

      
    本文由以下三个部分组成(特别说明:此文参考今日头条技术文档)
      1、讲解缓存更新策略
      2、对每种策略进行缺点分析
      3、针对缺点给出改进方案

    正文

      先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。因此,接下来讨论的思路不依赖于给缓存设置过期时间这个方案。

    在这里,我们讨论三种更新策略:

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

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

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

      应该没人问我,为什么没有先更新缓存,再更新数据库这种策略。

    (1)先更新数据库,再更新缓存

      这套方案,大家是普遍反对的。为什么呢?有如下两点原因。

      原因一(线程安全角度)
      同时有请求A和请求B进行更新操作,那么会出现

      (1)线程A更新了数据库

      (2)线程B更新了数据库

      (3)线程B更新了缓存

      (4)线程A更新了缓存

      这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。

      原因二(业务场景角度)
      有如下两点:

      (1)如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。

      (2)如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。

    技术讨论:Redis缓存和数据库双写一致性问题
    接下来讨论的就是争议最大的,先删缓存,再更新数据库。还是先更新数据库,再删缓存的问题。

    (2)先删缓存,再更新数据库

      该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:

      (1)请求A进行写操作,删除缓存

      (2)请求B查询发现缓存不存在

      (3)请求B去数据库查询得到旧值

      (4)请求B将旧值写入缓存

      (5)请求A将新值写入数据库

    上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。

      那么,如何解决呢?采用延时双删策略

      伪代码如下

    public void write(String key,Object data){
    
    redis.delKey(key);
     db.updateData(data);
     Thread.sleep(1000);
     redis.delKey(key);
    }
    

    转化为中文描述就是

      (1)先淘汰缓存

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

      (3)休眠1秒,再次淘汰缓存

      这么做,可以将1秒内所造成的缓存脏数据,再次删除。

      那么,这个1秒怎么确定的,具体该休眠多久呢?

      针对上面的情形,读者应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

      如果你用了mysql的读写分离架构怎么办?

      ok,在这种情况下,造成数据不一致的原因如下,还是两个请求,一个请求A进行更新操作,另一个请求B进行查询操作。

      (1)请求A进行写操作,删除缓存

      (2)请求A将数据写入数据库了,

      (3)请求B查询缓存发现,缓存没有值

      (4)请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值

      (5)请求B将旧值写入缓存

      (6)数据库完成主从同步,从库变为新值

    上述情形,就是数据不一致的原因。还是使用双删延时策略。只是,睡眠时间修改为在主从同步的延时时间基础上,加几百ms。

      采用这种同步淘汰策略,吞吐量降低怎么办?

      ok,那就将第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后了,再返回。这么做,加大吞吐量。

      第二次删除,如果删除失败怎么办?

      这是个非常好的问题,因为第二次删除失败,就会出现如下情形。还是有两个请求,一个请求A进行更新操作,另一个请求B进行查询操作,为了方便,假设是单库:

      (1)请求A进行写操作,删除缓存

      (2)请求B查询发现缓存不存在

      (3)请求B去数据库查询得到旧值

      (4)请求B将旧值写入缓存

      (5)请求A将新值写入数据库

      (6)请求A试图去删除请求B写入对缓存值,结果失败了。

    ok,这也就是说。如果第二次删除缓存失败,会再次出现缓存和数据库不一致的问题。

    如何解决呢?

      具体解决方案,且看博主对第(3)种更新策略的解析。

    (3)先更新数据库,再删缓存

      首先,先说一下。老外提出了一个缓存更新套路,名为《Cache-Aside pattern》。其中就指出

      失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

      命中:应用程序从cache中取数据,取到后返回。

      更新:先把数据存到数据库中,成功后,再让缓存失效。

      另外,知名社交网站facebook也在论文《Scaling Memcache at Facebook》中提出,他们用的也是先更新数据库,再删缓存的策略。
    这种情况不存在并发问题么?

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

      (1)缓存刚好失效

      (2)请求A查询数据库,得一个旧值

      (3)请求B将新值写入数据库

      (4)请求B删除缓存

      (5)请求A将查到的旧值写入缓存

    ok,如果发生上述情况,确实是会发生脏数据。

    然而,发生这种情况的概率又有多少呢?

      发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。

      假设,有人非要抬杠,有强迫症,一定要解决怎么办?

    如何解决上述并发问题?

      首先,给缓存设有效时间是一种方案。其次,采用策略(2)里给出的异步延时删除策略,保证读请求完成以后,再进行删除操作。

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

      有的,这也是缓存更新策略(2)和缓存更新策略(3)都存在的一个问题,如果删缓存失败了怎么办,那不是会有不一致的情况出现么。比如一个写数据请求,然后写入数据库了,删缓存失败了,这会就出现不一致的情况了。这也是缓存更新策略(2)里留下的最后一个疑问。
    如何解决?

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

    方案一:

    如下图所示
    在这里插入图片描述

    流程如下所示

      (1)更新数据库数据;

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

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

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

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

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

    方案二:

    流程如下图所示:
    在这里插入图片描述
      (1)更新数据库数据

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

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

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

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

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

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

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

    展开全文
  • 解决:时效性要求很高的数据,库存,采取的是数据库+缓存双写技术方案,也解决双写的一致性的问题 最经典的缓存+数据库读写的模式,cache aside pattern Cache Aside Pattern (1)读的时候,先读缓存,缓存...

    解决:时效性要求很高的数据,库存,采取的是数据库+缓存双写的技术方案,也解决双写的一致性的问题

    最经典的缓存+数据库读写的模式,cache aside pattern

    Cache Aside Pattern

    • (1)读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应
    • (2)更新的时候,先删除缓存,然后再更新数据库

    为什么是删除缓存,而不是更新缓存呢?

    原因很简单,很多时候,复杂点的缓存的场景,因为缓存有的时候,不简单是数据库中直接取出来的值

    比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据,并进行运算,才能计算出缓存最新的值的

    更新缓存的代价是很高的

    是不是说,每次修改数据库的时候,都一定要将其对应的缓存去跟新一份?也许有的场景是这样的,但是对于比较复杂的缓存数据计算的场景,就不是这样了

    如果你频繁修改一个缓存涉及的多个表,那么这个缓存会被频繁的更新,频繁的更新缓存

    但是问题在于,这个缓存到底会不会被频繁访问到???

    举个例子,一个缓存涉及的表的字段,在1分钟内就修改了20次,或者是100次,那么缓存跟新20次,100次; 但是这个缓存在1分钟内就被读取了1次,有大量的冷数据

    所以记住一个28法则,黄金法则,20%的数据,占用了80%的访问量所以还有百分之80的数据访问量很少

     

    1、最初级的缓存不一致问题以及解决方案

    问题:先修改数据库,再删除缓存,如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据出现不一致

    解决思路

    先删除缓存,再修改数据库,如果删除缓存成功了,如果修改数据库失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致

    因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中

    2、比较复杂的数据不一致问题分析

    数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改,

    一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中

    数据变更的程序完成了数据库的修改

    完了,数据库和缓存中的数据不一样了。

     

     

    • 为什么上亿流量高并发场景下,缓存会出现这个问题?   

    只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题

    其实如果说你的并发量很低的话,特别是读并发很低,每天访问量就1万次,那么很少的情况下,会出现刚才描述的那种不一致的场景

    但是问题是,如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况

    高并发了以后,问题是很多的

    • 解决方案:数据库与缓存更新与读取操作进行异步串行化

    更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个jvm内部的队列中

    读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个jvm内部的队列中

    一个队列对应一个工作线程

    每个工作线程串行拿到对应的操作,然后一条一条的执行

    这样的话,一个数据变更的操作,先执行,删除缓存,然后再去更新数据库,但是还没完成更新

    此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成

    这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可

    待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中

    如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回; 如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值

    • 高并发的场景下,该解决方案要注意的问题
    • (1)读请求长时阻塞

    由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回

    该解决方案,最大的风险点在于说,可能数据更新很频繁,导致队列中积压了大量更新操作在里面,然后读请求会发生大量的超时,最后导致大量的请求直接走数据库

    务必通过一些模拟真实的测试,看看更新数据的频繁是怎样的

    另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要部署多个服务,每个服务分摊一些数据的更新操作

    如果一个内存队列里居然会挤压100个商品的库存修改操作,每隔库存修改操作要耗费10ms区完成,那么最后一个商品的读请求,可能等待10 * 100 = 1000ms = 1s后,才能得到数据

    这个时候就导致读请求的长时阻塞

    一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会hang多少时间,如果读请求在200ms返回,如果你计算过后,哪怕是最繁忙的时候,积压10个更新操作,最多等待200ms,那还可以的

    如果一个内存队列可能积压的更新操作特别多,那么你就要加机器,让每个机器上部署的服务实例处理更少的数据,那么每个内存队列中积压的更新操作就会越少

    其实根据之前的项目经验,一般来说数据的写频率是很低的,因此实际上正常来说,在队列中积压的更新操作应该是很少的

    针对读高并发,读缓存架构的项目,一般写请求相对读来说,是非常非常少的,每秒的QPS能到几百就不错了

    一秒,500的写操作,5份,每200ms,就100个写操作

    单机器,20个内存队列,每个内存队列,可能就积压5个写操作,每个写操作性能测试后,一般在20ms左右就完成

    那么针对每个内存队列中的数据的读请求,也就最多hang一会儿,200ms以内肯定能返回了

    写QPS扩大10倍,但是经过刚才的测算,就知道,单机支撑写QPS几百没问题,那么就扩容机器,扩容10倍的机器,10台机器,每个机器20个队列,200个队列

    大部分的情况下,应该是这样的,大量的读请求过来,都是直接走缓存取到数据的

    少量情况下,可能遇到读跟数据更新冲突的情况,如上所述,那么此时更新操作如果先入队列,之后可能会瞬间来了对这个数据大量的读请求,但是因为做了去重的优化,所以也就一个更新缓存的操作跟在它后面

    等数据更新完了,读请求触发的缓存更新操作也完成,然后临时等待的读请求全部可以读到缓存中的数据

    • (2)读请求并发量过高

    这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时hang在服务上,看服务能不能抗的住,需要多少机器才能抗住最大的极限情况的峰值

    但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大

    按1:99的比例计算读和写的请求,每秒5万的读QPS,可能只有500次更新操作

    如果一秒有500的写QPS,那么要测算好,可能写操作影响的数据有500条,这500条数据在缓存中失效后,可能导致多少读请求,发送读请求到库存服务来,要求更新缓存

    一般来说,1:1,1:2,1:3,每秒钟有1000个读请求,会hang在库存服务上,每个读请求最多hang多少时间,200ms就会返回

    在同一时间最多hang住的可能也就是单机200个读请求,同时hang住

    单机hang200个读请求,还是ok的

    1:20,每秒更新500条数据,这500秒数据对应的读请求,会有20 * 500 = 1万

    1万个读请求全部hang在库存服务上,就死定了

    • (3)多服务实例部署的请求路由

    可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过nginx服务器路由到相同的服务实例上

    • (4)热点商品的路由问题,导致请求的倾斜

    万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能造成某台机器的压力过大

    就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以更新频率不是太高的话,这个问题的影响并不是特别大

    但是的确可能某些机器的负载会高一些

    展开全文
  • 打算采用主的架构实现这个需求,但是通常主架构是为了备份,不是为了多点写入,所以实际项目中多个系统只访问主中 的一个数据库,另一个作为实时备份,在一个服务器故障时可以立刻切换到另一个服务器,业务...

    1.需求描述

        近期一个项目需要实现两个数据库间的数据双向实时同步。

    2.解决思路

       打算采用双主的架构实现这个需求,但是通常双主架构是为了备份,不是为了多点写入,所以实际项目中多个系统只访问双主中 的一个数据库,另一个作为实时备份,在一个服务器故障时可以立刻切换到另一个服务器,业务数据访问不受影响。

    3.解决方案

    MYSQL主从同步是目前使用比较广泛的数据库架构,技术比较成熟,配置也不复杂,特别是对于负载比较大的网站,主从同步能够有效缓解数据库读写的压力。

    MySQL主从同步的机制

    MYSQL主从同步是在MySQL主从复制(Master-Slave Replication)基础上实现的,通过设置在Master MySQL上的binlog(使其处于打开状态),Slave MySQL上通过一个I/O线程从Master MySQL上读取binlog,然后传输到Slave MySQL的中继日志中,然后Slave MySQL的SQL线程从中继日志中读取中继日志,然后应用到Slave MySQL的数据库中。这样实现了主从数据同步功能。

    MySQL主从同步的作用

    1、可以作为一种备份机制,相当于热备份

    2、可以用来做读写分离,均衡数据库负载

    MySQL主从同步的步骤

    一、准备操作

    1、主从数据库版本一致,建议版本5.5以上

    2、主从数据库数据一致

    二、主数据库master修改

     

    1、修改MySQL配置:

     

    # 日志文件名

    log-bin = mysql-bin

     

    # 主数据库端ID号

    server-id = 1

     

    # 不同步哪些数据库

    binlog-ignore-db = mysql

    binlog-ignore-db = performance_schema

    binlog-ignore-db = information_schema

     

    # 只同步哪些数据库,除此之外,其他不同步

    binlog-do-db = quality-control

     

    # 允许复制主服务的库的哪一张表

    replicate-do-table=quality-control.t_todo

     

    # 日志保留时间

    expire_logs_days = 10

     

    # 控制binlog的写入频率。每执行多少次事务写入一次

    # 这个参数性能消耗很大,但可减小MySQL崩溃造成的损失

    sync_binlog = 1

     

    # 日志格式,建议mixed

    # statement 保存SQL语句

    # row 保存影响记录数据

    # mixed 前面两种的结合

    binlog_format = mixed

     

    2、重启mysql,创建用于同步的账户:

    # 创建slave帐号slave_account,密码123456

    mysql>grant replication slave on *.* to 'slave_account'@'%' identified by '123456';

     

    如果账户仅用于复制,那么replication slave的权限就够了,但在本地查看从库(slave server)信息,还需要replication client权限。

     

    # 更新数据库权限

    mysql>flush privileges;

     

    3、查询master的状态

     

    mysql> show master status;

    +------------------+----------+--------------+------------------+

    | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |

    +------------------+----------+--------------+------------------+

    | mysql-bin.000009 |      196 |              |                  |

    +------------------+----------+--------------+------------------+

    1 row in set

     注:执行完这个步骤后不要再操作主数据库了,防止主数据库状态值变化

     

    三、从数据库slave修改

    1、修改MySQL配置:

    log_bin =mysql-bin

    #relay_log=/path_to_mysql_log/mysql-relay-bin

    log_slave_updates=1

    read_only=1

    binlog-do-db = quality-control

     

    # 从数据库端ID号

    server-id =2

     

    2、重启mysql执行同步命令

    # 执行同步命令,设置主数据库ip,同步帐号密码,同步位置

    mysql>change master to master_host='172.16.17.8',master_user='slave_account',master_password='123456',master_log_file='mysql-bin.000009',master_log_pos=196;

     

    # 开启同步功能

    mysql>start slave;

     

    3、检查从数据库状态:

     

    mysql>

    *************************** 1. row ***************************

                   Slave_IO_State: Waiting for master to send event

                      Master_Host: 172.16.17.8

                      Master_User: slave_account

                      Master_Port: 3306

                    Connect_Retry: 60

                  Master_Log_File: mysql-bin.000009

              Read_Master_Log_Pos: 196

                   Relay_Log_File: vicky-relay-bin.000002

                    Relay_Log_Pos: 253

            Relay_Master_Log_File: mysql-bin.000009

                 Slave_IO_Running: Yes

                Slave_SQL_Running: Yes

                  Replicate_Do_DB:

              Replicate_Ignore_DB:

              ...

    注:Slave_IO_Running及Slave_SQL_Running进程必须正常运行,即YES状态,否则说明同步失败。

    到这里,主从数据库设置工作已经完成,自己可以新建数据库和表,插入和修改数据,测试一下是否成功

     

    四、反向配置

    反向配置参考上述步骤,只有server_id不同

     

    五、其他可能用到的相关参数

     

    slave端:

    # 停止主从同步

    mysql> stop slave;

     

    # 连接断开时,重新连接超时时间

    mysql> change master to master_connect_retry=50;

     

    # 开启主从同步

    mysql> start slave;

    以上连接超时设置,类似方式可用于设置主数据库ip,同步帐号密码,同步位置

     

    展开全文
  • 各位关心OceanBase...OceanBase数据库是阿里巴巴和蚂蚁金服完全自主研发的金融级分布式关系数据库系统,和基于开源数据库产品进行改造的解决方案不同的是:OceanBase内核100多万行代码都是我们的同学一行行出...
  • 数据安全是数据库安全性的核心要素,达梦数据库(以下简称达梦...横向来看,用户和用户之间是会话独立的、操作层面实现REDO、UNDO等恢复保护机制、落地数据提供双写文件等备份机制。鉴于达梦安全体系的复杂性,我们对...
  • 数据安全是数据库安全性的核心要素,达梦数据库(以下简称达梦)从多维度...横向来看,用户和用户之间是会话独立的、操作层面实现REDO、UNDO等恢复保护机制、落地数据提供双写文件等备份机制。鉴于达梦安全体系的复杂...
  • 一、数据库架构原则二、常见的架构方案方案一:主备架构,只有主库提供读写服务,备库冗余作故障转移用方案二:主架构,两个主库同时提供服务,负载均衡方案三:主从架构,一主多从,读写分离方案四:主+主从...
  • 最近公司想把原Oracle数据库都迁移到Mysql,这个切换需要一段时间过渡,所以存在Oracle、Mysql在项目中同时使用的情况。这样就需要使用多数据源的技术。多数据源配置本身比较简单,但有一个场景出现了问题。考虑如下...
  • 1 需求概述2 技术方案2.1 活同步条件2.1.1 自增主键2.1.2 同步用户2.1.3 启用BinLog2.2 配置复制任务2.2.1 单向复制任务2.2.2 反向复制任务2.3 启动复制任务1 需求概述多地业务系统直接读写某地A的MySQL中心数据库...
  • MySQL高可用方案

    千次阅读 2019-12-02 18:11:10
    2 技术方案 2.1 活同步条件 2.1.1 自增主键 2.1.2 同步用户 2.1.3 启用BinLog 2.2 配置复制任务 2.2.1 单向复制任务 2.2.2 反向复制任务 2.3 启动复制任务 1 需求概述 多地业务系统直接读写某地A的...
  • 缓存的技术方案分析

    2019-01-17 09:34:20
    1.数据库与缓存双写,数据不一致的问题 1.最初级的缓存不一致问题以及解决方案 问题: 先修改数据库,再删除缓存,如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据出现不一致 解决思路: 先...
  • Mycat是一个开源的分布式数据库系统,其核心功能是分表分库,即将一个大表水平分割为多个小表,存储在后端MySQL... 本课程适合有一定MySQL基础的学员,让学员能掌握数据库优化的解决方案,应用在真实项目系统搭建中。
  • 对于高并发的业务场景,常用的技术手段包括黑白名单、限流防刷、熔断降级、兜底、隔离、多级缓存(客户端、CDN、NGINX、内存缓存、分布式缓存)等等。这些手段相互结合,才能应对高并发场景下的各种细分场景。本文...
  • 实时性比较高数据,比如说库存,销量之类的这种数据,我们采取的实时的缓存+数据库双写技术方案,双写一致性保障的方案 实时性要求不高数据,比如说商品的基本信息,等等,我们采取的是三级缓存架构的技术方案,...
  • 5.1.1数据读一致性与一致性 5.1.2多版本数据块 5.1.3 ANSIISO事务隔离级别 5.1.4 Oracle事务隔离级别 5.1.5锁管理器 5.2 RAC资源的协调和管理 5.2.1 Cache Fusion的结构 5.2.2 Cache Fusion工作原理 5.2.3...
  • 在企业中,数据库高可用一直是企业的重中之重,中小企业很多都是使用mysql主从方案,一主多从,读写分离等,但是单主存在单点故障,从库切换成主库需要作改动。因此,如果是主或者多主,就会增加mysql入口,增加高...
  • QuestDB是用于时间序列数据的高性能开源SQL数据库。 它使用面向列的方法,大量并行向量化执行,SIMD指令以及一系列低延迟技术。 整个代码库都是从头开始构建的,没有依赖关系,并且100%没有垃圾回收。 QuestDB...
  • Mysql-mmm就是mysql主主复制管理器,实现的功能有:高可用性(类似keepalived的vip浮动技术)同个时间只提供一台数据库写操作,保证数据库的一致性。提升slave为master,延续主的架构首先先搭建好数据库主主,主从...
  • 要想学习和掌握它的诸多新特性,只能从Oracle手册入手,而数万页的11g手册不免让人心存畏惧,从中挑出对新特性的描述更需要一“火眼金睛”。  好消息!在本书第1版出版时隔4年后,Thomas Kyte及时了解了大家的这...
  • 实时性比较高的那块数据,比如说库存,销量之类的这种数据,我们采取的实时的缓存+数据库双写技术方案,双写一致性保障的方案 实时性要求不高的数据,比如说商品的基本信息,等等,我们采取的是三级缓存架构的...
  • Mysql-mmm就是mysql主主复制管理器,实现的功能有: 高可用性(类似keepalived的vip浮动技术) ...同个时间只提供一台数据库写操作,保证数据库的一致性。 提升slave为master,延续主的架构   首先先搭建
  • 涉及到数据更新:数据库和缓存更新,就容易...这里给出两个解决方案,先易后难,结合业务和技术代价选择使用。 延时删策略 DB前后都执行redis.del(key),并设定合理超时时间。 执行流程 先删除缓存 再写数据库
  • mysql 读写分离读写分离首先不推荐使用,很多业务场景也没有必要,不仅增加技术复杂度,而且可能会导致读到落后的数据,建议优化数据库,推荐使用keepalive+mysql主复制的方案然后mysql-proxy是官方推出的中间件,...
  • 1.1 秒杀系统架构 秒杀技术特点 读多少 举例: 淘宝商场在十一活动时,可能有几千万人在浏览同一个商品,而在这个时间点里读的是特别多,但是...比如有1000件商品,然后有多个人对数据库进行的操作,也就是...
  • 容灾技术分析

    2013-12-19 14:16:24
    数据复制技术很多,初步比较如下。 后面重点讨论银行最常用的存储复制和数据库复制。。当然,我最推荐的还是应用方式。。只有应用做好了才能做到真正的多活应用...应用双写 应用同时连接两个数据库将数据写入的方

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 158
精华内容 63
关键字:

数据库双写技术方案