您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

缓存穿透、击穿、雪崩什么的分不清楚?看了这篇文章后,我明白了

时间:2020-12-14 13:30:21  来源:  作者:

对于缓存,大家肯定都不陌生,不管是前端还是服务端开发,缓存几乎都是必不可少的优化方式之一。在实际生产环境中,缓存的使用规范也是一直备受重视的,如果使用的不好,很容易就遇到缓存击穿、雪崩等严重异常情景,从而给系统带来难以预料的灾害。

为了避免缓存使用不当带来的损失,我们有必要了解每种异常产生的原因和解决办法,从而做出更好的预防措施。

缓存穿透

而缓存穿透是指缓存和数据库中都没有的数据,这样每次请求都会去查库,不会查缓存,如果同一时间有大量请求进来的话,就会给数据库造成巨大的查询压力,甚至击垮db系统。

缓存穿透、击穿、雪崩什么的分不清楚?看了这篇文章后,我明白了

 

比如说查询id为-1的商品,这样的id在商品表里肯定不存在,如果没做特殊处理的话,攻击者很容易可以让系统崩溃,那我们该如何避免这种情况发生呢?

一般来说,缓存穿透常用的解决方案大概有两种:

一、缓存空对象

当缓存和数据都查不到对应key的数据时,可以将返回的空对象写到缓存中,这样下次请求该key时直接从缓存中查询返回空对象,就不用走db了。当然,为了避免存储过多空对象,通常会给空对象设置一个比较短的过期时间,就比如像这样给key设置30秒的过期时间:

redisTemplate.opsForValue().set(key, null, 30, TimeUnit.SECONDS);

这种方法会存在两个问题:

  • 如果有大量的key穿透,缓存空对象会占用宝贵的内存空间。
  • 空对象的key设置了过期时间,这段时间内可能数据库刚好有了该key的数据,从而导致数据不一致的情况。

这种情况下,我们可以用更好的解决方案,也就是布隆过滤器

二、Bloom Filter

布隆过滤器(Bloom Filter)是1970年由一个叫布隆的小伙子提出的,是一种由一个很长的二进制向量和一系列随机映射函数构成的概率型数据结构,这种数据结构的空间效率非常高,可以用于检索集合中是否存在特定的元素。

设计思想

布隆过滤器由一个长度为m比特的位数组(bit array)与k个哈希函数(hash function)组成的数据结构。原理是当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就大约知道集合中有没有它了,也就是说,如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。

至于说为什么都是1的情况只是可能存在检索元素,这是因为不同的元素计算的哈希值有可能一样,会出现哈希碰撞,导致一个不存在的元素有可能对应的比特位为1。

举个例子:下图是一个布隆过滤器,共有18个比特位,3个哈希函数。当查询某个元素w时,通过三个哈希函数计算,发现有一个比特位的值为0,可以肯定认为该元素不在集合中。

缓存穿透、击穿、雪崩什么的分不清楚?看了这篇文章后,我明白了

 

优缺点

优点:

  • 节省空间:不需要存储数据本身,只需要存储数据对应hash比特位
  • 时间复杂度低:基于哈希算法来查找元素,插入和查找的时间复杂度都为O(k),k为哈希函数的个数

缺点:

  • 准确率有误:布隆过滤器判断存在,可能出现元素不在集合中;判断准确率取决于哈希函数的个数
  • 不能删除元素:如果一个元素被删除,但是却不能从布隆过滤器中删除,这样进一步导致了不存在的元素也会显示1的情况。

适用场景

  • 爬虫系统url去重
  • 垃圾邮件过滤
  • 黑名单

缓存击穿

缓存击穿从字面上看很容易让人跟穿透搞混,这也是很多面试官喜欢埋坑的地方,当然,只要我们对知识点了然于心的话,面试的时候也不会那么被糊弄

简单来说,缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就好像堤坝突然破了一个口,大量洪水汹涌而入。

当发生缓存击穿的时候,数据库的查询压力会倍增,导致大量的请求阻塞。

解决办法也不难,既然是热点key,那么说明该key会一直被访问,既然如此,我们就不对这个key设置失效时间了,如果数据需要更新的话,我们可以后台开启一个异步线程,发现过期的key直接重写缓存即可。

