Redis—缓存雪崩、缓存击穿、缓存穿透

一、缓存雪崩

很多时候,Redis中的缓存是要设置过期时间的,假如Redis中的数据,过期时间都设置成一样的,那么到了时间之后,全部缓存过期失效,下一秒所有的请求都会访问数据库,那么数据库可能因为访问量过大导致“崩溃”,这就是缓存雪崩。

如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。

这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。

应对方法:

1、缓存永远不过期:最暴力的解决办法,缓存不设置自动过期时间,只要缓存不崩,数据库就不会崩。

2、使用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。

加锁排队. 限流-- 限流算法. 1.计数 2.滑动窗口 3.  令牌桶Token Bucket 4.漏桶 leaky bucket [1]。

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

3、做二级缓存,或者双缓存策略。

A为原始缓存,B为拷贝缓存。A失效时,可以访问B,缓存A的失效时间为20分钟,缓存B不设置失效时间。自己做缓存预热操作。细分以下几个小点:

  • 从缓存A读数据库,有则直接返回。
  • 若缓存A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。
  • 更新线程同时更新缓存A和缓存B。

4、给缓存的失效时间,加上一个随机值,避免集体失效。让缓存过期时间不那么一致,比如一批缓存数据24小时后过期,那么就在这个基础上,每条缓存的过期时间前后随机1-6000秒(1-10分钟)。设置不同的过期时间,让缓存失效的时间点尽量均匀。

二、缓存击穿

缓存击穿跟缓存雪崩有点像,但是又有一点不一样,缓存雪崩是因为大面积的缓存失效,打崩了DB,而缓存击穿不同的是缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。

应对方法:

1、设置热点数据永远不过期。或者加上互斥锁就能搞定了。

https://www.lizenghai.com/archives/27853.html

https://mp.weixin.qq.com/s?__biz=MzUxNDA1NDI3OA==&mid=2247484581&idx=2&sn=54f2306012619e3dbe36c4fcf9493039&chksm=f94a854cce3d0c5a8be0825de18d381b388d247a7a2145759c3818cfcfa28a364bf8b8b38383&mpshare=1&scene=23&srcid=0627JwWmdlo4tdChETfgpSBL#rd

三、缓存穿透

很多项目在使用Redis或其他缓存框架的时候,都是先查询缓存,查询不到的话再查询数据库,查到之后再放到缓存(内存)中;如果一个key值本身就不存在,那么每一次都会查询数据库,也就是常说的【缓存穿透】。简单理解就是先去缓存中找不到,再去数据库查询,在数据库中也找不到,就发生了缓存穿透。

key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。数据在redis不存在,数据库也不存在,返回空,一般来说空值是不会写入redis的,如果反复请求同一条数据,那么则会发生【缓存穿透】。

应对方法:

1、缓存空对象

如果在Redis中查询不到,并且查询数据库也没有结果,那么就将这个key设置一个空值(value=空),同时写入到Redis中,并设置一个超时过期时间,例如五分钟,那么五分钟以内对这个key的所有查询就可以拦截下来,就不会访问数据库了。如果数据库有key对应的数据了,那么五分钟后Redis中的缓存过期,会访问数据库并加载缓存;但是如果被恶意攻击,每次请求的key都不相同且在数据库中也是不存在的,那么依然会发生缓存穿透,会穿透到数据库。

 如果一个数据库查询返回的数据为空,我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。缓存空对象会有两个问题:

  • 空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 ( 如果是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
  • 缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为 5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。

2、布隆过滤器

将可能存在的数据Hash到一个足够大的bitmap上,它可以告诉你 “某个key一定不存在或者可能存在”,一个一定不存在的数据会被bitmap拦截。

四、缓存并发

大多数时候,我们的程序访问Redis都不可能是单线程,那么当多个Client并发对Redis进行set key操作的时候,可能会产生一些问题;其实Redis本身是单线程的,这种时候会按照先后顺序进行操作;或者把操作放在队列中,按顺序执行;

但比如这种情况:

1.token过期,有两个线程都去重新获取token;

2.线程1获取到token1;

3.线程2获取到token2,此时token1过期;

4.线程1把token1放到Redis,再拿着token1去调用服务,发现过期了,继续去请求token3,此时token2过期;

5.线程2把token2放到Redis,再拿着token2去调用服务,发现过期了,继续去请求token4,此时token3过期;

6.... ...

这就需要我们在更新缓存的时候,做一些控制了。

 

posted @ 2020-09-02 14:08  刘_love_田  阅读(2296)  评论(0编辑  收藏  举报