Cache Aside Pattern(读写缓存模式)

https://www.jianshu.com/p/fb3a41ea18ec
https://www.cnblogs.com/findbetterme/p/11443168.html

前言

缓存是互联网高并发系统里常用的组件。由于多增加了一层,如果没有正确的使用效果可能适得其反,诸如“缓存是删除还是更新?”,“先操作数据库还是先操作缓存?”都是些老生常谈的话题,今天要介绍的是一个由 Facebook 提出的广受认可的缓存方案。

缓存是删除还是更新

  1. 缓存更新策略,如果缓存里面存的 value 是经过序列化的对象,需要经过反序列化设置新值等步骤更新成本高,此时删除缓存成本低 ,只需直接淘汰缓存,等待下次数据汇源在设置新的缓存。
  2. 缓存更新策略,高并发场景有脏数据问题。
同时有请求A和请求B进行更新操作,那么会出现:

线程A更新了数据库;

线程B更新了数据库;

线程B更新了缓存;

线程A更新了缓存;

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

总结:更新缓存会带来种种问题,直接删除缓存比较简单粗暴,稳妥。

先更新数据库还是先操作缓存

  1. 先操作(删除)缓存的情况,还是回到上面的高并发场景:
1. 请求A进行写操作,删除缓存;

2. 请求B查询发现缓存不存在;

3. 请求B去数据库查询得到旧值;

4. 请求B将旧值写入缓存;

5. 请求A将新值写入数据库;

此时如果缓存没有设置超时时间,则缓存里面的数据会一直都是旧的数据。

  1. 先更新数据库的情况,这就是 Cache Aside Pattern 里的原则之一,下面分析下 case :
1. 缓存刚好失效,请求A查询数据库,得一个旧值;

2. 请求B将新值写入数据库;

3. 请求B删除缓存;

4. 请求A将查到的旧值写入缓存;

这时缓存里面确实是脏数据了,然而这种情况很小概率发生。因为只有在第 2 步写数据库的请求比第 1 步查询数据的请求还快还会发生这种情况,由于数据库的特性,这种情况很少会存在,所以这种方案相对来说是比较可靠的。

Cache Aside Pattern

除了上面举的例子,Cache Aside Pattern 还有几个原则。

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

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

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

前面两点几乎都是共识了也没必要展开讲了,重点就是第 3 点。

参考资料:https://mp.weixin.qq.com/s/-fk-cEIo3iDCUSwT_l8d2w

分割线:-----------------------------------------------------------------------------------------------------------------------------------------------------------

在讨论这三种情况之前,先说明一下我使用缓存的策略,也是大多数人使用的策略,叫做 Cache Aside Pattern。简而言之,就是

1. 首先尝试从缓存读取,读到数据则直接返回;如果读不到,就读数据库,并将数据会写到缓存,并返回。

2. 需要更新数据时,先更新数据库,然后把缓存里对应的数据失效掉(删掉)。

读的逻辑大家都很容易理解,谈谈更新。如果不采取我提到的这种更新方法,你还能想到什么更新方法呢?大概会是:先删除缓存,然后再更新数据库。这么做引发的问题是,如果A,B两个线程同时要更新数据,并且A,B已经都做完了删除缓存这一步,接下来,A先更新了数据库,C线程读取数据,由于缓存没有,则查数据库,并把A更新的数据,写入了缓存,最后B更新数据库。那么缓存和数据库的值就不一致了。另外有人会问,如果采用你提到的方法,为什么最后是把缓存的数据删掉,而不是把更新的数据写到缓存里。这么做引发的问题是,如果A,B两个线程同时做数据更新,A先更新了数据库,B后更新数据库,则此时数据库里存的是B的数据。而更新缓存的时候,是B先更新了缓存,而A后更新了缓存,则缓存里是A的数据。这样缓存和数据库的数据也不一致。按照我提到的这种更新缓存的策略,理论上也是有不一致的风险的,之前在其他的博客文章有看到过,只不过概率很小,我们暂时可以不考虑,后面我们有其他手段来补救。讨论完使用缓存的策略,我们再来看这三种不一致的情况。

1. 对于第一种,在读数据的时候,会自动把数据库的数据写到缓存,因此不一致自动消除.

2. 对于第二种,数据最终变成了不相等,但他们之前在某一个时间点一定是相等的(不管你使用懒加载还是预加载的方式,在缓存加载的那一刻,它一定和数据库一致)。这种不一致,一定是由于你更新数据所引发的。前面我们讲了更新数据的策略,先更新数据库,然后删除缓存。因此,不一致的原因,一定是数据库更新了,但是删除缓存失败了。

3. 对于第三种,情况和第二种类似,你把数据库的数据删了,但是删除缓存的时候失败了。

因此,最终的结论是,需要解决的不一致,产生的原因是更新数据库成功,但是删除缓存失败。

解决方案大概有以下几种:

1. 对删除缓存进行重试,数据的一致性要求越高,我越是重试得快。

2. 定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载。

3. 给所有的缓存一个失效期。

第三种方案可以说是一个大杀器,任何不一致,都可以靠失效期解决,失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定。

并发不高的情况:

读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;

写: 写mysql->成功,再写redis;

并发高的情况:

读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;

写:异步话,先写入redis的缓存,就直接返回;定期或特定动作将数据保存到mysql,可以做到多次更新,一次保存;

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值