<一> 缓存一致性
如下的a,b表示两个线程,其中a:写线程,b:读线程,↑表示线程切换
**并发环境下:
缓存失效跟不失效两种情景导致数据不一致问题:
1.1<缓存数据失效过期>
a线程更新数据库数据,并且之后将数据从缓存中删除,(修改数据库之前,b线程从数据库中读取到了旧的数据,此时a线程更新数据库之后,会删除缓存[此时缓存已经因过期没数据],之后b线程因从数据库读到了旧数据,会将数据重新写入到缓存中(此时写入的是旧数据,故而导致数据不一致)[该情况一般只会发生在读操时间 大于写时间,发生几率很小],另外或者说使用的数据库为主从分离的情况下,当主数据库还未同步到从数据库时,读线程进行了读取旧数据。
1.1.1<模拟线程a,b执行流程:>
a-----------------更新数据-------删除缓存------------------->
↑ ↓
b--数据库读数据------------------------------更新写缓存----->
b先读取了数据库, a再更新数据库,a再删除缓存,b在更新数据到缓存。
1.2<缓存数据未过期>
读线程在写线程更新完数据且删除缓存之间那段时间读取的数据都是旧数据
1.2.1<模拟线程a,b执行流程:>
a-------更新数据----------------------------删除缓存------->
↓ ↑
b-------------------b线程读取的都是旧数据------------------->
此时是从缓存中读取的数据都是旧的,(上图为先更新数据库,再删除缓存情况)
1.3<如下两种先后顺序操作导致数据不一致问题>
A.删除缓存,先更新数据库,导致数据不一致问题
a--删除缓存----------------------------------更新数据库------------------------>
↓ ↑ ↓
b------------b线程发现缓存没有,从数据库读--------------------数据回写缓存-------->
如上会导致数据不一致。
解决方法:
<1>.延迟双删
延迟:
就是在更新数据库之后,人为的延迟删除缓存的时间,即便在上1.1.1种b写入了旧数据,之后延迟删除也会将缓存的旧数据删除掉,解决了写入旧数据问题,如下图:
<模拟执行流程:>
a---删除缓存----------------------更新数据----------------------sleep---延迟删除缓存---->
↓ ↑ ↓ ↑
b-------------读数据库数据-------------------------更新写缓存----------------------------->
双删:
删除两次,再更新数据库之前先进行一次删除缓存,删除之前可能存在的旧数据。避免了修改数据库到第二次读取缓存中间这段时间可能读取存在的旧数据。解决了如上1.2.1所遇到的问题。
<模拟执行流程:>
a----删除缓存-----更新数据库-------------------------------------sleep--延迟删除缓存------->
↑ ↓ ↑
b--------------------------b因缓存无数据,读数据库----更新缓存---------------------->
Sleep,由于 Sleep 的时间大于线程b读数据+写缓存的时间,所以缓存被再次删除。
注:目的是保证数据最设置缓存过期时间:终一致性,所有的操作以数据库为准,只要缓存达到了过期时间,后面数据自动回通过数据库查询最终回滚到缓存.
另外:如果数据库是主从分布,,可以让线程b,如果允许条件直接从主数据库中读取数据,再更新缓存,所得的数据就是一致的。
B.先更新数据库,再删除缓存,导致数据不一致
// X表示缓存删除不成功
a--更新数据库----删除缓存---X---------------------------------->
↓
b-------------------------------b线程从数据库读取旧数据----->
解决方法:
增加一个消息队列,在延迟删除的时候,将需要需要删除的key扔到队列中,通过队列消费来进行缓存的删除,另外需要设置重试机制,确保数据最终删除。该方法又个缺点,那就是业务层代码,会增加对应的队列相关的代码.这样低业务代码入侵比较大。可以通过订阅程序订阅数据库写入操作是写的binlog,一旦有数据写入成功,订阅程序感知到数据的变化,主动通过binlog的操作,来处理删除缓存的处理。
// X表示缓存删除不成功
a--更新数据库----删除缓存---将删除的key存储至队列中---X------------------------队列消费删删除缓存----->
↓ ↑
b-----------------------------------------------------b线程数据库读旧数据----->
上述队列可以持续消费来删除,组中确保数据一致性。
如图:
改图借鉴与网上搜索的一个图展示,配合上述线程切换&文字配合理解。
<总结>
为什么删除,而不是更新操作?
举例说明: 更新数据库n次,同样缓存也需要更新n次,但是再次过程中,数据读取只读了一次缓存(那么,之前的n次更新缓存都是无用功),反之,直接删除掉,当真正读取数据的时候,会从数据库读取,再次写入缓存即可。
删除缓存两种方式:
先删除缓存,再更新数据库,解决方法使用延迟双删策略。
先更新数据,再删除缓存,通过队列orbinlog同步,推荐使用binlog操作,针对要求不是很高的场景,只设置超时时间就可以了。