小言_互联网的博客

「面试」缓存知识点大总结

399人阅读  评论(0)

周末一刻

这周过的比较快,事情也比较多,依然无法阻挡我每周一浪的节奏。

先去了“杰论”的奶茶店,好不好喝不做评价,主要是捧个场,就感觉你们每次在文章下面点个赞一样。

这周还去了所谓网红桥,被网友们称为成都桥梁界的"新晋扛把子"。看看长撒子样子

这个大桥听说参考了"无限之环"—莫比乌斯杯的概念,将可能在四维空间中才存在的抽象涉及到三维空间,就仿佛数学符号"∞",以此表达高新区无限可能性和开放发展的广阔胸怀,当然也给与了不少的工作机会

吃完串串,这个串串感觉真的巴适。吃完约几个朋友漫步于桥上,听着河水流动的呲呲声,呼吸着湿润的空气是不是很xui服,


没想到吃完了去抽个奖,啊哈,中了100的代金卷,没想我旁边的菇凉比我还激动,当然我确实没想到我会抽个100的代金卷,毕竟一直都是:“感谢抽奖”。下次请你吃?


生活着不就这样嘛,吃吃喝喝,逛逛,别天天想着买房买车,给自己压力,有个大大的梦想,咦,分治思想嘛,化成一个个小目标慢慢实现勒

好了,还是学点东西,让我有点东西。

使用好缓存就仿佛拥有了无比锋利的宝剑,对于性能的提升是立竿见影的。但是如果使用的不好就会严重的影响系统的运行,那么今天我们看看缓存到底是什么以及缓存的应用模式

1 缓存的本质

网上有各种对缓存的定义和解释,我认为缓存是为了节约对原始资源重复获取的开销

什么是资源?

首先如果这个资源是经常变化的,你觉得缓存了有什么意义。显然不可行,所以,我们对资源的操作需要时是幂等切安全的。举个例子,比如银行转账,改变了目标状态,自然就不能缓存。

其次,缓存的数据是"重复"获取的。意味着将经常使用的数据缓存下来,当下一次访问的时候直接返回即可,这样节约了通过原始流程访问数据的时间。比如数据库中的连接池,线程池,就是为了避免频繁的创建链接,销毁链接而出。

如何衡量这个缓存的效果?

在数据获取的过程中,通过缓存获得数据的次数,除以总的次数所得到的结果为缓冲命中率

加了一层缓存就一定更快?

非也。首先这个快是相对而言的,因为在不同的系统中对于同一对象可能扮演的角色不同。比如CPU的多级高速缓存,即内存的缓存。而内存对于CPU存储而言又比较慢,但是对于磁盘却快了很多,所以它可以用作磁盘的缓存介质

能够举一个常见的例子来阐述缓存的应用?

如果看了我之前写的面试文章,应该对这个面试题非常的熟悉了。“当敲入URL会经历什么?”,在那篇文章中,写了一万多字来阐述这个问题,意味着如果面试官问你这个问题,你可以讲什么且可以从一个点出发将的比较深。回到这里,如果我让你以缓存的角度来阐述这个问题,你将如何去回答?

首先在地址栏输入域名,会查询浏览器内部"域名-IP"缓存,如果之前通过这个浏览器访问过这个域名,那么就有一张映射表。

如果没有,则会查询操作系统是否存在这个缓存,如果是mac电脑则/etc/hosts就可以定义域名到IP的关系。

如果还是没有则会查询域名服务器DNS得到对应的IP和可缓存的时间。如果需要使用命令来测试,这里可以同dig命令查看这个详细过程。

当请求到达了服务端,通常会有反向代理,在反向代理中会存在缓存配置,对于一个MVC架构的应用而言,MVC的各个层都有可以应用缓存模式

Controller层有拦截过滤器,可以通过配置缓存来过滤我们需要的请求,这样过滤器直接返回缓存结果而不执行后面的逻辑

