剖析浏览器缓存

前言

永远把别人对你的批评记在心里,别人的表扬,就把它忘了。Hello 大家好~!我是南宫墨言QAQ

浏览器缓存是指浏览器在访问网页时将一些页面资源(如图像、样式表、脚本文件等)存储在本地计算机上的临时存储区域。当用户再次访问同一网页时,浏览器会首先检查缓存,如果找到了相应的资源,则直接从缓存中加载,而不是再次从服务器下载

缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷

本文将从缓存类型、缓存位置、缓存过程、缓存策略以及实际场景应用等方面来剖析浏览器缓存机制

image.png

浏览器缓存机制

观看到文章最后的话,如果觉得不错,可以点个关注或者点个赞哦!感谢~❤️

缓存类型

缓存类型可以分为强缓存和协商缓存。强缓存是不需要发送HTTP请求,而协商缓存是需要发送HTTP请求的。也就是说,在发送HTTP请求之前,浏览器会先检查强缓存,如果命中就直接返回,否者进入协商缓存阶段

强缓存

不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回 200 的状态码,并且Size显示from disk cachefrom memory cache

浏览器检查强缓存主要是判断这两个字段:Expires(有效期,HTTP/1.0的产物)和 Cache-Control(缓存管理,HTTP/1.1的产物)

Expires

Expires表示缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点,存在于服务端返回的响应头中,告诉浏览器在这个过期时间之前可以直接从缓存里面获取数据,无需再次请求。比如下面这样:

Expires: Tue Jul 11 2023 18:00:00 GMT+0800

表示这个资源在2023年7月11日18点过期,到了过期时间就需要重新向服务器请求

但是,当服务器时间和浏览器时间不一致(比如手动修改本地时间),那么就会导致缓存失效。因为这个缺陷,所以在HTTP1.1中被抛弃了

Cache-Control

抛弃了Expires之后, HTTP/1.1采用了Cache-Control这个重要的规则,它和Expires本质的不同在于它并没有采用具体的过期时间点这个方式,而是采用过期时长来控制缓存,对应的字段是max-age

Cache-Control:max-age=3600

代表这个响应返回后在 3600 秒,也就是一个小时之内可以直接使用缓存

Cache-Control不仅仅有max-age 这一个属性, 其实它有很多的用法, 你甚至可以采用组合的方式:

Cache-Control: public, max-age=3600

上面?用法的意思是响应可以被任何对象(客户端, 代理服务器等)缓存, 且过期时长为1小时

因为一个请求经历的不仅仅是客户端(浏览器)和目标服务器,它中间有可能会经过不同的代理服务器,所以我们通过组合不同指令限制其使用场景,下面列举下Cache-Control中常用的指令:

image.png

Cache-Control指令

public 表示客户端和代理服务器都可以缓存。响应可以被中间任何的一个节点缓存,在Browser -> proxy1 -> proxy2 -> Server这个请求过程中,中间的代理(proxy)可以缓存资源,当Browser再次请求这个资源的时候,浏览器就会直接到proxy1中拿缓存的东西而不必再经过proxy2去取得

private 表示响应只可以被客户端缓存,是Cache-Control得默认取值。响应不允许中间节点缓存,在Browser -> proxy1 -> proxy2 -> Server这个请求过程中,中间的代理(proxy)不可以缓存资源,当Browser再次请求这个资源的时候,proxy会把Server返回的数据发送给Browser,做好请求转发,而不是自己缓存数据

max-age 表示缓存的资源在多久之后过期。max-age=3600表示在1小时后缓存的资源失效,需要重新发起HTTP请求

s-maxage 覆盖max-age,作用一样,但是它只在代理服务器中生效,且s-maxage的优先级高

no-store 表示资源都不会被缓存,既不进行强缓存,也不进行协商缓存

no-cache 表示不进行强缓存验证,发送HTTP请求,直接进入协商缓存阶段

