缓存更新策略以及缓存击穿、雪崩、穿透

本项目使用的更新策略是:cache aside ;

简而言之,就是

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

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

首先要确定的是使用先更新数据库,再更新缓存的方法(最好淘汰缓存),防止多线程情况下,更新缓存和更新数据库不一致,存在脏数据。

原因:先更新数据库,如果此时来请求,会先访问老的缓存,取到缓存数据,然后我们更新缓存,让缓存信息淘汰,下一次请求的时候,这个数据就会从数据库中取。

还存在一种情况:先淘汰缓存在更新数据库。如果AB线程同时完成淘汰缓存的工作后,准备更新数据库,A更新完之后,来了一个读操作,这时候从数据库读到的是A更新的数据,若此时,B开始更新数据库,那么就存在数据库缓存的不一致问题。

另外有人会问,如果采用你提到的方法,为什么最后是把缓存的数据删掉,而不是把更新的数据写到缓存里。这么做引发的问题是,如果A,B两个线程同时做数据更新,A先更新了数据库,B后更新数据库,则此时数据库里存的是B的数据。而更新缓存的时候,是B先更新了缓存,而A后更新了缓存,则缓存里是A的数据。这样缓存和数据库的数据也不一致。

这两种方法都会有一定的风险造成缓存的一致性问题:我们最终只能保证缓存的最终一致性而不能保证缓存“强一致”

解决:

1.对每个缓存设置过期时间,自动失效带来的是一定会从数据中取数据

2.定期全量更新缓存,将所有缓存都淘汰,然后重新加载

3.缓存淘汰重试,越是热点数据,淘汰失败,重试的次数,速度越快。

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

Cache Aside Pattern

这是最常用最常用的pattern了。其具体逻辑如下:

  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  • 命中:应用程序从cache中取数据,取到后返回。
  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

注意,我们的更新是先更新数据库,成功后,让缓存失效。那么,这种方式是否可以没有文章前面提到过的那个问题呢?我们可以脑补一下。

一个是查询操作,一个是更新操作的并发,首先,没有了删除cache数据的操作了,而是先更新了数据库中的数据,此时,缓存依然有效,所以,并发的查询操作拿的是没有更新的数据,但是,更新操作马上让缓存的失效了,后续的查询操作再把数据从数据库中拉出来。而不会像文章开头的那个逻辑产生的问题,后续的查询操作一直都在取老的数据。

Read/Write Through Pattern

我们可以看到,在上面的Cache Aside套路中,我们的应用代码需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository)。所以,应用程序比较啰嗦。而Read/Write Through套路是把更新数据库(Repository)的操作由缓存自己代理了,所以,对于应用层来说,就简单很多了。可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache。

Read Through

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

Write Through

Write Through 套路和Read Through相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)

缓存击穿、缓存雪崩和缓存穿透

缓存穿透:当利用一个不存在的Key访问缓存的时候,必然访问不到,命中miss,然后我们就会去数据库中查找,当存在一定量的并发,或者有人恶意频繁用不存在的key来访问我们的服务器的时候,一定量的次数后,数据库服务器必然承受不住,崩溃。

解决方案:

1.用一个bit map (布隆过滤器),将所有可能性的key都存储在其中,如果出现一定不存在的key,拦截掉该不可能存在的请求,不让其访问数据库。

2.如果出现访问不存在的key,也让数据库换回一个null值,并存储在数据库中。但是可以设置其过期时间极短。

缓存雪崩:当我们设置的一些数据key 采用相同的过期时间,当达到一个过期时间临界点的时候,大部分缓存过期,这时候如果一个高并发量的访问过来,就会同时访问数据库,造成数据压力或者崩溃。

解决:

1.可以在原有的失效时间的基础上,加上以个随机值,1-5分钟,这样就可以防止同时过期,造成大量数据库的访问。

2.分级缓存,第一级缓存失效的基础上,访问二级缓存,在二级缓存的失效同时在更新,分级缓存时间不同。

3.在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

缓存击穿:对于某一个热点数据,相对于雪崩,雪崩是多个数据,热点数据在某个时间段失效,高并发下大量访问数据库,造成数据库的压力。

解决:在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。

猜你喜欢

转载自blog.csdn.net/weixin_38035852/article/details/81253860