对于Model层,在DAO上的Service暴露API应用缓存也是非常常见的操作

对于View层,页面标签居多,不会每次都去渲染,而是通过缓存标签的方式节省渲染操作

这一切操作完事后就会返回给浏览器,浏览器会加载页面中大量的资源,这都是通过读取浏览器的缓存来避免HTTP请求的开销。

看吧,我们在不断冲浪的同时,下面有着这么精彩的故事,也说明了缓存的重要性了吧

3 缓存应用模式

通过不同的特性和优缺点,将缓存分了几种模式,我们看看这几种模式的区别以及优缺点

Cache-Aside

数据库的异常情形

  • 如果数据库读取异常直接返回失败,不会存在数据不一致的情况
  • 如果数据读取成功了,但是缓存的写入失败,那么下次访问统一数据的时候继续尝试写入,也没有数据不一致的情况

所以这两种情况是比较安全的

数据更新的策略,到底是先更新数据库还是先让缓存失效

假设我们让缓存显示小,意味着在数据库更新成功之前,如果另外的请求访问缓存,此时缓存已经失效,那么就会按照原来的访问策略,从数据库中使用这个已经旧的数据去更新缓存的数据,这样就导致过期的数据会长期存放于缓存中,最终导致数据不一致。

为了加深大家对这个概念的理解,画了下面这幅图

第二需要注意的是:数据库更新以后,需要让缓存失效而不是更新缓存为最新的值

为什么呢?如果两个几乎同时发出的请求分别更新数据库中的值A和B,如果结果是B的更新晚于A,那么数据中的最终值为B。但是,如果数据库更新后去更新缓存,而不是让缓存失效,那么缓存中的数据就有可能是A,而不是B。因为数据库虽然是"更新为A"在"更新为B"之前发生,但如果不做特殊的跨存储系统的事务控制,缓存的更新顺序就未必遵从"A先于B"这个规则,就会导致缓存中的数据是一个长期错误的值A。

同样画个图,让大家理解更深刻

4 缓存常出现的问题

上面大多数时候都在谈论缓存带来的好处,现在该说说其弊端了

  • 缓存穿透

当在同一时间有大量的数据访问,经过了缓存屏障但是并没有起到应有的保护作用。举个例子,需要在数据库对一个key进行查询,如果数据库中没有这个数据,那么缓存也没有这个数据,每次请求都会去数据库查,缓存再次根本没有作用

怎么解决这个问题勒?

我们可以在缓存中对这个key存放一个空结果,毕竟没有结果也是结果嘛

还有一种比较严重的缓存穿透后果。在一般的缓存策略下,通常需要先发生一次缓存命中失败,然后从数据库得到结果,再填到内存缓存中。但是,如果这个数据库的查询太慢,此时大量的数据接踵而至,全部穿透缓存落在数据库上, 此时对于最初的请求所引发的缓存可能还没来得及混村,数据库就直接挂掉了,这种问题有那么解决方案呢

还有其他的解决方案?

使用布隆过滤器。主要用来判断一个元素是否在一个集合中,这个算法由一个二进制数据和一个hash算法组成,其基本的思路是这样的:

我们把集合中的每一个值按照提供的 Hash 算法算出对应的 Hash 值,然后将 Hash 值对数组长度取模后得到需要计入数组的索引值,并且将数组这个位置的值从 0 改成 1。在判断一个元素是否存在于这个集合中时,你只需要将这个元素按照相同的算法计算出索引值,如果这个位置的值为 1 就认为这个元素在集合中,否则则认为不在集合中。 画个图看看就更清楚了

ABC组成一个集合,元素D计算出来对应的数值为1,所以D也存在于集合。但是F在数组值为0,所以不在集合中了。

如何通过布隆过滤器解决缓存穿透?

我们以存储用户为例子,初始化一个大数组,长度为10亿的数据,选择一个Hash算法,计算用户的ID的Hash值并映射到这个大数组中,映射的位置为1,其他设置为0

