Redis核心技术与实战-学习笔记(二十五):缓存一致性

网友投稿 781 2022-09-16

Redis核心技术与实战-学习笔记(二十五):缓存一致性

Redis核心技术与实战-学习笔记(二十五):缓存一致性

一.缓存和数据库数据不一致

缓存中有数据:缓存的数据值需要和数据库中的值相同。缓存中无数据:数据库中的值是最新值。

二.缓存分为读写缓存和只读缓存

对于读写缓存来说,如果要对数据进行增删改,就需要在缓存中进行,同时还要根据采用的写回策略,决定是否同步写回到数据库中。

同步直写策略:写缓存时,也同步写数据库,缓存和数据库中的数据一致。在业务应用中使用事务机制,来保证缓存和数据库的更新具有原子性:两者要么一起更新,要么都不更新,返回错误信息,进行重试。异步写回策略:写缓存时候不同步写数据库,等到数据从缓存中淘汰时,再写回数据库。使用 异步写回策略时,如果数据还没写回数据库,缓存就发生故障,那么此时数据库就没有最新的数据了。

三.只读缓存

数据新增,直接写入数据库;数据删改,把只读缓存中的数据标记为无效。应用后续访问增删这些数据时候,因为缓存没有相应的数据会发生缓存缺失。此时,应用再从数据库中将数据读入缓存后续直接从缓存中读取。

1.新增数据

如果是新增数据,数据会直接写到数据库中,不用对缓存做任何操作。此时,缓存中本身没有新增数据,数据库中是最新值,此时缓存和数据库中的数据是一致的。

2.删改数据

如果发生删改操作,应用既要更新数据库,也要在缓存中删除数据。这两个操作如果无法保证原子性,也就是说,要不都完成,要不都没完成,此时,就会出现数据不一致问题了。这个问题比较复杂,我们来分析一下。

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

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

不同情况

潜在问题

先删除缓存值,后更新数据库值

数据库更新失败,导致请求再次访问缓存时,发现缓存缺失,再读数据库时,从数据库中读到旧值

先更新数据库值,后删除缓存值

缓存删除失败,导致请求再次访问缓存时,缓存命中,但是读到旧值

四.如何解决数据不一致问题?

重试机制

具体来说:可以把要删除的缓存值或者要更新到数据库的值暂存在消息队列中可以从消息队列中重新读取这些值当应用没有能够成功的删除缓存值或者是更新数据库值时再次删除或者更新。当成功的删除或者更新的时候,我们要把这些值从消息队列中去除,以免重复操作此时,我们可以保证数据库和缓存的数据一致。当依然更新或者删除失败的时候,我们会继续重试,重试超过一定次数,还是没有成功,我们需要向业务层发送报错信息。

高并发情况下:

在更新数据库或者删除缓存时候其中有一个操作失败的情况实际上即使这两个操作第一次执行时候都没有失败。当有大量并发请求的时候,应用还是有可能读到不一致的数据。

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

假设线程A删除缓存值后,还没来得及更新数据库(比如说网络延迟),线程B就开始读取数据了,这时候B会发现缓存缺失,只会去数据库读取这就会带来两个问题:

线程B读取到旧值;线程B在缓存缺失的情况下读取数据库并且把旧值写入缓存,导致其他线程从缓存中读到旧值。等到线程B从数据库读取完数据,更新了缓存后,线程A才开始更新数据库,此时,缓存中的数据是旧值,而数据库中的就是最新值,两者就不一致了。

时间

线程A

线程B

问题

t1

删除数据X的缓存值

t2

1.读取数据X,缓存缺失

从数据库读取X,读到旧值

2.把数据X写入缓存

1.线程

a尚未更新数据库的值

导致线程B读到旧值.

2.线程B把旧值写入缓存,导致其他线程读到旧值

t3

更新数据库中的

x

缓存和数据库不一致

在线程A更新完数据库值后,我们先让它sleep,再进行缓存删除操作。

原因

sleep的这段时间,就是为了让线程B能够先从数据库读取数据,再把缺失的数据写入缓存,线程A再进行删除,所以,线程Asleep的时间大于线程B读取数据写入缓存的时间。其他线程读取数据后,会发现缓存缺失,所以会从数据库中读取最新值。

延迟双删除:

在第一次删除缓存值后,延迟一段时间再次进行删除;

延迟双删除方案的示例:

redis.delKey(X)db.update(X)Thread.sleep(N)redis.delKey(X)

六:先更新数据库值,再删除缓存值

如果线程A删除了数据库中的值,但是还没来得及删除缓存值,线程B就开始读取数据了线程B查询了缓存时,发现缓存命中就会直接从缓存中读取旧值。不过,这种情况,如果其他线程并发读缓存的请求不多,那么,就不会有很多读取到旧值。线程A一般也会很快删除缓存值,这样一来,其他线程再次读取时,就会发生缓存缺失进而从数据库中读取最新值,这样对业务的影响较小。