max-stale 表示能容忍的最大过期时间。max-stale=30表示30秒内,即使缓存过期,客户端也使用该缓存

min-fresh 表示能容忍的最小新鲜度。min-fresh=30表示希望在30秒内回去最新的响应

基于以上这些指令,我们可以对其进行不同的组合以达到不同的目的,实现不同的效果

image.png

Cache-Control

从图中可以看出,我们可以基于这些指令进行不同的组合,以达到不同的目的,实现不同的效果。

当然,还存在一种情况,当资源缓存时间超时了,也就是强缓存失效了,就可以进入第二级屏障 – 协商缓存

协商缓存

会向服务器发起请求的缓存。协商缓存就是在强缓存失效的情况下,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否是使用缓存的过程,主要有以下两种情况:

  • 协商缓存生效,返回304和 Not Modified
    image.png

协商缓存生效

  • 协商缓存失效,返回200和请求结果
    image.png

协商缓存失效

上面提到的缓存标识有两个:Last-ModifiedETag,这两者各有优劣,并不存在谁对谁有绝对的优势,下面将对这两个标识进行剖析

Last-Modified

Last-Modified表示的是资源的最后修改时间,这也是协商缓存判断方式的一种,它会配合If-Modified-Since字段使用

使用Last-Modified进行协商缓存会经过以下几个步骤:

image.png

协商缓存Last-Modified

  1. 浏览器第一次向服务器请求这个资源
  2. 服务器在返回这个资源的时候,会在response header中添加Last-Modified这个标识, 值为该资源在服务器上最后的修改时间
  3. 浏览器接收到缓存资源和header
  4. 当下次浏览器再次请求这个资源的时候,检测到有Last-Modified这个标识,就会在请求头中添加If-Modified-Since这个标识,值为Last-Modified
  5. 服务器再次接收到该资源的请求,则根据If-Modified-Since与服务器中的这个资源的最后修改时间做对比
  6. 对比结果相同则返回304和一个空的响应体,告诉浏览器从自己的浏览器缓存中取
  7. 对比结果不同(If-Modified-Since小于服务器资源最后修改时间),则表示资源被修改了,那么就返回200和最新的资源,当然包括最新的Last-Modified

但是Last-Modified存在一些弊端

  • 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成Last-Modified被修改,服务端不能命中缓存导致发送相同的资源
  • 因为Last-Modified只能以秒计时,如果某个文件在1秒内被修改了很多次,那么这时候的Last-Modified并没有体现出修改了,此时服务端不会返回正确的资源

既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略呢?所以在HTTP/1.1的时候出现了ETagIf-None-Match

ETag

ETagLast-Modified的原理差不多,不过它不是根据资源的最后修改时间来判断的,而是通过一个唯一的标识,这个标识是根据当前的文件内容生成的,只要里面的内容有改动,这个值就会改变,它会配合If-None-Match字段使用

使用ETag进行协商缓存会经过以下几个步骤:

image.png

协商缓存ETag

  1. 浏览器第一次向服务器请求这个资源
  2. 服务器在返回这个资源的时候,会在response header中添加ETag这个标识
  3. 浏览器接收到缓存资源和header
  4. 当下次浏览器再次请求这个资源的时候,浏览器会在请求头中添加If-None-Match,值为刚刚缓存的ETag标识
  5. 服务器再次接收到该资源的请求,则根据If-None-Match与服务器中的该资源自身做对比
  6. 对比结果相同则返回304,告诉浏览器从自己的浏览器缓存中取
  7. 对比结果不同,则表示资源被修改了,则表示资源被修改了,那么就返回200和最新的资源,当然包括最新的ETag

两者之间对比

  • 在精度上,ETag要优于Last-Modified
    Last-Modified的时间是秒,如果某个文件在1秒内被修改了很多次,那么这个时候Last-Modified并没有体现出修改了,但ETag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致

  • 在性能上,ETag要逊于Last-Modified,毕竟Last-Modified,只需要记录时间,而ETag则需要服务器通过算法来计算出一个hash值

  • 在优先级上,服务器校验优先考虑 ETag

