面试:什么是缓存雪崩、缓存击穿、缓存穿透?
文章目录
- 缓存雪崩
-
- 1.什么是缓存雪崩
- 2.解决方案
- 缓存击穿
-
- 1.什么是缓存击穿
- 2.解决方案
- 3.缓存雪崩和缓存击穿的区别
- 缓存穿透
-
- 1.什么是缓存穿透
- 2.解决方案
- 3.布隆过滤器
- 4.缓存击穿和缓存穿透的区别
- 总结
缓存雪崩
1.什么是缓存雪崩
为了使查询速度更快,我们选择使用缓存来保存数据,使原本每次请求都需要查询数据库的操作变成先查询缓存,缓存有直接返回,缓存没有则查询数据库然后再写入缓存中,通常缓存都是有有效时长的,否则就会一直占用内存空间。
当大量请求在访问都会先从缓存查询,如果此时大部分缓存同时过期失效,那么这些请求都查询不到缓存,此时他们会全部将请求到数据库,当请求数量足够大时此时将会把数据库压垮,这就是缓存雪崩。比如在凌晨十二点搞促销,大约有10000个用户发起请求,此时缓存过期,则这10000个请求直接打到数据库上,把数据库压垮,即使重启数据库请求依然会打到数据库上
2.解决方案
- 不设置过期时间,缓存更新直接刷新
- 过期时间上加随机值,避免缓存集中过期
- 使用集群将数据均匀分布在机器上
- 采取一定的限流降级机制,防止大量请求搞垮数据库
缓存击穿
1.什么是缓存击穿
当redis缓存中有一个key是大量请求同时访问的热点数据,如果突然这个key时间到了,那么大量的请求在缓存中获取不到该key,穿过缓存直接来到数据库导致数据库崩溃,这样因为单个key失效而穿过缓存到数据库称为缓存击穿
2.解决方案
- 最简单粗暴的解决方案就是让热点key不设置过期时间,即key一直存在于缓存中,更新时直接覆盖即可
- 设置定时任务检测要过期的key,然后在将要过期的时候重新从数据库把数据刷新到缓存中,这样的方式增加系统复杂度,并且实现复杂
- 使用互斥锁的方案,在缓存中没有数据去数据库查询时加上锁,让一个线程去查询数据库以及更新缓存,其他线程等待,这样减小数据库压力,代码如下。从代码中可以看出加互斥锁的方式比较复杂,并且使用递归的方式获取数据,极端情况下如果一直不能从缓存获取数据则会一直尝试获取锁
public Object getData(String key) { ReentrantLock lock = new ReentrantLock(); // 先从缓存取数据 Object result = getDataFromCache(key); if (Objects.isNull(result)) { // 没取到数据则上锁从数据库查询,取到了直接返回 if (lock.tryLock()) { // 争取到锁,从数据库查询数据 Object dbData = getDataFromDataBase(key); if (Objects.nonNull(dbData)) { // 如果数据库查询数据不为空刷新缓存然后返回 setCache(dbData); return dbData; } } else { // 没争取到锁则等待一下,等待时间自己把握 // 然后再次尝试调用获取方法,如果其他线程刷新缓存成功,在之后的调用中就会从缓存拿到数据 // 如果其他线程也没有刷新缓存,则在递归调用中继续争取锁 try { TimeUnit.MILLISECONDS.sleep(1000); result = getData(key); } catch (InterruptedException e) { e.printStackTrace(); } } } return result;}
这里依然会存在一些问题,比如等待时间长短不容易把握,其次在等待过后继续递归调用方法争取锁,若长时间没有争取到锁,递归的层次就会越来越深,性能会大幅度下降,甚至造成SOFE。通常情况下解决缓存击穿直接让key缓存不过期就行了,毕竟并发量也达不到那么高
3.缓存雪崩和缓存击穿的区别
缓存雪崩和缓存击穿看起来是一回事,都是key过期导致的,其实缓存雪崩是大范围的key过期导致大量请求直接到数据库,缓存击穿是单个热点key失效导致大量请求到数据库。
缓存穿透
1.什么是缓存穿透
指当请求查询缓存和数据库都不存在的数据时,先查询缓存为空,再查询数据库依然为空,向请求返回空,如果大量请求同时访问这些不存在key那么这些请求依然会造成压垮数据库的现象,这种通常是恶意查询和被攻击几率较大
2.解决方案
- 缓存中存放查询的key,值设置为空,这样就能避免请求打到数据库,但是这样就会占用缓存空间
- 在请求接口处做检查,如用户鉴权、参数校验等,对于不合法的请求直接返回,这样能够拦截部分不合法的请求
- 使用布隆过滤器,那么什么是布隆过滤器呢?
3.布隆过滤器
布隆过滤器(Bloom Filter),其实就是一个值只有0和1的bit数组,对待过滤的数值求hash散列后可以查看这个数组中对应的位置上是否为1来进行判断过滤,常应用于网页黑名单系统、垃圾邮件过滤系统、爬虫网址判重系统等。布隆过滤器具有以下特点:
- 数组占用空间小,通常一个容量为1亿的bit数组占用空间为11.92MB左右
- 通过hash定位数组速度快,可在O(1)时间内返回结果
- 不能够删除并且存在一定的误差
产生误差是由于在计算hash定位时可能会出现hash冲突,也就是说值通过hash计算定位在一个值为1的位置,但是该值并不需要过滤因此会产生一定误差,解决办法有两种
- 选用多个hash函数进行散列映射,当多个函数定位到的位置上全部为1时过滤该值,否则不过滤,这样准确率将会上升但并不能百分之百解决
- 如果能提前知道总共数量则可以将bit数组大小设为总共数量大小,这样能够保证每个位置只能有一次命中,相对来说更占空间一点,但是由于bit数组本身占空间较小,即使100亿长度的数组也只占用1G左右空间
可以通过使用布隆过滤器来对缓存穿透问题进行解决,当访问不存在的key时,将该key散列到bit数组中,且对应位置的值置1,下一次再访问该值时先在bit数组上查找,发现映射位置上为1,直接返回空,不需要再进行redis和数据库的查询
4.缓存击穿和缓存穿透的区别
- 缓存击穿是在单个key上的存在大量访问时,key失效导致大量访问直接到数据库上,就好像在缓存上开了一个洞,请求全部从这个洞中穿过来到数据库
- 缓存穿透是指不停的访问缓存中不存在的值导致请求不停的请求数据库中不存在的数据,同样增加数据库压力,这样的方式相当于刻意绕过(穿过)缓存这部分来到数据库
总结
缓存雪崩、缓存击穿、缓存穿透是生产和面试中常见的问题,在请求量小的时候这些问题造成的影响不大,但是一旦访问量大起来这些问题将会造成服务器宕机,甚至在重启服务器之后依然会扛不住压力继续宕机,只有提前为数据做好分析准备,选用合适方案进行解决才能够尽可能的减小生产服务器损失。