当然,这种解决方案只适用于不要求数据严格一致性的情况,因为当后台线程在构建缓存的时候,其他的线程很有可能也在读取数据,这样就会访问到旧数据了。

如果要严格保证数据一致的话,可以用互斥锁

互斥锁

互斥锁就是说,当key失效的时候,让一个线程读取数据并构建到缓存中,其他线程就先等待,直到缓存构建完后重新读取缓存即可。

如果是单机系统,用JDK本身的同步工具Synchronized或ReentrantLock就可以实现,但一般来说,都达到防止缓存击穿的流量了谁还搞什么单机系统,肯定是分布式高大上点啊,这种情况我们就可以用分布式锁来做互斥效果。

为了你们能更懂流程,作为暖男的我还是一如既往的给你们准备了伪代码啦:

public String getData(String key){
    String data = redisTemplate.opsForValue().get(key);
    if (StringUtils.isNotEmpty(data)){
        return data;
    }
    String lockKey = this.getClass().getName() + ":" + key;
    RLock lock = redissonClient.getLock(lockKey);
    try {
        boolean boo = lock.tryLock(5, 5, TimeUnit.SECONDS);
        if (!boo) {
            // 休眠一会儿,然后再请求
            Thread.sleep(200L);
            data = getData(key);
        }
        // 读取数据库的数据
        data = getDataByDB(key);
        if (StringUtils.isNotEmpty(data)){
            // 把数据构建到缓存中
            setDataToRedis(key,data);
        }
    } catch (InterruptedException e) {
        // 异常处理,记录日志或者抛异常什么的
    }finally {
        if (lock != null && lock.isLocked()){
            lock.unlock();
        }
    }
    return data;
}

当然,采用互斥锁的方案也是有缺陷的,当缓存失效的时候,同一时间只有一个线程读数据库然后回写缓存,其他线程都处于阻塞状态。如果是高并发场景,大量线程阻塞势必会降低吞吐量。这种情况该如何处理呢?我只能说没什么设计是完美的,你又想数据一致,又想保证吞吐量,哪有那么好的事,为了系统能更加健全,必要的时候牺牲下性能也是可以采取的措施,两者之间怎么取舍要根据实际业务场景来决定,万能的技术方案什么的根本不存在。

缓存雪崩

缓存雪崩也是key失效后大量请求打到数据库的异常情况,不过,跟缓存击穿不同的是,缓存击穿因为指一个热点key失效导致的情况,而缓存雪崩是指缓存中大批量的数据同时过期,巨大的请求量直接落到db层,引起db压力过大甚至宕机,这也符合字面上的“雪崩”说法。

缓存穿透、击穿、雪崩什么的分不清楚?看了这篇文章后,我明白了

 

解决方案

缓存雪崩的解决方案和击穿的思路一致,可以设置key不过期或者互斥锁的方式。

除此之外,因为是预防大面积的key同时失效,可以给不同的key过期时间加上随机值,让缓存失效的时间点尽量均匀 ,这样可以保证数据不会在同一时间大面积失效

redisTemplate.opsForValue().set(Key, value, time + Math.random() * 1000, TimeUnit.SECONDS);  

同时还可以结合主备缓存策略来让互斥锁的方式更加的可靠,

主缓存:有效期按照经验值设置,设置为主读取的缓存,主缓存失效后从数据库加载最新值。

备份缓存:有效期长,获取锁失败时读取的缓存,主缓存更新时需要同步更新备份缓存。

一般来说,上面三种缓存异常场景问的比较多,了解这几种基本就够了,但有些面试官可能喜欢剑走偏锋,进一步延伸其他的异常情景做询问,以防万一,我们也加个菜,介绍下另外两种常见缓存异常。

缓存预热

缓存预热就是系统上线后,先将相关的数据构建到缓存中,这样就可以避免用户请求的时候直接查库。

这部分预热的数据主要取决于访问量和数据量大小,如果数据的访问量不大的话,那么就没必要做预热,都没什么多少请求了,直接按正常的缓存读取流程执行就好。

