如何保证缓存与数据库的双写一致性?

缓存由于其高并发和高性能的特性,已经在项目中被广泛使用,在缓存的使用中,通常会面临一个更新的问题,当数据源产生变化,如何去更新到数据库与缓存之中,并且尽量保证安全与性能。

更新缓存的的Design Pattern有四种:Cache aside, Read through, Write through, Write behind caching,我们下面一一来看一下这四种Pattern。


一:Cache Aside Pattern

标准的Pattern,facebook就是使用这种方式,具体流程图如下:

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

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

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

如何保证缓存与数据库的双写一致性?_第1张图片
Cache Aside


读的部分大家都很熟悉,先读cache,如果cache中没有命中,去读底层数据库等存储介质,返回数据,并且设置缓存。

写的部分有一些争议,网上流传很多种做法,简单分析几种:

1.先更新缓存,再写数据库

同时有请求A和请求B进行更新操作,那么会出现

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

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

(3)线程B更新了缓存

(4)线程A更新了缓存

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

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

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

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

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

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

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

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

这种线程安全问题需要通过延时双删等方案解决

大概的策略是:

(1)先淘汰缓存

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

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

也不考虑

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

即Cache Aside

那么,是不是Cache Aside这个就不会有并发问题了?不是的,比如,一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。

这个case理论上会出现,不过,实际上出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。


二:Read/Write Through Pattern

文档地址:

https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177

Read Through

Read Through 就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的。

Write Through

Write Through 就是双写


如何保证缓存与数据库的双写一致性?_第2张图片
Write Through

三:Write Behind Caching Pattern

Write Behind 又叫 Write Back,大概就是先更新cache,背后批量去更新数据库等。

最终一致性

Write back


总结:

分布式系统里要么通过2PC或是Paxos协议保证一致性,要么就是拼命的降低并发时脏数据的概率

缓存系统适用的场景就是非强一致性的场景,所以它属于CAP中的AP,BASE理论。

异构数据库本来就没办法强一致,我们只是减少时间窗口,达到最终一致性。

还有别忘了设置过期时间,这是个兜底方案

你可能感兴趣的:(如何保证缓存与数据库的双写一致性?)