db与缓存一致性

背景:

我们项目里的代码大量使用了redis缓存,数据更新时,都是先操作db再淘汰缓存,好像也没有遇到什么问题,但心中还是不解,先操作db再淘汰缓存,不是会有数据不一致的情况吗? db是新的了,再淘汰缓存之前,缓存还是旧的。于是网上查阅记下笔记

正确的姿势

先要讨论几个问题?

1.是淘汰缓存还是更新缓存

1.淘汰缓存,会增加cache miss。理论上我们应该倾向于更新缓存。

2.但是更新缓存如果遇到业务复杂的场景,先要查询几个表才能得到新的值,这个时候更新缓存的代价很大。

总结:从各种场景来看,淘汰缓存反而是最简单的,并且带来的副作用只是增加了一次cache miss,建议作为通用的处理方式。

这我们确定了是淘汰缓存,那么我们还有个新问题解决

2.是先操作db还是先操作缓存

当写操作发生时,假设淘汰缓存作为对缓存的通用处理方式,又面临两种抉择。

1.先写db,再淘汰缓存

2.先淘汰缓存,再写db

究竟该选哪种,我们下面挨个分析。

1.先写db,再淘汰缓存会带来什么问题?

很显然,就向上面所说很容易出现数据不一致的情况。假设先写db,再淘汰缓存:第一步写db成功,第二部淘汰缓存失败,则会出现db中是新数据,cache中是旧数据,数据不一致。

2.先淘汰缓存,再写db

第一步淘汰缓存成功,第二步写数据库失败,则只会引发一次cache miss

但是这样也会存在问题,后面再说

结论:数据和缓存的操作时序,结论是清楚的:先淘汰缓存,再写数据库。

3.我们决定的操作时序,先淘汰缓存,再写数据库会有什么问题?

也会有数据不一致的情况出现,有以下几种情况

1.单库情况下,并发读写,缓存与db的操作交叉执行

举例:

1).线程a淘汰了缓存,再写db之前被卡住了,因为复杂的逻辑

2),线程b读数据,发现cache miss,然后去db读,再设置缓存,返回给前端

3).线程a此时写db

数据不一致的情况就这样出现了。虽然很少见,但是还是存在的

2.主从同步下,读写分离的情况下,读从库读到旧数据

1).线程a发起了一个写操作,第一步淘汰了cache,先后成功写入db

2).线程b发起了一个读操作,cache miss, 因为读写分离,从从库读取

3).因为主从延迟的原因,b读取到的还是旧数据,并设置了cache

这从根本上说,也不是缓存的原因,主从延迟同步是主要原因。

4.如何优化

出现不一致的根本原因:

1).单库情况下,写被业务逻辑卡住,可能读到旧数据

2).主从情况下,主从同步延迟,可能读到旧数据

既然旧数据就是在那短时间的间隙中入缓存的,是不是可以在写请求完成后,再休眠一会,比如1s,再次淘汰缓存,就能将这写入的脏数据再次淘汰掉呢?补充:这里得假设缓存设置的时间远大于这个短时间的间隙(1s),否则没有什么意义。因为我先写db,

再淘汰缓存能达到一样的效果。

答案是可以的。

写请求的步骤由2步升级为3步

1)。先淘汰缓存

2)在写数据库

3)休眠1s,再淘汰缓存

所有的写请求都阻塞了1s,大大降低了写请求的吞吐量,增长了处理时间,业务上是接受不了的

再次分析,其实第二次淘汰缓存是为了保证缓存一致而做的操作,而不是业务要求,所以其实无需等待,用一个异步的timer,或者利用消息总线异步的来做这个事情即可。

1),先淘汰缓存

2)。再写数据库

2.5)。不再休眠1s,而是往消息总线esb发送一个消息,发送完成之后马上就能返回。这样的话,写请求的处理时间几乎没有增加,这个方法淘汰了缓存两次,因此被称为缓存双淘汰法。这方法付出的代价是,缓存会增加1次cache miss(代价几乎可以忽略)

而在下游,有一个异步淘汰缓存的消费者,再接收到消息之后,asy-expire在1s之后淘汰缓存。这样,即使1s内有脏数据入缓存,也有机会再次被淘汰掉。

上述方案有一个缺点,需要业务线的写操作增加一个步骤,有没有方案对业务线的代码没有任何入侵呢,是有的,通过分析线下的binlog来异步淘汰缓存。

业务线的代码就不需要动了,新增一个线下的读binlog的异步淘汰模块,读取到binlog中的数据,异步的淘汰缓存。

总结

在异常时序或者读从库导致脏数据入缓存时,可以用二次异步淘汰的缓存双淘汰法来解决缓存与数据库中数据不一致的问题,具体实施至少有三种方案:

(1)timer异步淘汰(本质就是起个线程专门异步二次淘汰缓存)

(2)总线异步淘汰

(3)读binlog异步淘汰

发布了47 篇原创文章 · 获赞 39 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_28119741/article/details/104063744