浏览器缓存机制--前端性能优化

一、总览

缓存一般有三种:

  1. 浏览器缓存
  2. 服务器缓存
  3. HTML5缓存

浏览器缓存机制--前端性能优化_第1张图片
本次要讨论的则是里面的浏览器缓存。

缓存是浏览器的一种机制,可以把请求过的web资源(html、css、js、图片等)拷贝一份副本存储在浏览器中,并根据请求配置选择是否使用该副本。缓存是浏览器中一种重要的并且简单高效的一种性能优化的方式。一个优秀的缓存策略起到以下作用:

1、缩短网页请求资源的距离,减少延迟,增加用户体验
2、缓存文件可以重复利用,还可以减少带宽,降低网络负荷。
3、降低服务器压力。

二、浏览器缓存规则定义位置

从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。

  1. Service Worker
  2. Memory Cache
  3. Disk Cache
  4. Push Cache

1、Service Worker

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。

当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。

2、Memory Cache

Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦关闭 Tab 页面,内存中的缓存也就被释放了。

那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢?
这是不可能的。计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。
内存缓存中有一块重要的缓存资源是preloader相关指令(例如)下载的资源。preloader的相关指令已经是页面优化的常见手段之一,它可以一边解析js/css文件,一边网络请求下一个资源。

需要注意的是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。

3.Disk Cache

Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,我们会在下文进行详细介绍。

浏览器会把哪些文件丢进内存中?哪些丢进硬盘中?
关于这点比较有争议,但是大概原则是这样的:

  • 对于大文件来说,大概率是不存储在内存中的,反之优先
  • 当前系统内存使用率高的话,文件优先存储进硬盘。

4.Push Cache

Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。

Push Cache 使用的很少,也不够普及,基本上有以下几种,可以了解一下

  • 所有的资源都能被推送,并且能够被缓存,但是 Edge 和 Safari 浏览器支持相对比较差
  • 可以推送 no-cache 和 no-store 的资源
  • 一旦连接被关闭,Push Cache 就被释放
  • 多个页面可以使用同一个HTTP/2的连接,也就可以使用同一个Push Cache。这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。
  • Push Cache 中的缓存只能被使用一次
  • 浏览器可以拒绝接受已经存在的资源推送
  • 你可以给其他域名推送资源
如果以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。

那么为了性能上的考虑,大部分的接口都应该选择好缓存策略,通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的

三、缓存过程

浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求,那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢?浏览器第一次向服务器发起该请求后拿到请求结果后,将请求结果和缓存标识存入浏览器缓存,浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

缓存分类

根据是否需要向服务器发送资源请求,分为 强缓存协商缓存。 强缓存意味着强制使用缓存,协商缓存意味着每用一次缓存都要协商一次。 强缓存和协商缓存都允许使用情况下,优先强缓存。

强缓存

不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。

强缓存的控制字段:

  • HTTP1.0:Expires
  • HTTP1.1 :Cache-Control

判断过程:请求再次发起 -> 浏览器根据 expirescache-control 判断目标资源是否命中"强缓存" -> 若命中,直接从缓存获取资源,不再与服务器发生通讯。

如果cache-controlexpires同时存在,以cache-control为主,继续使用 expires 的目的就是向下兼容。

Expire已经被 Cache-Control替代,原因在于 Expires依赖于本地时间,它控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,那么如果客户端与服务端的时间因为某些原因(例如时区不同;客户端和服务端有一方的时间不准确;用户修改了本地时间)发生误差,那么强制缓存则会直接失效。

Cache-Control取值:

在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:

public:所有内容都将被缓存(客户端和代理服务器都可缓存)

private:所有内容只有客户端可以缓存,Cache-Control的默认取值

no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定

no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存

max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效

must-revalidate: 强制浏览器严格遵守你设置的cache规则
proxy-revalidate: 强制proxy严格遵守你设置的cache规则
浏览器缓存机制--前端性能优化_第2张图片

协商缓存