时间

线程A

线程B

问题

t1

删除数据库中的数据X

t2

读取数据X,缓存命中

从缓存读取X,读到旧值

线程A尚未删除,导致线程B读到缓存旧值

t3

删除缓存的数据X

删除缓存值或者更新数据库失败而导致数据不一致,使用重试机制确保删除或更新成功。在删除缓存值、更新数据库的这两步操作中,有其他线程的并发读操作,导致其他线程读取到旧值,应对方案是延迟双删。

优先使用先更新数据库再删除缓存的方法:

先删除缓存值再更新数据库,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力;如果业务应用中读取数据库和写缓存的时间不好估算,那么,延迟双删中的等待时间就不好设置。

如果业务层要求必须读取一致的数据:

先在 Redis 缓存客户端暂存并发读请求等数据库更新完、缓存值删除后再读取数据,从而保证数据一致性

七.数据在删改操作时,如果不是删除缓存值,而是直接更新缓存的值,你觉得和删除缓存值相比,有什么好处和不足?

先更新数据库,再更新缓存:如果更新数据库成功,但缓存更新失败,此时数据库中是最新值,但缓存中是旧值,后续的读请求会直接命中缓存,得到的是旧值。先更新缓存,再更新数据库:如果更新缓存成功,但数据库更新失败,此时缓存中是最新值,数据库中是旧值,后续读请求会直接命中缓存,但得到的是最新值,短期对业务影响不大。但是,一旦缓存过期或者满容后被淘汰,读请求就会从数据库中重新加载旧值到缓存中,之后的读请求会从缓存中得到旧值,对业务产生影响。这种其中一个操作可能失败的情况,也可以使用重试机制解决,把第二步操作放入到消息队列中,消费者从消息队列取出消息,再更新缓存或数据库,成功后把消息从消息队列删除,否则进行重试,以此达到数据库和缓存的最终一致。

并发请求

先更新数据库,再更新缓存,写+读并发:线程A先更新数据库,之后线程B读取数据,此时线程B会命中缓存,读取到旧值,之后线程A更新缓存成功,后续的读请求会命中缓存得到最新值。这种场景下,线程A未更新完缓存之前,在这期间的读请求会短暂读到旧值,对业务短暂影响。先更新缓存,再更新数据库,写+读并发:线程A先更新缓存成功,之后线程B读取数据,此时线程B命中缓存,读取到最新值后返回,之后线程A更新数据库成功。这种场景下,虽然线程A还未更新完数据库,数据库会与缓存存在短暂不一致,但在这之前进来的读请求都能直接命中缓存,获取到最新值,所以对业务没影响。先更新数据库,再更新缓存,写+写并发:线程A和线程B同时更新同一条数据,更新数据库的顺序是先A后B,但更新缓存时顺序是先B后A,这会导致数据库和缓存的不一致。先更新缓存,再更新数据库,写+写并发:与场景3类似,线程A和线程B同时更新同一条数据,更新缓存的顺序是先A后B,但是更新数据库的顺序是先B后A,这也会导致数据库和缓存的不一致。场景1和2对业务影响较小,场景3和4会造成数据库和缓存不一致,影响较大。也就是说,在读写缓存模式下,写+读并发对业务的影响较小,而写+写并发时,会造成数据库和缓存的不一致。针对场景3和4的解决方案是,对于写请求,需要配合分布式锁使用。写请求进来时,针对同一个资源的修改操作,先加分布式锁,这样同一时间只允许一个线程去更新数据库和缓存,没有拿到锁的线程把操作放入到队列中,延时处理。用这种方式保证多个线程操作同一资源的顺序性,以此保证一致性。使用读写缓存同时操作数据库和缓存时,因为其中一个操作失败导致不一致的问题,同样可以通过消息队列重试来解决。而在并发的场景下,读+写并发对业务没有影响或者影响较小,而写+写并发时需要配合分布式锁的使用,才能保证缓存和数据库的一致性。读写缓存模式由于会同时更新数据库和缓存,优点是,缓存中一直会有数据,如果更新操作后会立即再次访问,可以直接命中缓存,能够降低读请求对于数据库的压力(没有了只读缓存的删除缓存导致缓存缺失和再加载的过程)。缺点是,如果更新后的数据,之后很少再被访问到,会导致缓存中保留的不是最热的数据,缓存利用率不高(只读缓存中保留的都是热数据),所以读写缓存比较适合用于读写相当的业务场景。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Powershell连接MsSQL提示 “provider: SQL Network Interfaces, error: 25 - 连接字符串无效”
下一篇:C# 基础知识系列- 1 数据类型(c罗)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~