总结协商缓存:准确度上ETag更强;性能上Last-Modified更好;两者都支持的话,ETag优先级更高

缓存位置

在上面已经介绍完了缓存的类型,之前说过,如果命中了强缓存或者服务器返回了304之后,要浏览器从缓存中获取资源,那么这些缓存具体是存储在哪里呢?

从优先级上依次划分为以下四种:Service WorkerMemory CacheDisk CachePush Cahe,下面将会对这四种缓存位置进行剖析

Service Worker

Service Worker是运行在浏览器背后的独立线程,由于它脱离了浏览器的窗体,所以无法直接访问DOM。虽然如此,但它仍然能帮助我们完成很多有用的功能,比如离线缓存消息推送网络代理等功能。其中离线缓存就是Service Worker Cache

简单的来说,它具有以下几个特点:

  • 借鉴了Web Worker的思路
  • 使用Service Worker的话,传输协议必须为HTTPS,因为Service Worker中涉及到请求拦截,所以需要用HTTPS协议来保证安全
  • 与浏览器其它内建缓存机制不同,它可以让我们自由的控制缓存哪些文件,如何匹配缓存、如何读取缓存,并且缓存是持续性的
  • Service Worker是PWA的重要实现机制

Memory Cache

Memory Cache是内存中的缓存,主要存储的是当前页面已经抓取到的资源,比如页面上已经下载的样式、脚本、图片等

简单的来说,它具有以下几个特点:

  • 读取效率快,单缓存持续时间短,会随着进程的释放而释放(一旦关闭Tab页面,就被释放了,还有可能在没有关闭之前,排在前面的缓存就失效了,比如一个页面的缓存占用了超级多的内存)
  • 几乎所有的请求资源都能进入memory cache,细分来说主要分为preloader和preload
  • 从memory cache度去缓存时,并不关心返回资源的HTTP缓存头Cache-Control中max-age、no-cache等头部配置,除非设置了no-store,同时资源的匹配也并非仅仅是对URL做匹配,还可能对Content-Type、CORS等其他特性做校验

preloader

preloader是页面优化的常见手段之一,她的作用主要是用于在浏览器打开一个网页的时候,能够一边解析执行js/css,一边去请求下一个资源,而这些被preloader请求来的资源就会被放入memory cache中,供之后的解析执行操作,比如

preload

preload与preloader仅两个字母之差,他能显示指定预加载的资源,这些资源也会被放进memory cache中,比如

Disk Cache

Disk Cache也叫做HTTP Cache,是存储在硬盘上的缓存,所以它是持久存储,是实际存在于文件系统的

从存储效率上说,它比内存缓存慢,但是优势在于什么都能存储,存储容量更大,并且存储时长更长

在所有浏览器缓存中,Disk Cache是覆盖面积最大的,它会根据前面我们提到的HTTP header中的缓存字段来判断哪些资源需要缓存,哪些资源不需要请求直接使用,哪些已经过期了需要重新请求获取

若是命中了缓存之后,浏览器会从硬盘中直接读取资源,虽然没有从内存中读取的快,但比网络缓存来的快

强缓存和协商缓存也是属于Disk Cache,他们最终都存储在硬盘里

Memory CacheDisk Cache各有优劣,那么浏览器如何决定将资源放进内存还是硬盘呢,主要策略如下:

  • 对于大文件来说会被丢到硬盘中存储,反之则存储在内存中
  • 当前系统使用率高的话,文件有限存储进硬盘

Push Cahe

Push Cahe(推送缓存),它是浏览器缓存的最后一道防线,当以上三种缓存都没有命中的时候,它才会被使用

它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存的时间也很短暂,在Chorome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令

