00:00
好,同学们,我们继续,那接下来双写一致性,谈谈一下你的理解,也就是一些细则要求。第一个假设当中有数据,你不是有就行了,你要明白你需要有一份可以参考的标准底单数据,那么假设这个时候我们以我们的MYSQL为主,那么有数据的话,你需要和MYSQL数据空中的值。一样数量。匹配同样是五条,数量对得上,值要相同。第二个如果re当中没有数据呢?那么这个时候要保证双写一致性,你需要数据库当中的值是最新的,就是说数据库那边要尽快定下来,MYSQL那边要稳了,然后尽快回写进ready,哎,这是他们的持续性的要求。那么接下来我们缓存按照操作来分呢?我们分为两种,第一种只读缓存啊,这个倒是简单,那么如果是只读缓存,那就没什么好说的了。
01:03
一句话。哥们儿,我这。没有,第三步不要回写,我所有数据我是通过red的命令或者批量脚本灌进去,有就是有,没有就是没有好,我这个red只做纯纯的查询缓存,你不要回血,他有这种业务要求啊,他怕回血出错,干脆就把它拿掉,这是一种。第二种呢,从我的了解,大部分是需要有回写机制的,那么这个时候这个re是读写缓存,就是有写操作,有读操作,那么就分为两种,第一种叫同步止血策略,什么叫同步止血呢?就是只要完成了第二步。MYQ立刻回写ready啊,也就是说双两边尽量的最短的时间内保持一致和同步,所以说这种情况下,就是我写完MYSQL数据库之后也要同步的,立刻回写red缓存保持一致,那么对于读写缓存来说,需要想保证一致,我们就要采用同步止血策略啊,那么什么样的场景呢?啊,比如说一些特别重要的数据,热点敏感数据,或者是VIP数据啊,比如说你现在充了个值,或者是你VIP升级了以后。
02:24
这一秒钟填写,下一秒钟就要更新,所以说有这种即时生效这样的业务要求,那么第二种呢,更多的是这个也推荐大家就是异步环写策略,就是比如说你的业务不用这么急,那假设啊。京东下单,我这一秒钟下单了,我起码可以给对方24小时来给我送达我的东西吧,我不会说这一秒钟下单了,下一秒钟叫快递员上门,那就见鬼了,对不对?所以说针对于这样的业务是允许有一些延时的,那么所以说在正常业务当中,MYSQL数据变动了,比如说我新添了一张订单写进库里面,但是在业务上容许出现一定时间的。
03:09
延时才作用于ready,比如说我们的仓库物流系统积分变更对吧,他要求比如说你下了个单,不是马上送你积分,要求你七天以后或者三天以后没有退东西,哎,他才给你加这个积分啊,确定完整购买以后等等,那么这样的话呢,就是正常业务要求,还有一种情况是异常情况出现。你不得不将失败的动作重新修补。哎,没办法,现在呢,回血失败,那么这个时候我就要人工补偿,或者是什么运维人员来干预,那么有可能需要借助卡不卡,WMQ等等消息中间件实现重试重写机制,所以各位亲,这个也就是我们对一致性。他提出的一些细节和业务要求,那么同步止血和异步环写策略,看你用哪一个根据业务来定技术方案。
04:04
对于缓存双写一致性是什么,以及一些细则要求理论介绍完毕,那么接下来这张图都听懂了,理论OK,实操呢?那么下面的业务逻辑,你这个Java代码该怎么写?你可能会说啊,杨哥,这个我们之前做项目了解过,我们都写过了,那别忘了,这是不是有道面试题?OK,好了,那么什么叫双减加速,这是什么东东呢?那么下面呢,我们来看一下啊,我们之前做过的案例和代码。第一个。各位同学,这个呢,都是我带着大家写过的啊,因为当时呢留了一手,就准备留到这儿来给大家介绍,首先我们一个用户red key的前缀叫user,因为冒号,然后呢,Service调我们的map来调我们的red,这个呢大家都了解过了,那么下面也不罗辑啊。
05:01
打开代码,我们看一下业务很简单,都是我们之前写过的代码,我们在学习spring的时候,Cloud的时候。走,通过T的前缀,结合我们用户ID拼成我们的T,先从RA里面去查询,如果能够查到值,直接返回皆大欢喜,也就是我们这的第一条线绿色版一号,可问题现在我们要研究的是缓存双写一致性,RA里面。没有MYQL有这种情况,好,那么下面看这个代码,走到第二步,Red里面没有,我们是不是继续往前去查询MYSQL,那么又分两种情况,第一个假设MYSQL里面也没有,那么就是我们的3.1这一步red加MYSQL都没有数据,那么这你要么直接返回,也就是个那。但是我们防止别人搞一些莫名其妙的key啊,都没有的,那么要防止什么缓存的多次穿透,我们业务规定,我们可以记录下这个难值的这个key,比如说刚才的user冒号123根本就没有这个key,这个key是一个可能是黑客攻击一个作废的key,我们要把它列入黑名单,或者记录,或者异常等等,好,那么目前我们主要研究的是这个分支right,没有MYQ。
06:26
隔到这进入到我们的3.2步,MYSQ是有的,干嘛?那么我们把从MYCQ查出来的值复制给48行这个变量直接返回,OK,除此之外,我们还要多一步回写数据,将MYSQ的这个值回写进我们的register,保证下一次缓存命中,那么下一次我们还是先去找red,那这个时这个时候是不是就直接找得到?从这查出来这个值,就不会再去打扰MYSQL,就给MYSQL消峰评估了,减轻MYSQ的压力。那么好,同学们,我们之前在学习spring spring cloud案例的时候都是这么做的。
07:05
那么下面没完呢,这个。他没有写到完美,过来看杨哥的笔记,我们之前在学boot cloud项目的时候,业务逻辑都这么写的,没错。那么对于小产和中产,也就是说你的QPS小于1000以下的可以这么干,就是你的并发度低,哎,不是那种高并发的系统,就这么写一下是可以的,但是大厂不可以,为什么?同学们思考一下。刚才我们那个是比较和谐啊,可能也就是一两个线程无所谓,可能在高并发的程序上面,一二三四五六七八九十。一堆线程过来,他们前后间隔。不是那么分得开,可能就是间隔一毫秒甚至一纳秒通通打到MY和ready先走,Ready有没有没有直接马上标到MY,那么这样是不是?
08:04
第一个一下子就把马打高了,第二个问题,回血他们都去,前后间隔可能就一纳秒,都去red查,没有马上都去找MY,而且他们还查出来了,以后还多次回血了。Rad。那么请告诉我,在这样的话就有两个隐患,第一个通通打到MYSQL容易把MYSQL打高,第二个通通回血ready容易出现数据覆盖,有这种情况吗?理由,因为兄弟们,48行往MYSQL查和56行往red回写,这两步是原子操作吗?那么这块是不是容易被高并发多个线程打爆啊?所以我们需要。
09:00
解决一个问题,这种实际生产案例故障,俗称双减加锁策略,这也就是为什么在大厂面试题这会有这么一个问法,他就看你遇到的实际工程经验案例情况是怎么样的。那么来同学们,我们刚才已经说了,一堆线程跑到这儿,一堆线程回血,那么我们是不是希望慢一点,别把买这克打爆打高CPU飙升那么搁到这儿。我们当多个线程同时去查询数据库的这条记录,我们可以在第一个查询线程的这个请求上面使用一个S或者lock and的锁来锁住啊,那么一旦锁住,我们标到这的时候,是不是只允许有一个线程进来?明白吧,那么这样就不会一下子一堆线程把马斯Q打爆,所以同学们请看这其他线程走到这一步,哎呀,他加了个central袋子排他独占的这把锁,我们只能是等着,那么等第一个线程做完这些事了,然后再去做缓存,那么后面的线程,后面的线程进来发现已经有缓存了,我们直接去走缓存,因为red他的并发性要比MYSQL高,所后面只有一个人去找MYSQL回血了以后,后面的也就不再去打扰MYSQLMYSQL就起到了保护作用,它的并发性就下降,不容易被打爆,请看第一个,我们写一下这个代码。
10:32
啊。我们先从RA里面去查这个只要如果。不等于呢,就是有值我们返回,如果说等于呢,那么就是缓存不存在,对什么方法干嘛加锁?同学们请看我这是不是有啊。OK,也就是说在这个加锁了以后,我又干了一次register get,看到没有,这个叫双减加锁,检查两次,在两次之间加了,有点像我们写的那个。
11:04
Double check lock,就是双端检索机制一样的思想,只不过这个是从两次加锁前从长加锁后从一次,那么都没有那么OK,如果说再从red里面再查一遍,第二次查red,那么现在如果说不等于赖返回,否则二次查询也不存在的话,我们再去找我们的MYSQ。然后把数据缓存加个过期时间再返回,OK,所以隔到这儿我们要明白,一般如果你为了避免这个key突然失效,打爆MYSQ这样的话,我们需要做一下预防,尽量不要出现缓存击穿这些情况。那么来同学们lawyer我们该怎么写呢?和之前差不多,还是熟悉的配方,还是熟悉的味道,第一步都去找red,找得到直接返回,那么下面找不到,看好。
12:04
大常用对于多线程高QPS的优化进来我就先加一步锁,我这是不是写了个user service OK,这这个类比那么好,我这。第一步查一下red没有马上进来加个锁,那么现在保加了锁以后,是不是保证现在只有一个请求,一个线程去访问我的MYSQL,我让外面的red你等一下,避免多个请求线程打爆MYSQL,起到一个保护作用,保证系统的健状和稳定性。那么下面加锁前查了一次red,加锁后我再去查询一次red,那么是不是在S之前有一次red查询,在synchize之后有一次查询,两次下来确确实实没有放心了,那么第二次查还是那没有,我们就可以去查MYSQL了,那么本次案例默认MYSQL里面有数据,那么如果从MYSQL里面查出来还是那,那么干嘛?我们查询MYSQL拿到数据默认。
13:14
如果说这块还是个,那我们就返回个,那否则MY这里面有数据的话,我们再写回register,那么这我们用这个命令register set if absent,如果没有就是set an ex,没有就写弱,且我们加个过期时间,那么七天以后这个K就消失了,否则我们避免这个K越来越大,越来越多。回写了以后,就完成了数据一致性的同步工作,所以说同学们这个就叫双。减。加锁策略的保证的一致性,那么在这儿也给大家呢,写了代码,OK,所以这个非常重要,这是避免打爆MYSQL的一种保护机制啊,请同学们务必掌握,你要在大厂里面写这个代码的话,写这段代码建议大家用这种,当然啊,这稍微可能会卡一下,会慢一点啊,因为毕竟加了把锁,但是为了保证买色铁不被达飙升,这个代价是值得的,所以说就是双减策略,加锁之前查一次,加锁之后再查一次,确定red没有了,再后面买这个操作再回血,那么我这第一个进来回血完成以后,示范锁后面的那个如法炮制也过来,哦,我这已经有了,因为第一个线程已经回血成功了,那么我也就不再去找MYSQ,这样我的系统就不容易被打爆,OK,好,那么同学们,这个就是我们的双减加锁策略相关的介绍。
14:42
接下来我们说一下数据库和缓存一致性的更新策略,也即针对于这块和这块这两个面试题,说白了肯定会有数据的变动,你以哪一个为准?你先动ready还是先动MYSQ,这个是有讲究的。好。那么我们下面来看一下第一个我们的目的。
15:10
要达到最终一致性。那现在。你到底是以write为准还是以MYSQ为准呢?我们这就先商量好了,那么来吧,我们首先缓存一般都会给它设置个过期时间啊,让它自我更新一下,定期清理缓这个缓存,并且要把它从MYSQL回写进ready,以便日常工作中保持双写一致性。但是我们最终的目的是要保证最终一致性的解决方案。那我们对存入的缓存设置过期时间,所有的写操作就是增删改查四大操作,增删改是不是都是写操作?我们以什么数据库MYSQL为准?对缓存red的操作只是尽最大努力即可。也就是说,如果数据库写成功,缓存更新失败了,那么只要达到过期时间,我们后面的读请求自然会从数据库当中读取最新的值,然后回填缓存会达到一致性,明白吗?这也就是为什么我们要给缓存设置过期时间的直接原因,也即我们更新只要是真实的数据,我们必须要保留一份底单数据。
16:31
按照我工作经验和大厂调研,大家一般偏向于以MYSQL数据为最终的一份。底单数据具有唯一解释性,那就是假,假设两个数据出现的不一致,或者同一条数据内容不一样,我们以买CK为准,那所以说在这块以后我们要慢慢的以它为参考调整我们的RA。那么基于此,我们以MYSQL数据库的写入库为。
17:01
底单数据为唯一合法解释的最终底线。那么上述方案。和后续的落地案例啊,都是杨哥调研以后主流加陈述的做法啊,我那些大厂弟子啊,包括一些大厂的技术粉丝啊,以前的同事啊等等,大部分我问了都这么着,那么当然了,我也不敢说把话说全了,考虑到各个公司业务系统的差距啊,下面不是100%的绝对正确,我不保证绝对的适配全部情况,那么请同学们自行斟酌选择打法合适自己的最好,那么一般而言啊,我认为应该是覆盖主流和成熟的经验了,主要。两大方法,第一种可不可以停机?比如说今天晚上我们呢,要更新一下我们的数据,先往MYSQL灌入了1万条数据,然后再解决red跟他数据同步的问题,那么假设可以停机的情况下,这是最好,对吧?我们不用一边奔跑一边换轮胎,那么来吧。
18:04
咋整?经常见到的凌晨三点钟开始干活,挂牌报错。凌晨升级温馨提示,服务降级,不好意思啊,尊敬的顾客,系统升级中,请明早上八点再试对吧?或者隔一天再来,那么补掉这个外界的操作,专心致志搞缓存双写一致性的数据问题,那么我们单线程来做这个事儿,就是外面的访问就没有了,专门找一个运维工程师啊,那么这样重量级的数据操作最好不要多线程,那么如果你们公司的业务是可以这样的,那么当然皆大欢喜对吧?但是呢,不好意思,难就难在这儿,一般互联网的这个。业务是。不大方便停机的,你不能说凌晨三点钟我不能去京东上买东西,不能去淘宝上下单,所以这个时候我们就要讨论后续四种更新策略。来,先更新数据库,再更新缓存,先更新缓存,再更新数据库,先删除缓存,再更新数据库,或者说先更新数据库,再删除缓存。
19:12
听完念完一遍,也大概晕了吧,那同学们做好准备。杨哥带你开车了。
我来说两句