说明:
(1)本篇博客内容:介绍了数据库和缓存双写数据不一致的问题;然后,介绍了一种缓存双删的解决方案;这是一种,保证MySQL和Redis数据最终一致性的方案;
(2)本篇博客的第五部分,梳理了本篇博客逻辑,十分重要;
目录
一:本篇博客合理性说明;
二:双写数据不一致,简介;
三:缓存数据双写一致,简介;
(1)双写一致是什么;
(2)DB + Redis 使用场景
(3)双写不一致,是什么;
(4)应该怎么做,才能实现双写一致;
(5)小节
四:实现双写一致的第二种方案:缓存双删;
1.第一删;
2.第二删;
3.分析;
4.疑问与解答;
五:整体逻辑分析;
一:本篇博客合理性说明;
二:双写数据不一致,简介;
由于分布性系统,不能保证每个节点都可用,所有可能引起redis在极限情况下数据没有写入成功。那么此时缓存中的数据和数据库数据不一致。
(1)我们有数据库和Redis,一开始时,数据库和Redis中是一致的,里面都有一个{name:imooc}这个数据;
(2)此时,用户发起了一个新的请求,这个请求是要修改name这个数据,将其更改为{name:muke};
(3)那么,在修改的时候,用户首先是把数据更新到数据库;然后,要把新的数据更新到Redis中;
(4)但是,Redis有可能(虽然这个可能性很低)发生一些网络故障,导致{name:muke}这个新数据,没有写入到Redis中;那么,在用户这次请求结束后,数据库中的数据是{name:muke},但是Redis中的数据还是旧的{name:imooc};
(5)于是,当下一次请求过来,想要查询name数据的时候,由于Redis中已经有name这个数据了,所以其会从Redis中拿;然后,其拿的就是旧的{name:imooc}了;这就是数据库、Redis双写数据不一致的情况;
……………………………………………………
(6)针对上面的这种情况,我们是可以解决的;
三:缓存数据双写一致,简介;
(1)双写一致是什么;
什么是双写一致,我们来看如下图:
用户请求到后端,执行数据保存到数据库,没毛病,接下来需要把数据在其他的数据服务中同时进行保存,常见的保存到Redis,es或者mongodb。
那么正由于分布式系统,每个节点相互独立,就有可能造成在数据库写完之后,后续的写操作无法执行,或者发生异常中断,或者网络延时问题,这么一来,读取的数据在数据库里是新的,而redis/es/mongo中是老的或者没有,这样显然数据就造成了不一致。(一般以Redis为例,结合数据库使用的场景是最多的)
(2)DB + Redis 使用场景
一般来说,数据库和redis结合以后的常见使用方式如下:
1.新增数据到数据库,此时redis中还没有数据
2.读取数据,如果缓存没有,则从db中读取,然后同时存入redis,如果redis有数据,直接返回
3.更新数据,先从缓存中删除,随后更新数据库,后续读取重复步骤2
……………………………………………………
那么问题来了,为啥要先从redis中把数据删除,然后再更新数据库,最后再写入redis呢?为何不直接更新redis?我们先来看如下图:
如上如,数据在修改的过程中必定会存在网络延时,因为分布式系统节点相互独立部署,那么在并发读的情况之下,还没来得及修改完,那么对于读操作,读到的数据都是老的数据,如果是某些不严谨的情况,无所谓,如果是极致的严谨,那么就不能这么做了。比如我们的环境监测,大气的一些数据会每隔1-2分钟更新,甚至有的是5分钟更新,所以如果读到的是一些老的数据,是没有关系的,因为最终几秒或者几十秒以后会更新,这些数据的来去不会很大,而且我们能容忍一定的误差,所以也就无所谓了。
(3)双写不一致,是什么;
那么什么时候会有不一致的情况呢?我们会沿用上一小段的步骤3,先看如下图,这是正常的一个读取流程,数据库和redis中都有一个name=itzixi,读取没毛病:
此时有一个更新的请求进来了,先从redis中删除name=itzixi,然后再把新的值更新给数据库,此时数据库中新的name=lee,如下图,是我们的理想状态:
但是,在这个更新的整个过程中,有可能这个name是一个热点key,存在并发的场景,由于并发读,并且数据库还没有来得及把name从itzixi更新为lee,而且redis中也没有这个name的值,此时会发生怎样的事?如下图:
此时,老的数据就会被新的并发读请求到,并写入到redis,然后返回给用户,那么最终,数据库和redis里的数据造成了不一致,所以我们要如何做才能避免这样的情况发生呢?
(4)应该怎么做,才能实现双写一致;
那么如何来解决上述问题,来达到双写一致的目的呢?
第一种方案:读取的时候只提供redis,也就是说,当更新操作一开始从redis中删除数据了,用户去读redis,如果没有是不会从数据库中读的,我只提供redis的读取,写入的时候只在数据新增以及更新后才会放入到redis,那么如此一来,并发读的时候就不会从数据库读取老的数据并且放入Redis中了。没有读到也没关系,做一些空数据的处理,可能会有个几百毫秒或者1-2秒的延迟,但是可以忍受,并且也能避免缓存穿透的问题。(即,这儿为了保证安全性,在这种【发生的次数很少,但要求很高】的情况上,作了妥协)
那么可能还会有同学会问,写入redis的时候,万一写失败呢?redis挂了呢?的确有可能,那么为了降低风险,成本最小的方式就是使用云厂商的云redis产品,风险低,高可用,维护成本也低。(这是我们目前所采用的方式)
……………………………………………………
第二种方案:缓存二次删除,啥意思,如下:
● 先删缓存
● 再更新数据库
● sleep 1秒(视情况,可以延长或缩短)
● 再次删除缓存
● 如此一来,就能保证在sleep以后,新的请求想要获取数据时,会从数据库中获取,然后把新数据缓存到Redis中(在sleep以后的缓存数据在再次被读取时会从db获取),如此一来达到一致性。
(即,这儿为了保证安全,让数据库承担了更多,而这种思路,整体上来看是不太好的)
……………………………………………………
那么上述方案其实可以达到一致的目的,而且比较简单,但是有时候热点数据的并发请求也会很多,这个时候怎么办?对于热点数据来说,宁可有脏数据,也不要硬轻易删除,因为热点数据的并发读是很大的,一旦删除,那么这个时候由于缓存击穿,数据库可能会瞬间被秒了,直接宕机。为了保证一致性那么有如下两种情况:
Condition1:读多写少的场景可以给缓存做更新状态标记,db更新前加标记位,更新后删除标记位,如果中途失败了,用补偿job刷新标记位时间>2分钟的缓存,之前在阿里的时候就用的这种方法,标记位可以做版本控制,数字递增或者更新时间序列都可以,这样就能判断更新先后顺序。或者你也可以在过期时间上做文章。但是一般对于热点数据我们都有双buff机制,就是缓存即将过期的时候去刷新,绝对不能让缓存失效。
Condition2:如果是写多读少的场景,并且必须要用缓存的话,你可以同步数据库binlog建立缓存,这样能保证缓存的有序写入,最终一致性的方案。
总之超高并发场景的一致性,都是最终一致性,所以要考虑每一个环节可能失败的情况,补偿job也是常有的;
(5)小节
相信大家从上面的几种方案都能看的出来,并发读的时候的确会有一些老数据,然后才会数据达到一致性,因为这采用的是弱一致性,这个咱们在CAP中提过了,如果你想要强一致性的话,在这里没办法做,只能不适用redis了。这点需要注意。
(1)双写一致是什么;
什么是双写一致,我们来看如下图:
用户请求到后端,执行数据保存到数据库,没毛病,接下来需要把数据在其他的数据服务中同时进行保存,常见的保存到Redis,es或者mongodb。
那么正由于分布式系统,每个节点相互独立,就有可能造成在数据库写完之后,后续的写操作无法执行,或者发生异常中断,或者网络延时问题,这么一来,读取的数据在数据库里是新的,而redis/es/mongo中是老的或者没有,这样显然数据就造成了不一致。(一般以Redis为例,结合数据库使用的场景是最多的)
(2)DB + Redis 使用场景
一般来说,数据库和redis结合以后的常见使用方式如下:
1.新增数据到数据库,此时redis中还没有数据
2.读取数据,如果缓存没有,则从db中读取,然后同时存入redis,如果redis有数据,直接返回
3.更新数据,先从缓存中删除,随后更新数据库,后续读取重复步骤2
……………………………………………………
那么问题来了,为啥要先从redis中把数据删除,然后再更新数据库,最后再写入redis呢?为何不直接更新redis?我们先来看如下图:
如上如,数据在修改的过程中必定会存在网络延时,因为分布式系统节点相互独立部署,那么在并发读的情况之下,还没来得及修改完,那么对于读操作,读到的数据都是老的数据,如果是某些不严谨的情况,无所谓,如果是极致的严谨,那么就不能这么做了。比如我们的环境监测,大气的一些数据会每隔1-2分钟更新,甚至有的是5分钟更新,所以如果读到的是一些老的数据,是没有关系的,因为最终几秒或者几十秒以后会更新,这些数据的来去不会很大,而且我们能容忍一定的误差,所以也就无所谓了。
(3)双写不一致,是什么;
那么什么时候会有不一致的情况呢?我们会沿用上一小段的步骤3,先看如下图,这是正常的一个读取流程,数据库和redis中都有一个name=itzixi,读取没毛病:
此时有一个更新的请求进来了,先从redis中删除name=itzixi,然后再把新的值更新给数据库,此时数据库中新的name=lee,如下图,是我们的理想状态:
但是,在这个更新的整个过程中,有可能这个name是一个热点key,存在并发的场景,由于并发读,并且数据库还没有来得及把name从itzixi更新为lee,而且redis中也没有这个name的值,此时会发生怎样的事?如下图:
此时,老的数据就会被新的并发读请求到,并写入到redis,然后返回给用户,那么最终,数据库和redis里的数据造成了不一致,所以我们要如何做才能避免这样的情况发生呢?
(4)应该怎么做,才能实现双写一致;
那么如何来解决上述问题,来达到双写一致的目的呢?
第一种方案:读取的时候只提供redis,也就是说,当更新操作一开始从redis中删除数据了,用户去读redis,如果没有是不会从数据库中读的,我只提供redis的读取,写入的时候只在数据新增以及更新后才会放入到redis,那么如此一来,并发读的时候就不会从数据库读取老的数据并且放入Redis中了。没有读到也没关系,做一些空数据的处理,可能会有个几百毫秒或者1-2秒的延迟,但是可以忍受,并且也能避免缓存穿透的问题。(即,这儿为了保证安全性,在这种【发生的次数很少,但要求很高】的情况上,作了妥协)
那么可能还会有同学会问,写入redis的时候,万一写失败呢?redis挂了呢?的确有可能,那么为了降低风险,成本最小的方式就是使用云厂商的云redis产品,风险低,高可用,维护成本也低。(这是我们目前所采用的方式)
……………………………………………………
第二种方案:缓存二次删除,啥意思,如下:
● 先删缓存
● 再更新数据库
● sleep 1秒(视情况,可以延长或缩短)
● 再次删除缓存
● 如此一来,就能保证在sleep以后,新的请求想要获取数据时,会从数据库中获取,然后把新数据缓存到Redis中(在sleep以后的缓存数据在再次被读取时会从db获取),如此一来达到一致性。
(即,这儿为了保证安全,让数据库承担了更多,而这种思路,整体上来看是不太好的)
……………………………………………………
那么上述方案其实可以达到一致的目的,而且比较简单,但是有时候热点数据的并发请求也会很多,这个时候怎么办?对于热点数据来说,宁可有脏数据,也不要硬轻易删除,因为热点数据的并发读是很大的,一旦删除,那么这个时候由于缓存击穿,数据库可能会瞬间被秒了,直接宕机。为了保证一致性那么有如下两种情况:
Condition1:读多写少的场景可以给缓存做更新状态标记,db更新前加标记位,更新后删除标记位,如果中途失败了,用补偿job刷新标记位时间>2分钟的缓存,之前在阿里的时候就用的这种方法,标记位可以做版本控制,数字递增或者更新时间序列都可以,这样就能判断更新先后顺序。或者你也可以在过期时间上做文章。但是一般对于热点数据我们都有双buff机制,就是缓存即将过期的时候去刷新,绝对不能让缓存失效。
Condition2:如果是写多读少的场景,并且必须要用缓存的话,你可以同步数据库binlog建立缓存,这样能保证缓存的有序写入,最终一致性的方案。
总之超高并发场景的一致性,都是最终一致性,所以要考虑每一个环节可能失败的情况,补偿job也是常有的;
(5)小节
相信大家从上面的几种方案都能看的出来,并发读的时候的确会有一些老数据,然后才会数据达到一致性,因为这采用的是弱一致性,这个咱们在CAP中提过了,如果你想要强一致性的话,在这里没办法做,只能不适用redis了。这点需要注意。
四:实现双写一致的第二种方案:缓存双删;
1.第一删;
(1)这是初始的情况,Redis中有旧的数据{name:imooc};(自然此时,数据库中的也是旧数据{name:imooc})(后面用户有个请求,是想把数据更新为{name:muke})
(2)用户更新请求过来的时候,先不要让用户把数据塞进去,而是先把Redis中的旧数据{name:imooc}给删除;
(3)然后,才会把新数据{name:muke}更新到数据库中;
(4)此时,如果有用户过来查询,而此时Redis中并没有数据;所以,会去数据库中查,而此时数据库中的数据是新的,所以后续会把新的数据缓存到Redis中;
……………………………………………………
修改UserServiceImpl的updateUserInfo()方法的逻辑如下;
但是,上面的过程依旧是不太OK的;
2.第二删;
(1)在前面,在更新前先删除Redis中的旧数据,然后把新数据写到数据库中;然后,后续用户再来请求的时候,因为Redis中并没有数据;所以,会去数据库中查,而此时数据库中的数据是新的,所以后续会把新的数据缓存到Redis中;
(2)但是,有可能在【我们把Redis中旧数据删除,正要把新数据更新到数据库中,但是还没真正更新时候】,用户请求过来了;于是,这个请求就会从数据库中获取旧数据,然后把旧数据缓存到Redis中;
(3)即,此时的情况就是,虽然数据库中是新数据,但是Redis中还是旧数据;于是,当用户请求过来的时候,其就会从Redis中获取旧数据;
(4)于是,为了应对这种情况,可以这样处理:在把数据更新到数据库中,可以在当前线程中休眠半秒或者一秒,然后再次删除Redis中的数据(实际中,这个删的数据可能是旧数据也可能是新数据);
(5)这样一来,后续再有请求过来的时候,因为Redis中并没有数据;所以,会去数据库中查,而此时数据库中的数据是新的,所以后续会把新的数据缓存到Redis中;这就是缓存双删除过后,后续就能够保证数据库和Redis中的数据是一致的;
……………………………………………………
修改UserServiceImpl的updateUserInfo()方法的逻辑如下;
3.分析;
(1)在某些特定情况下,我们不能为了【业务100%的正确性,一点都不能有脏数据,一点都不能出错的】而牺牲掉【系统能够处理并发请求】的;换句话说,我们有时需要【保证系统能够处理高并发的请求;即要保证系统的可用性】,为此【偶尔出现一点脏数据,也是可以忍受的;即牺牲了一致性】;
(2)绝大部分情况下,并发都是在首页和详情页,这都是跟着业务走的;用户的查询肯定会有很多的,如果出现【1-2秒的时间可能出现脏数据】的情况,也是可以容忍的;即,虽然有时可能获取的是老的脏数据,但是我们却保证了可用性;
(插)因为,C(Consistency,一致性)、 A(Availability,可用性)、P(Partition tolerance,分区容错性)中我们一定要保证P;如果我们需要保证的C的话,就需要牺牲掉A;如果要保证A的话,就要牺牲掉C;
(3)对于互联网的现状来说,系统的可用性是尽量要保证的;
● 其实,在我们的网站中,用户的注意力其实是在新闻、文章上的,而不是如新闻的发布者这些;所以,我们有几秒钟的数据不一致是无所谓的;因为热点数据的并发读是非常大的,一旦我们把这个数据在Redis中给删除后,大量的请求涌向数据库,就会造成缓存击穿;
● 即,还是那句话,互联网项目中,在C(Consistency,一致性)、 A(Availability,可用性)之间,大多数会选择A(Availability,可用性),以系统的可用性为优先考虑,哪怕无法保证一致性而有了脏数据也是没有关系的;
● 只是,对个别情况(比如银行系统),我们就需要保证C,而牺牲掉A了;
4.疑问与解答;
(1)疑问1;
Q:
A:避免设置之前有高并发来读取获得脏数据;;;即基本思路是,能提升一点是一点;
(2)疑问2;
Q:请问老师,在双删操作之间有请求访问数据是不是还是会出现读到旧数据的情况?那么双删就是为了保证在双删操作之外的时间数据的一致性?
A:用阿里中间件canal监听数据库来更新最好;
(3)疑问3;
Q:为什么要线程休眠,直接删除缓存不好么,或者说线程休眠的好处是什么?
A:
● 为了等待一下。这么做并不是最好方式。有兴趣可以了解一下canal中间件。可以同步数据库到redis。是最好的解决方案哈
● 不加的话,可能新设置的数据会被脏数据覆盖的噢;给与时间做缓冲,在极致的并发下,是海量请求过来的,也就是在新set一个新值的时候,还有海量的请求获得了数据库的老数据,那么他们是一起set的,所以这个时候新值会被老值覆盖。
(4)疑问4:自己提的一个问题;
Q:
A:
1.第一删;
(1)这是初始的情况,Redis中有旧的数据{name:imooc};(自然此时,数据库中的也是旧数据{name:imooc})(后面用户有个请求,是想把数据更新为{name:muke})
(2)用户更新请求过来的时候,先不要让用户把数据塞进去,而是先把Redis中的旧数据{name:imooc}给删除;
(3)然后,才会把新数据{name:muke}更新到数据库中;
(4)此时,如果有用户过来查询,而此时Redis中并没有数据;所以,会去数据库中查,而此时数据库中的数据是新的,所以后续会把新的数据缓存到Redis中;
……………………………………………………
修改UserServiceImpl的updateUserInfo()方法的逻辑如下;
但是,上面的过程依旧是不太OK的;
2.第二删;
(1)在前面,在更新前先删除Redis中的旧数据,然后把新数据写到数据库中;然后,后续用户再来请求的时候,因为Redis中并没有数据;所以,会去数据库中查,而此时数据库中的数据是新的,所以后续会把新的数据缓存到Redis中;
(2)但是,有可能在【我们把Redis中旧数据删除,正要把新数据更新到数据库中,但是还没真正更新时候】,用户请求过来了;于是,这个请求就会从数据库中获取旧数据,然后把旧数据缓存到Redis中;
(3)即,此时的情况就是,虽然数据库中是新数据,但是Redis中还是旧数据;于是,当用户请求过来的时候,其就会从Redis中获取旧数据;
(4)于是,为了应对这种情况,可以这样处理:在把数据更新到数据库中,可以在当前线程中休眠半秒或者一秒,然后再次删除Redis中的数据(实际中,这个删的数据可能是旧数据也可能是新数据);
(5)这样一来,后续再有请求过来的时候,因为Redis中并没有数据;所以,会去数据库中查,而此时数据库中的数据是新的,所以后续会把新的数据缓存到Redis中;这就是缓存双删除过后,后续就能够保证数据库和Redis中的数据是一致的;
……………………………………………………
修改UserServiceImpl的updateUserInfo()方法的逻辑如下;
3.分析;
(1)在某些特定情况下,我们不能为了【业务100%的正确性,一点都不能有脏数据,一点都不能出错的】而牺牲掉【系统能够处理并发请求】的;换句话说,我们有时需要【保证系统能够处理高并发的请求;即要保证系统的可用性】,为此【偶尔出现一点脏数据,也是可以忍受的;即牺牲了一致性】;
(2)绝大部分情况下,并发都是在首页和详情页,这都是跟着业务走的;用户的查询肯定会有很多的,如果出现【1-2秒的时间可能出现脏数据】的情况,也是可以容忍的;即,虽然有时可能获取的是老的脏数据,但是我们却保证了可用性;
(插)因为,C(Consistency,一致性)、 A(Availability,可用性)、P(Partition tolerance,分区容错性)中我们一定要保证P;如果我们需要保证的C的话,就需要牺牲掉A;如果要保证A的话,就要牺牲掉C;
(3)对于互联网的现状来说,系统的可用性是尽量要保证的;
● 其实,在我们的网站中,用户的注意力其实是在新闻、文章上的,而不是如新闻的发布者这些;所以,我们有几秒钟的数据不一致是无所谓的;因为热点数据的并发读是非常大的,一旦我们把这个数据在Redis中给删除后,大量的请求涌向数据库,就会造成缓存击穿;
● 即,还是那句话,互联网项目中,在C(Consistency,一致性)、 A(Availability,可用性)之间,大多数会选择A(Availability,可用性),以系统的可用性为优先考虑,哪怕无法保证一致性而有了脏数据也是没有关系的;
● 只是,对个别情况(比如银行系统),我们就需要保证C,而牺牲掉A了;
4.疑问与解答;
(1)疑问1;
Q:
A:避免设置之前有高并发来读取获得脏数据;;;即基本思路是,能提升一点是一点;
(2)疑问2;
Q:请问老师,在双删操作之间有请求访问数据是不是还是会出现读到旧数据的情况?那么双删就是为了保证在双删操作之外的时间数据的一致性?
A:用阿里中间件canal监听数据库来更新最好;
(3)疑问3;
Q:为什么要线程休眠,直接删除缓存不好么,或者说线程休眠的好处是什么?
A:
● 为了等待一下。这么做并不是最好方式。有兴趣可以了解一下canal中间件。可以同步数据库到redis。是最好的解决方案哈
● 不加的话,可能新设置的数据会被脏数据覆盖的噢;给与时间做缓冲,在极致的并发下,是海量请求过来的,也就是在新set一个新值的时候,还有海量的请求获得了数据库的老数据,那么他们是一起set的,所以这个时候新值会被老值覆盖。
(4)疑问4:自己提的一个问题;
Q:
A:
五:整体逻辑分析;
即,我们这儿的逻辑是这样的:
(1)数据库是基于硬盘的,访问速度慢,数据库处理并发能力很低;Redis是基于内存的,处理速度快,可以很好的应对并发;
(2)CAP理论中,C(Consistency,一致性)、 A(Availability,可用性)、P(Partition tolerance,分区容错性)中我们一定要保证P;如果我们需要保证的C的话,就需要牺牲掉A;如果要保证A的话,就要牺牲掉C;
(3)对于我们这儿的业务来说,鉴于业务的具体情况,我们要保证的是A(Availability,可用性),即要保证我们开发的系统能够应对高并发请求,系统别垮掉;
(4)所以,我们在系统中引入了Redis,让Redis缓存来帮助满足高并发的需求;
(5)但是,虽然我们无法保证C(Consistency,一致性),但也没有彻底摆烂;
(6)因为使用了Redis,所以就存在MySQL和Redis中数据不一致的情况;为此,我们要尽量保证MySQL和Redis中数据的一致性,尽量缩小二者数据不一致的时间,以尽量减少数据不一致带来的问题;
(7)所以,我就使用了双删策略中的第一删;
(8)但是,在【把旧数据从MySQL删除】到【把新数据彻底更新到MySQL】这个时间段内,还可能有新的请求过来查询数据,以把旧数据弄到Redis中;;;所以,就有了双删策略中的第二删;;;;同时,第二删的时候,我们延迟了一下,以让所有以前还在使用旧数据的线程跑完,事务走完,以确保彻彻底底把就数据给删完了,以确保,下次在往Redis中弄的数据是新数据;
(9)即,我们这儿虽然为了可用性,牺牲了一致性(即,虽然一致性可以达到99.99%,但是无法在一致性上保证100%),但是我们这儿的双删策略却保证了最终一致性,也就是本篇博客提到的双写数据一致性;
即,我们这儿的逻辑是这样的:
(1)数据库是基于硬盘的,访问速度慢,数据库处理并发能力很低;Redis是基于内存的,处理速度快,可以很好的应对并发;
(2)CAP理论中,C(Consistency,一致性)、 A(Availability,可用性)、P(Partition tolerance,分区容错性)中我们一定要保证P;如果我们需要保证的C的话,就需要牺牲掉A;如果要保证A的话,就要牺牲掉C;
(3)对于我们这儿的业务来说,鉴于业务的具体情况,我们要保证的是A(Availability,可用性),即要保证我们开发的系统能够应对高并发请求,系统别垮掉;
(4)所以,我们在系统中引入了Redis,让Redis缓存来帮助满足高并发的需求;
(5)但是,虽然我们无法保证C(Consistency,一致性),但也没有彻底摆烂;
(6)因为使用了Redis,所以就存在MySQL和Redis中数据不一致的情况;为此,我们要尽量保证MySQL和Redis中数据的一致性,尽量缩小二者数据不一致的时间,以尽量减少数据不一致带来的问题;
(7)所以,我就使用了双删策略中的第一删;
(8)但是,在【把旧数据从MySQL删除】到【把新数据彻底更新到MySQL】这个时间段内,还可能有新的请求过来查询数据,以把旧数据弄到Redis中;;;所以,就有了双删策略中的第二删;;;;同时,第二删的时候,我们延迟了一下,以让所有以前还在使用旧数据的线程跑完,事务走完,以确保彻彻底底把就数据给删完了,以确保,下次在往Redis中弄的数据是新数据;
(9)即,我们这儿虽然为了可用性,牺牲了一致性(即,虽然一致性可以达到99.99%,但是无法在一致性上保证100%),但是我们这儿的双删策略却保证了最终一致性,也就是本篇博客提到的双写数据一致性;