访问量大的话,也要看数据的大小来做预热措施。

  • 数据量不大的时候,工程启动的时候进行加载缓存动作,这种数据一般可以是电商首页的运营位之类的信息;
  • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
  • 数据量太大的时候,优先保证热点数据进行提前加载到缓存,并且确保访问期间不能更改缓存,比如用定时器在秒杀活动前30分钟就把商品信息之类的刷新到缓存,同时规定后台运营人员不能在秒杀期间更改商品属性。

缓存降级

缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。

在项目实战中通常会将部分热点数据缓存到服务的内存中,类似HashMap、Guava这样的工具,一旦缓存出现异常,可以直接使用服务的内存数据,从而避免数据库遭受巨大压力。

当然,这样的操作对于业务是有损害的,分布式系统中很容易就出现数据不一致的问题,所以,一般这种情况下,我们都优先保证从运维角度确保缓存服务器的高可用性,比如Redis的部署采用集群方式,同时做好备份,总之,尽量避免出现降级的影响。

最后

关于缓存的几大异常处理我们就讲解到这了,虽然每种异常我们都给出了解决的方案,但不是说这玩意直接套上就能用了。现实开发过程中还是要根据实际情况来针对缓存做相应措施,比如用布隆过滤器预防缓存穿透虽然很有效,但并不算特别常用,这年头,防止恶意攻击什么的都是先在运维层面做限制,业务代码层面更多的是对参数和数据做校验。

如果每个使用缓存的地方都要考虑的这么复杂的话,那工作量无疑会更加繁杂,过度设计只会让代码维护起来也麻烦,而且实用性还不一定强,没必要啊。程序员嘛,给自己增添烦恼的事情越少越好,毕竟我们最大的敌人不是996,而是那珍贵的发量啊。



