Skip to the content.

缓存更新 Cache Aside Pattern

read 两种情况

第一种 cache hit 缓存命中

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

第二种 cache miss 缓存失效

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

更新update

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

问题

问题1 为什么是 先更新数据库再删缓存?

假设有两个并发操作,一个是更新update操作,一个读read操作。

此时 缓存和数据库中的数据均为num=1,更新操作将设置num=2

下面具体看并发操作顺序

  1. update 先删除了缓存。 此时情况为:缓存 empty ; 数据库 num=1
  2. read 读进来,缓存未命中。 此时情况为:缓存 empty ; 数据库 num=1
  3. read 还是这个读操作,读到了数据库 num=1。此时情况为:缓存 empty ; 数据库 num=1
  4. update 更新数据库,操作结束。 此时情况为:缓存 empty ; 数据库 num=2
  5. read 还是读操作,由于刚才读到的数据库num=1,所以设置缓存为num=2,此时情况为:缓存 empty ; 数据库 num=2

上面这几个顺序不是固定的,只要保证两点

  1. readupdate之后,也就保证了,read读不到缓存,必须经过数据库。
  2. read操作读到的数据库数据是还未更新的(可能是update还没更新,也可能是主从延迟),就会出现数据不一致。

问题2 为什么是 是删除del缓存而不是设置set缓存?

假设有两个并发更新update操作,先假设为A updateB update

此时 缓存和数据库中的数据均为num=1A update更新操作将设置num=2B update更新操作将设置num=3

下面具体看并发操作顺序

  1. A update 更新数据库 num=2。 此时情况为:缓存 num=1 ; 数据库 num=2
  2. B update 另一个并发写请求,更新数据库 num=3。此时情况为:缓存 num=1 ; 数据库 num=3
  3. B update 这个请求写缓存 num=3。 此时情况为:缓存 num=3 ; 数据库 num=3
  4. A update 最开始的请求写缓存 num=2。此时情况为:缓存 num=2 ; 数据库 num=3

缓存和数据库数据不一致。如果是del缓存,就不会出现这样的问题。

问题3 如果update更新操作 发生了设置数据库成功,删除del缓存失败。

这种肯定会出现的,可能是网络抖动,应用服务器连接缓存服务器失败、超时等等。

其实如果连接数据库服务器失败的话,是没有问题的,因为数据库和缓存都没有变化,还是一致的。

为了保证删除缓存这个信息传递到缓存服务器,我们可以通过中间件databus等,监听MySQL bin log的方式,一旦MySQL bin log数据有变动,则发出消息,推送到缓存服务器,让缓存服务器删除对应的数据。使用了消息队列,可以保证消息必达。

最佳的方式

问题4 读取操作 主从复制延迟,读到的旧数据

这个就是发生的概率比较大,因为数据库一般都是主从同步的架构,并且存在一定的延迟,还有就是read操作一般都是落在从库上。

解决方法

问题5 Cache Aside pattern 并发问题,我觉得是不太容易理解。

假设有两个并发操作,一个是更新update操作,一个读read操作。

此时 缓存中没有数据,数据库中的数据为num=1,更新操作将设置num=2

下面具体看并发操作顺序

  1. read读操作,cache miss没有命中缓存。此时情况为:缓存 empty ; 数据库 num=1
  2. 还是这个read读操作,读到数据库num=1。此时情况为:缓存 empty ; 数据库 num=1
  3. update更新操作,更新数据库num=2。 此时情况为:缓存 empty ; 数据库 num=2
  4. 还是这个update更新操作,让缓存失效了。 此时情况为:缓存 empty ; 数据库 num=2
  5. 还是这个read读操作,设置缓存为num=1,因为上面步骤2从数据库中读到的是1。此时情况为:缓存 empty ; 数据库 num=2

此时,数据情况不一致,但是发生的概率较小。

这几个步骤,第1步和最后一步都是read操作,中间是update操作。

一般来讲,update操作要比read操作耗时长。

参考资料