一旦存在新注册的用户,当我们需要查询一个用户的信息的时候,首先查询的ID在布隆过滤器中是否存在,不存在则直接返回空,不继续查询数据库和缓存,这样不就大大的减少异常查询带来的缓存穿透

过滤器具有非常高的性能,无论是写入还是读取时间复杂度都是O(1)。空间上也是有着不错的又是,假设20亿 的数据需要2000000000/8/1024/1024 = 238M 控制,而如果使用数组来村粗,每个用户ID占4个字节,存储20亿 需要2000000000 * 4 / 1024 / 1024 =7600M 的空间,是布隆过滤器的 32 倍

布隆过滤器有哪些缺陷?

  • 判断元素是否在集合中有一定的错误几率

这个问题是hash算法的问题,Hash算法难免有一定的碰撞几率,所谓碰撞,即不同的输入值得到相同的Hash结果

  • 不支持删除元素

举个例子,假设两个元素A和B都是集合中的元素,具有相同的Hash值,会映射到数组相同的位置。此时如果删除A,数组中对应的位置从1变为-,那么在判断B的时候发现B不在集合中了,得到了错误的结论

如何解决这个问题

让数组中不在只有0和1两个数值,直接存储一个计数。假设AB同时命中数组的索引,此时这个值为2,如果A删除,将这个值改为1。当然这样就会增加空间的消耗,所以使用过滤器根据业务需求而定。

流量控制

限制对于同一数据的访问,必须等到前一个完成以后才能进行下一个,即如果缓存失效而引发的数据库查询正在进行,那么请他的请求只好乖乖的等着,这种方法通用性还可以,但是这个等待机制会比较影响用户的体验

缓存预热

在大量请求到来之前先预热一波,简单高效,局限性在于需要提前知道那些数据可能引发缓存穿透的问题

  • 缓存雪崩

在一定时间段出现大量的请求访问失效,直接造成后方的系统压力过大,引起系统过载,宕机,这就是缓存雪崩。

通常采取限流和预热两种方式进行避免。限流可以保障请求大量落到数据库的时候,系统只接纳能够承载的数量。对于预热则主动先王内存中加载一定的热点数据,请求到来的时候缓存不是空的,这样具有一定的保护能力。

缓存数据存在过期时间,如果缓存加载时间比较集中,那么此时大量的缓存同时国企,对这些数据的请求将全部落在后面的数据库从而导致系统崩溃,如何避免?

这里主要避免缓存集中写入时间,如果无法避免则使用一个范围的随机数均匀分散过期时间,从而打散系统造成的压力

  • LRU致命缺陷

LRU即Least Recently Used,最少最近使用算法,原理是维护一个限定最大容量的队列,队列头部总是放置最近访问的元素,超过容量限制的时候从队尾淘汰元素。

通过一个图看看LRU工作原理

这看起来是 个不错的方案,需要从缓存淘汰数据的时候总是从尾部淘汰一个不常用的,但是如果有用户有意无意的访问一些错误信息,此时就会破坏整个LRU队列中最近访问数据的真实性。

如果一不小心咱们缓存中存放不少冷门数据,热门活动开始后,大量的请求到来,全部穿透缓存导致数据库压力倍增,网站响应时间瞬间到告警线上。

怎么解决这个问题?

这里基于LRU的改进方案很多,简单说下LRU-K,即主缓存队列排的是"第K次访问的元素",也就是如果访问的次数小于k,则将其放在另一个"低级"的队列中维护,这样保证在一定的访问下限才能被送到LRU主队列中

5 小结

其中总结了我觉得比较高频的关于缓存的题,希望对大家有所帮助,下一周大大要过来,意味着9点下班可能成为日常,是高兴呢还是高兴?求个,么么哒


转载:https://blog.csdn.net/L15519543837/article/details/109148159
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场