由于它是Http/2中的内容,因此在国内不是很普及,这里贴上一个比较好的总结:

  • 所有的资源都能被推送,并且能够被缓存,但Edge和Safari浏览器支持相对比较差
  • 可以推送no-cache和no-store的资源
  • 一旦链接被管理,Push Cahe就被释放
  • 多个页面可以使用同一个HTTP/2的连接,也就可以使用同一个Push Cahe。主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的tab标签使用同一个HTTP连接
  • Push Cahe的缓存只能被使用一次
  • 浏览器可以拒绝接受已经存在的资源推送
  • 你可以给其他域名推送资源

缓存过程

根据上面的介绍,我们知道了缓存类型和缓存位置,那么浏览器具体的缓存行径是怎么样的呢?

浏览器与服务器通信方式为应答模式,即浏览器发起HTTP请求,服务器响应该请求。那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢?

浏览器发起HTTP请求到获得请求结果的过程可以分为以下几个步骤:
image.png

HTTP请求过程

  1. 浏览器第一次发起HTTP请求,在浏览器缓存中没有发现请求的缓存结果和缓存标识
  2. 因此向服务器发起HTTP请求,获得该请求的结果还有缓存规则(Last-ModifiedETag
  3. 浏览器把响应内容存入Disk Cache,把响应内容的引用存入Memory Cache
  4. 把响应内容存入Service WorkerCache Storage(如果Service Worker的脚本调用了cache.put()

下一次请求相同资源的时候:

  1. 调用Service Workerfetch事件响应
  2. 查看memory cache
  3. 查看disk cache,这里细分为:
  • 有强缓存且未失效,则使用强缓存,不请求服务器,返回的状态码都是200
  • 有强缓存且已失效,使用协商缓存判断,是304还是200(读取缓存还是重新获取)

缓存机制

强制缓存优先于协商缓存进行,若强者缓存(ExpiresCache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified/If-Modified-SinceETag/If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200、资源和缓存标识,再存入浏览器混吃中,生效则返回304,继续使用缓存,具体流程如下图:

image.png

缓存的机制

如果什么缓存策略都没设置,那么浏览器会怎么处理呢?

对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的Date减去Last-Modified值的10%作为缓存时间

缓存策略的实际应用场景

在实际使用上来说,我们怎么使用缓存策略呢?对此我们可以针对资源是否频繁变动进行剖析

频繁变动的资源

对于频繁变动的资源,首先需要使用Cache-Control: no-cache

Cache-Control: no-cache

这样使得浏览器每次都请求服务器,然后配合ETag或者Last-Modified来验证资源是否有效。

这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小

非频繁变动的资源

对于非频繁变动的资源,我们会给Cache-Control: max-age=3153600

Cache-Control: max-age=3153600

通过给Cache-Control配置一个很大的max-age,这样子浏览器之后请求相同的URL会命中强缓存。而为了解决更新的问题,就需要在文件名或路径中添加hash、版本号等动态字符,之后通过更改动态字符,就能达到更改引用URL的目的,让之前的强制缓存失效(其实并没有立即失效,只是不再使用了而已)

用户行为对浏览器缓存的影响

胡勇行为对浏览器缓存的影响值得是用户在浏览如何操作时,会触发怎样的缓存策略,主要行为有三种:

  1. 打开网页,地址栏输入地址:查找disk cache中是否有匹配,如果有就使用,反之就发送网络请求
  2. 普通刷新(F5):AB并没有关闭,因此memory cache是可用的,如果匹配的话就会被优先使用,其次才轮到disk cache
  3. 强制刷新(Crl + F5):浏览器不使用缓存,因此发送的请求头部均带有Cache-Control: no-cache(为了兼容,还带了Pragma: no-cache),服务器直接返回200和最新内容

参考文章

深入理解浏览器的缓存机制

霖呆呆你来说说浏览器缓存吧

(1.6w字)浏览器灵魂之问,请问你能接得住几个?

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY8kHeVW' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片