许多面试过开发岗位的小伙伴都知道,“缓存和数据库的双写一致性”是面试的重灾区,我们经常会面对面试官的redis连环提问。本期小树懒来给大家总结一下缓存和数据库一致性相关的面试题,让大家在面试时不再“无话可说”。
存储速度不同。缓存是将低速存储的结果暂时存储在高速存储中的技术。
如图所示,金字塔上的存储可以作为下面存储的缓存。
在高并发性的业务场景中,数据库大多是用户并发访问的最弱环节。因此,有必要使用redis进行缓冲操作,以便首先访问redis,而不是直接访问MySQL等数据库。
该商业场景,主要是解决从Redis缓存中读取数据的问题,一般是按照下图的流程进行商业操作。
由于其高并发性和高性能的特点,缓存在项目中得到了广泛的应用。在使用缓存时,通常会面临更新问题。当数据源发生变化时,如何更新到数据库和缓存中,并尽可能保证安全性和性能。
CacheAsidePattern是最经典的缓存+数据库读写模式。
标准Pattern,facebook就是这样使用的,具体流程图如下:
故障:应用程序首先从cache获取数据,如果没有获取,则从数据库中获取数据,成功后,放入缓存。
命中:应用从cache中获取数据,获取后返回。
更新:先将数据存储在数据库中,成功后缓存无效。
阅读部分大家都很熟悉,先阅读cache,如果cache没有命中,去阅读存储介质,如底层数据库,返回数据,并设置缓存。
下面我们讨论三种更新策略:
先更新缓存,再写数据库。
首先更新数据库,然后更新缓存。
首先删除缓存,然后更新数据库。
(1) 先更新缓存,再写数据库。
同时,如果要求A和要求B更新,就会出现。
线程A更新了数据库。
线程B更新了数据库。
线程B更新缓存。
线程A更新缓存。
因此,请求A更新缓存应该比请求B更新缓存更早,但由于网络等原因,B更新缓存比A更早。这样会导致脏数据,所以没有考虑。
(2)在缓存之前更新数据库。
这个方案,大家都很反对。为什么呢?原因如下。
有两点:
如果你写数据库的场景较多,而读数据场景较少的业务需求,采用这个方案就会导致,数据压根还没有读到,缓存就会频繁更新,浪费性能。
写入数据库的值不是直接写入缓存,而是通过一系列复杂的计算写入缓存。那么,每次写入数据库,再次计算写入缓存的值无疑是浪费性能的。显然,删除缓存更合适。
(3)在更新数据库之前删除缓存。
这个计划会导致不一致的原因是。与此同时,一个要求A进行更新操作,另一个要求B进行查询操作。然后会出现以下情况:
这种情况会导致不一致。此外,如果不使用为缓存设置过期时间策略,数据将永远是脏数据。
那怎样解决呢?采用延迟双删策略。
伪代码如下
public void write(String key,Object data){ redis.delKey(key); db.updateData(data); Thread.sleep(1000); redis.delKey(key); }}
ReadThrough是在查询操作中更新缓存,也就是说,当缓存失效时(过期或更换LRU),CacheAside由调用者负责将数据加载到缓存中,而ReadThrough则由缓存服务自己加载,从而使应用者更加透明。WriteThrough是双写的。
WriteBehind又称WriteBack,可能是先更新cache,然后批量更新数据库。