强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那如何获知服务器端内容是否已经发生了更新呢?此时需要用到协商缓存策略。
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

  1. 协商缓存生效,返回304
  2. 协商缓存失效,返回200和请求结果。

协商缓存流程:浏览器缓存机制--前端性能优化_第3张图片
协商缓存的控制字段:

  • HTTP1.0:Last-Modified && If-Modified-Since
  • HTTP1.1 :Etag && If-None-Match
Last-Modified 与 If-Modified-Since

Last-Modified 是服务器响应请求时,返回该资源文件在服务器最后被修改的时间

流程:

  • 首次请求
  • 服务器告知启用协商缓存规则,并在响应头中带上 Last-Modified,告知缓存到期时间
  • 随后的每次请求,请求头上都会携带 If-Modified-Since,该值等于上一次响应头中的 Last-Modified 的值
  • 服务器收到 If-Modified-Since 后,会将该属性的值与服务器上资源的最后修改时间进行匹配,从而判断资源是否发生了变化
  • 如果发生变化会返回一个完整的响应内容,在响应头中添加新的 Last-Modified 值,否则,只返回 header 部分,状态码为304,响应头不会再添加 Last-Modified

弊端: Last-Modified 无法正确感知文件的变化,譬如说,文件的编辑时间修改了而内容没有修改,或者修改文件速度太快,几毫秒就改一次文件,If-Modified-Since 只能检测秒级的变化. 为了解决这个问题,Etag 作为 Last-Modified 的升级版,因时而生。 Etag 是通过标识字符串来辨别文件内容是否发生修改的,文件内容不一致才会生成新的标识字符串,这就弥补了 Last-Modified 时间戳的不足,通过 Etag 可以精准的感知文件的变化。

Etag && If-None-Match

Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成)

流程:

  • 首次请求
  • 服务器启用协商缓存情况下,会在响应头中带上 Etag
  • 随后每次请求,请求头上都会带上 If-None-Match,该值等于上一次响应头中的 Etag 的值
  • 服务器收到 If-None-Match 后,会进行比对,从而判断资源是否发生变化
  • 如果变化返回一个完整响应内容,在响应头上添加新的 Etag 值,否则返回 304,响应头不会在添加 Etag

弊端: Etag的生成需要服务器付出额外的开销,会影响服务端性能。

Etag 并不能替代 Last-Modified,只能作为 Last-Modified 的补充和强化存在,当 Etag 和 Last-Modified 同时出现时,以 Etag 为准。

整体流程

  1. 强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存。
  2. 若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存。

3.若协商缓存生效,那么代表该请求的缓存生效,返回304,继续使用缓存。

  1. 若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中。

浏览器缓存机制--前端性能优化_第4张图片

不能被缓存的请求

  1. HTTP信息头中包含Cache-Control:no-cache,pragma:no-cache(HTTP1.0),或Cache-Control:max-age=0等告诉浏览器不用缓存的请求
  2. 需要根据Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
  3. 经过HTTPS安全加密的请求
  4. POST请求无法被缓存
  5. HTTP响应头中不包含Last-Modified/Etag,也不包含Cache-Control/Expires的请求无法被缓存

四、缓存策略的应用场景

频繁变动的资源

Cache-Control: no-cache

对于频繁变动的资源,首先需要使用Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。

不常变化的资源

Cache-Control: max-age=31536000

通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。
在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。

五、用户操作行为与缓存

用户操作 Expires/cache-control last-Modified/Etag
地址栏回车 有效 有效
页面链接跳转 有效 有效
新开窗口 有效 有效
前进后退 有效 有效
F5刷新 无效 有效
Ctrl+F5强制刷新 无效 无效

主要有 3 种:

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

说明

以上部分内容来源与自己复习时的网络查找,也主要用于个人学习,相当于记事本的存在,暂不列举链接文章。如果有作者看到,可以联系我将原文链接贴出。

你可能感兴趣的:(浏览器缓存机制--前端性能优化)