Tags:缓存   点击:()  评论:()
声明:本站部分内容来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除,谢谢。
▌相关评论
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
▌相关推荐
对于缓存,大家肯定都不陌生,不管是前端还是服务端开发,缓存几乎都是必不可少的优化方式之一。在实际生产环境中,缓存的使用规范也是一直备受重视的,如果使用的不好,很容易就遇到缓...【详细内容】
2020-12-14   缓存  点击:(0)  评论:(0)  加入收藏
当打开软件或文件时,Windows会自动将其中的部分或全部存储在系统内存中。当然,应用程序数据如何存储在内存中完全由Windows管理。由于系统内存比常规的硬盘或SSD更快,更易访问,...【详细内容】
2020-11-26   缓存  点击:(9)  评论:(0)  加入收藏
关于TCP的接收缓存以及通告窗口,一般而言懂TCP的都能说出个大概,但是涉及到细节的话可能理解就不那么深入了。问题: 明明在接收端有8192字节的接收缓存,为什么收了不到8000字节...【详细内容】
2020-11-18   缓存  点击:(5)  评论:(0)  加入收藏
前言LRU算法和LFU算法是属于页面置换的一种算法,或者更通俗的说,就是缓存如何淘汰的一种策略。我们通常在设计一个系统的时候,由于数据库的读取速度远小于内存的读取速度,所以为...【详细内容】
2020-11-04   缓存  点击:(18)  评论:(0)  加入收藏
我们都知道,对于安卓手机,随着手机的使用,内存会越来越小,尤其是QQ、微信这一类的聊天通讯软件,应用数据是一天一天的见长,动不动就是好几个G,今天就给大家安排一款软件Avast Clean...【详细内容】
2020-10-27   缓存  点击:(15)  评论:(0)  加入收藏
一、缓存设计1、缓存的作用在业务系统中,查询时最容易出现性能问题的模块,查询面对的数据量大,筛选条件复杂,所以在系统架构中引入缓存层,则是非常必要的,用来缓存热点数据,达到快...【详细内容】
2020-10-21   缓存  点击:(12)  评论:(0)  加入收藏
go-zero微服务框架中提供了许多开箱即用的工具,好的工具不仅能提升服务的性能而且还能提升代码的鲁棒性避免出错,实现代码风格的统一方便他人阅读等等。本文主要讲述进程内共...【详细内容】
2020-10-18   缓存  点击:(4)  评论:(0)  加入收藏
LRU的英文全称是Least Recently Used,也即最不经常使用。我们看着好像挺迷糊的,其实这个含义要结合缓存一起使用。对于工程而言,缓存是非常非常重要的机制,尤其是在当下的互联网...【详细内容】
2020-10-14   缓存  点击:(13)  评论:(0)  加入收藏
一谈缓存,内心顿时豁然开朗。迫于key-value的形式,总感觉轻风拂面,杨柳依依,一切都尽在我掌握之中。犹如那一眼相中佳人的冲动,脑子里尽是佳人的容颜。 那缓存如果站在网站架构的...【详细内容】
2020-09-29   缓存  点击:(10)  评论:(0)  加入收藏
在从计算机上拔下 USB 闪存驱动器之前,许多人已经养成了手动点击任务栏上的“安全删除硬件”图标来“弹出”的习惯。然而从 Windows 10 1903 版本起,这似乎已经成为了一项不必...【详细内容】
2020-09-18   缓存  点击:(12)  评论:(0)  加入收藏
据外媒最新报道称,从Windows 10 1903版本起,微软就默认禁用了USB驱动器缓存,确保大部分情况下可直接断开设备连接。如果你是一个急性子,不希望因为这样的事情浪费几秒钟、以及减...【详细内容】
2020-09-14   缓存  点击:(10)  评论:(0)  加入收藏
背景在查询类开发中我们有使用缓存的场景,一般可以使用Redis作为缓存,来缓解数据库如MySQL的压力。使用缓存的步骤为:“(1)从Redis缓存中获取数据,如果存在数据,直接返回值。(2)如果...【详细内容】
2020-08-28   缓存  点击:(2)  评论:(0)  加入收藏
Node.js 使用的是 V8 引擎,会自动进行垃圾回收(Garbage Collection,GC),因而写代码的时候不需要像 C/C++ 一样手动分配、释放内存空间,方便不少,不过仍然需要注意内存的使用,避免造成内存泄漏(Memory Leak)。...【详细内容】
2020-08-24   缓存  点击:(1)  评论:(0)  加入收藏
AMD可能会在其第四代Ryzen和Epyc Genoa CPU中引入第四级缓存(L4),以解决不断增加的内核数量。早些时候,我们曾报道说Zen 4的核心数量可能会增加一倍,因为尽管密度增加了近80%(5nm...【详细内容】
2020-08-14   缓存  点击:(8)  评论:(0)  加入收藏
HTTP缓存策略http协议是什么?HTTP协议(超文本传输协议),简单来说就是一种网络传输协议, 浏览器请求服务器获取内容就是基于http协议或者https协议。 使得计算机可以在浏览器和服...【详细内容】
2020-08-06   缓存  点击:(3)  评论:(0)  加入收藏
三个重要的标准:---大型缓存架构中需要首先说一下:海量数据:支持海量数据缓存,支持大规模数据;高并发:在亿级QPS的场景下,可以做到满足业务需求;高可用:表示redis可以做到并且尽可能...【详细内容】
2020-07-24   缓存  点击:(6)  评论:(0)  加入收藏
漏洞信息披露者:Ron Reshef (ronr)严重程度:中危漏洞类型:Web缓存欺骗攻击漏洞介绍网站通常倾向于使用Web缓存功能(例如,通过CDN,负载平衡器或简单地通过反向代理)。目的很简单:存储...【详细内容】
2020-07-21   缓存  点击:(8)  评论:(0)  加入收藏
IT之家 7 月 18 日消息 根据外媒 Tom's Hardware 的消息,英特尔新款赛扬 G5925 和 G5905 都在零售商 LambdaTek 上市,相比之前推出的赛扬 G5920 和 G5900 有 0.1GHz 的主...【详细内容】
2020-07-19   缓存  点击:(10)  评论:(0)  加入收藏
本周的MIUI负责人在线如期而至!此次请来的是输入法产品负责人和隐私安全研发负责人来回答大家的提问。本期主要从两大方向展开讨论,一是你对输入法有什么期待、意见或建议?对全...【详细内容】
2020-07-16   缓存  点击:(10)  评论:(0)  加入收藏
我们学会了如何使用Spring Boot使用进程内缓存在加速数据访问。可能大家会问,那我们在Spring Boot中到底使用了什么缓存呢?在Spring Boot中通过@EnableCaching注解自动化配置...【详细内容】
2020-07-16   缓存  点击:(12)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条