对应的面试题
了解什么是Redis的雪崩、穿透和击穿?Redis崩溃之后会怎么样?系统该如何应对这种情况?如何处理Redis的穿透?
基础知识点
要回答上面的问题,首先你要知道缓存的雪崩、穿透、击穿都分别是什么。
那让我们来一起看看吧
缓存雪崩 Cache Avalanche
缓存雪崩,我们可以从字面上去理解一下。有一句话说的好,雪崩之前没有一片雪花是无辜的。在这里我们可以把每一个key作为所谓的一篇的雪花。那么就很简单的可以理解了,雪崩无非就是在一个时段内,一大批的key也就是所谓的雪花都出现了问题(比如过期了或者缓存的机器出现了问题),而恰巧又有一大批读取请求过来,导致这些请求全部都落在了数据库上,数据库必然扛不住而就导致数据库挂了,进一步导致了服务的不可用
举例一个业务场景来加深一下印象:对于系统A,假设每天高峰期每秒5000个请求,本来缓存在高峰期可以扛住每秒4000个请求的,但是缓存机器意外发送了全盘宕机。缓存挂了,此时1秒5000个请求全部落在数据库上,数据库必然扛不住,它会报警,然后挂了。此时如果没有采用什么特别的方案来处理这个故障,DBA很着急,重启数据库,但是数据库立马又背新的流量给打死了
这就是缓存雪崩
缓存穿透 Cache Penetration
缓存穿透,笔者是这么理解的所谓的穿透就是穿过了缓存,也就是缓存被架空了。也就意味着每一次请求都直接跳过了缓存,直接落到了数据库上。出现这种问题的主要问题是数据库中也没有请求要的值(null),也就导致了查询完数据库并没有去更新缓存,而导致了每次请求都反反复复的落到了数据库上,而导致数据库挂了
举例一个业务场景来加深一下印象吧:对于系统A,假设每秒5000个请求,结果其中4000个请求都是黑客发出的恶意攻击。黑客发出的那4000个攻击,缓存中查不到,每次你去数据库里查,也查不到。比如在数据库中某张商品信息表的id是从1开始的,结果恶意请求携带的请求id是个负数。这样子的话,缓存中肯定没有,请求的每次都会直接穿过缓存(也就是缓存穿透了)直接查询到数据库,然后数据库就被拍死了
缓存击穿 Hotspot Invalid
缓存击穿,从名字上看起来和缓存穿透很相近。其实出现的场景也很相近,刚刚提到的缓存穿透是被恶意攻击而导致的,而缓存击穿是正常的大量请求导致的。出现的场景为某个热门的key被大量的请求,处于集中式高并发访问的情况,所以当这个key在失效的瞬间,大量的请求就击穿了缓存全部落到了数据库上而导致了数据库挂了
举例一个业务场景:对于前段时间的外卖大战而言,领取优惠卷的页面的点击量是很大的,大家都想快一步去抢卷,如果这个时候某个卷的信息缓存key失效了,这个时候大量的请求全部都落在了数据库上,而导致数据库挂了,那么就出现了所谓的缓存击穿
总结
基础知识搞定~我们再来总结一下
缓存异常出现的三大问题:缓存雪崩、缓存穿透、缓存击穿
| 缓存异常 | 产生问题 |
|---|---|
| 缓存雪崩 | 1.同一时段,大量的key出现失效的情况 2.缓存机器出现宕机的情况 |
| 缓存穿透 | 1.大量的恶意请求架空缓存 |
| 缓存击穿 | 1.频繁访问的热点key突然失效 |
解决方案
前面我们介绍完了对应的基础知识,现在我们知道问题出现了在哪一些地方。所以下一步我们来一个一个解决一下
应对-缓存雪崩
前面我们提到了缓存雪崩的产生原因主要分为两个方面一是大量的key同一个时间段出现失效 二是缓存机器故障宕机
那么我们先来解决大量的key同一个时间段出现失效的问题吧
方案-大量的key同一个时间段出现失效
常见的方法:
- 均匀设置过期时间
- 互斥锁
- 后台更新缓存
均匀设置过期时间
我们可以在设置缓存过期时间的部分下手,既然是大量的key在同一个时间段出现了失效导致的。那么我们就应该避免将大量的数据设置为同一个过期时间。我们可以在对缓存设置过期时间的时候,给每个数据的过期时间加上一个随机数以此来确保数据不会大量堆积到一个时间段内过期
互斥锁
在设置完了过期时间的情况下,我们可能还需要来个互斥锁来确保,因为就单单设置一个过期时间无法确保不会再出现大量的数据在缓存过期时间不一致,毕竟还是存在偶然性。那么我们需要在更新缓存这边进行保障,确保在大量数据过期且大量请求过来落到数据库的前一步进行一个保障。确保同一时间内只有一个请求来构建缓存,当缓存构建完后,再释放锁。而其他请求在没获取到锁之前,也就是有一个请求在构建完缓存前,先重复去等待,等待锁释放后再重新读取缓存,或者可以直接返回空值或者默认值
需要注意的是在实现互斥锁的时候,最好设置锁的过期时间,避免出现第一个请求拿到了锁却因为意外导致一直阻塞,一直不释放锁导致系统的宕机无响应的现象出现
后台更新缓存
这个方法则是将缓存的更新不再让业务线程负责,缓存也不再设置过期时间,而是让缓存永久有效,将缓存更新的工作交由后台线程定时更新。需要注意的是,这里的永久有效是逻辑上的,也就是有些数据并不是一直存在缓存中,因为当系统内存紧张的时候,有些缓存数据会被淘汰。而当数据被淘汰的时间段内业务线程读取数据失败就返回空值,在业务线程的视角就会认为是数据丢失了,那么如何解决呢?
两个办法,一个是后台线程不单单负责更新,也负责频繁检测缓存是否有效,如果检测到了失效了,就立马去数据库读取数据,并且更新到缓存中,但是这个方法存在一定的问题:后台线程的间隔不能太长,太长会导致用户获取数据一直是一个空值并不是真正的数据,所以检测的间隔最好是一个毫秒级的,但是总归是有一个间隔是时间,对于用户的体验感可能会下降。另外一个办法则是当业务线程发现缓存数据失效后,通过消息队列发送一条消息通知后台线程更新缓存
在业务刚上线的时候,最好提前将数据缓起来,而不是等用户访问才来触发缓存构建,这就是所谓的缓存预热
方案-应对Redis故障宕机
针对因为Redis故障宕机而导致的缓存雪崩的问题要怎么解决呢?我们一起来看看
服务熔断或请求限流机制
当Redis故障宕机的时候,我们可以启动服务熔断机制,暂停业务应用对缓存服务的访问,直接返回错误信息,保护我们系统中的数据库,确保数据库系统的正常运行,等到Redis恢复正常后,再恢复服务的访问,但是服务熔断有时候会导致因为部分业务的不可用而导致全部业务无法正常工作,所以为了减少影响,我们还有另外的办法,就好比我们可以启用请求限流机制,在确保数据库系统正常运行的情况下,只将少部分请求发送到数据库进行处理,大量的请求数据就直接进行拒绝,等Redis服务正常后再解除限流的机制,恢复系统的正常运转
构建Reids缓存高可靠集群
前面说的方法是亡羊补牢,也就是缓存雪崩后的处理措施。因此,我们应该一开始就避免出现这种情况,我们最好通过主从节点的方式构建Redis缓存高可用集群。如果Redis缓存的主节点故障宕机,从节点可以切换为主节点,继续提供对应的服务,从而从最大的程度避免由于Redis故障宕机而导致的缓存雪崩的问题
当然解决方案还有很多
缓存雪崩的事前事中事后的解决方案如下:
- 事前:Redis高可用,主从+哨兵,Redis cluster,避免全盘奔溃
- 事中:本地ehcache缓存+hystrix限流&降级,避免MySQL挂掉
- 事后:Redis持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据
应对-缓存穿透
前面我们提到了缓存穿透的原因,也就是请求的数据在缓存和数据库中都没有,有可能是恶意的请求或者业务的错误操作导致了数据的丢失,因此在应对缓存穿透的时候我们有下面三种方案:
- 非法请求的限制
- 缓存空值或者默认值
- 采用布隆过滤器
非法请求的限制
当大量的恶意请求到达缓存和数据库成为缓存穿透触发原因,这个时候说明我们的API入口处理不是很完善,因此我们需要修改API入口的参数认证,判断请求参数是否合理,请求参数是否有非法值、请求字段是否存在。如果判断出是恶意请求就直接返回错误,避免业务因为大量的恶意请求而导致的缓存穿透
缓存空值或者默认值
当线上的业务出现了缓存穿透的现象也就是可能是业务线程错误的操作导致数据丢失,我们可以直接针对查询的数据,直接在缓存中设置一个空值或者默认值,确保后续的请求不会再直接落到数据库上
采用布隆过滤器
我们可以采用布隆过滤器来快速判断数据是否存在,避免通过查询数据库来判断数据是否存在,减少数据库的压力。我们可以在写入数据库时,使用布隆过滤器做个标记,当业务线程接收到用户请求且确认缓存失效后,在访问查询数据库前,先通过布隆过滤器快速判断数据是否存在,而再做是否查询数据库的决定(如果请求数据的key不存在布隆过滤器中,业务线程直接返回错误或者不存在,当请求的数据的key存在布隆过滤器中时,则继续向下查询)。这样子即使出现了缓存穿透也只会访问到Redis和布隆过滤器,而不会将压力到数据库上。保证了数据库的正常运行
关于布隆过滤器的运行方式将在下一篇帖子中进行解读 这边我们就不过多解读
应对-缓存击穿
前面我们提到了缓存击穿是由于热点key在频繁读取的时候失效了,而导致某个时间段大量的请求落到数据库上导致数据库挂掉。
那么对应的解决方案如下:
- 如果缓存的数据是基本上不会发送变换的,则可以尝试将热点数据设置为不过期
- 如果缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于Redis、zookeeper等分布式中间件的分布式互斥锁,或者本地互斥锁以保证少量的请求数据库并且重新构建缓存,其他的线程则在锁释放后能访问到新缓存
- 如果缓存的数据更新频繁或者缓存的刷新整个流程耗时较长的情况下,可以利用定时线程在缓存前期主动地重新构建缓存或者延后缓存的过期时间,以确保所有的请求能一直访问到对应的缓存
缓存穿透
查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库
解决方案一:缓存空数据
解决方案二:布隆过滤器
缓存击穿
给某一个key设置了过期时间,当key过期的时候,恰好这个时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮
解决方案一:互斥锁,强一致性,性能差
解决方案二:逻辑过期,高可用,性能优,不能保证数据绝对一致
缓存雪崩
是指同一时段内大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力
解决方案
- 给不同的key的TTL添加随机值
- 利用Redis集群提高服务的可用性
- 给缓存业务添加降级限流策略 (降级可做为系统的保护策略,适用于穿透、击穿、雪崩)
- 给业务添加多级缓存