前端面试常备06:浏览器缓存

1. 浏览器缓存过程

浏览器与服务器通信的过程是一种应答模式: 浏览器发起一个请求, 服务器根据请求中参数返回结果. 缓存可以减少数据传输量, 提高访问效率. 浏览器第一次向服务器请求后拿到请求结果, 会根据响应报文中的 HTTP 头的缓存标识, 决定是否缓存结果. 具体流程如下:

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

这两点保证了每个请求的缓存存入和读取, 只要在理解浏览器缓存的使用规则, 那么所有问题就清晰了. 根据是否需要向服务器重新发起 HTTP 请求, 可以将缓存过程分为两部分, 分别是强缓存协商缓存.

2. 浏览器缓存机制

浏览器缓存策略分为强缓存和协商缓存, 其请求缓存的过程如下:

  1. 浏览器在加载资源时, 会根据请求头的expirescache-control判断是否命中强缓存, 如果命中, 直接从缓存中读取资源, 不发请求到服务器.
  2. 如果没有命中强缓存, 则发送一个请求到服务器, 通过last-modifiede-tag验证资源是否命中协商缓存, 如果命中, 服务器会将这个请求返回, 但是不会返回这个资源的数据, 依然是从缓存中读取资源.
  3. 如果协商缓存也没有命中, 则直接从服务器加载资源.

两种缓存的相同点在于如果命中, 都是从客户端加载资源, 而不是从服务器加载资源, 不同在于强缓存不发送请求到服务器, 而协商缓存会发送一个请求到服务器.

下面来解释响应头的参数细节:

2.1 强缓存

ExpiresCache-Control的区别在于前者是http1.0的协议, 后者是http1.1的协议, 并且后者的优先级高于前者.

2.1.1 Expires

Expires 是一个表示资源过期的 header, 描述一个绝对时间, 由服务器返回, Expires 受限于本地时间, 修改本地时间会造成缓存失效.

Expires: Wed, 11 May 2018 07:20:00 GMT
复制代码

2.1.2 Cache-Control

Cache-Control这个首部是可选的, 并且可以用于请求以及响应时.

http1.1中, Cache-Control是最重要的规则, 用于控制网页缓存, 取值如下:

  • public: 所有内容都被缓存(客户端和代理服务器都可缓存)
  • private(默认值): 所有内容只有客户端可以缓存, Cache-Control
  • no-cache: 字面意思是不要缓存, 实际上的机制是, 仍然对资源使用缓存, 但是每一次在使用缓存之前必须(MUST)想服务器对缓存资源进行验证. 也就是进行协商缓存
  • no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存.
  • max-age=xxx (xxx is numeric):缓存内容将在 xxx 秒后失效, 缓存资源如果时间比指定的时间数值小, 那么客户端就直接接受花村的资源. 如果指定为0, 那么通常需要直接请求服务器
  • min-fresh=60(单位: 秒):要求缓存服务器返回至少还未过指定时间的缓存资源. 比如这里就是要求60秒内不会过期的资源返回。
  • s-maxage(单位为 s):同 max-age 作用一样,只在代理服务器中生效(比如 CDN 缓存)。比如当 s-maxage=60 时,在这 60 秒中,即使更新了CDN的内容,浏览器也不会进行请求。max-age 用于普通缓存,而 s-maxage 用于代理缓存。s-maxage 的优先级高于 max-age。如果存在 s-maxage,则会覆盖掉max-ageExpires header
  • must-revalidate: 如果配置了max-age信息, 当资源小于max-age的时候使用使用缓存, 否则需要对资源进行验证.
  • max-stale=3600(单位: 秒): 可指示缓存资源, 即便是过期也照常接收.
  • only-if-cached: 表示客户端仅在仅在缓存服务器本地缓存目标资源的情况下才会要求其返回. 换句话说, 该指令要求缓存服务器不重新加载响应, 也不会重新确认资源的有效性.
  • proxy-revalidate: 要求所有的缓存服务器在接收到客户端带有该指令的请求返回响应之前, 必须再次验证缓存的有效性
  • no-transform: 规定无论是在请求还是响应中, 缓存都不能改变实体主体的媒体类型

2.1.3 强缓存的流程示意

2.2 协商缓存

当强缓存没有命中的时候, 浏览器就会发送一个请求到服务器, 验证协商缓存是否命中, 如果协商缓存命中, 请求响应返回的 http 状态为 304 并且会显示一个Not Modified的字符串.

协商缓存是利用Last-Modified, If-Modified-SinceETag, If-None-Match两组 Header 来管理的,

2.2.1 Last-Modified, If-Modified-Since

Last-Modified表示本地文件最后修改日期, 浏览器会在request header加上If-Modified-Since(上次返回的Last-Modified的值), 询问服务器在该日期后资源是否有更新, 有更新则将新的资源发送回来.
这个参数的缺陷在于如果在本地打开缓存文件就会造成Last-Modified被修改. 因此在http1.1中出现了ETag.

2.2.2 ETag、If-None-Match, If-Match

Etag是服务器根据具体的资源生成的一个哈希值, 用来作为资源的一个唯一标识. 资源的变化会导致ETag发生变换, 跟最后修改时间没有关系,

If-None-Match的 header 会将上次返回的ETag发送给服务器, 询问该资源的ETag是否更新, 有变动就会发送新的资源回来.

If-Match会告知服务器匹配资源所用的实体标记, 这时服务器无法使用弱Etag值, 会对比If-Match字段值和资源的ETag, 仅当两者一致的时候, 才会执行请求, 否则返回412 Precondition Fialed, 也可以使用”*”来忽略该值

ETag的优先级比Last-Modified更高, 主要出于以下的几种考虑:

  1. 周期性的文件修改, 即仅仅是修改了时间, 内容没有变动, 这种时候我们希望客户端能从缓存读取
  2. 原有的Expires只能控制到秒级的文件修改频率, 在 1s 内如果修改了 N 次, 则无法判断修改了多少次.
  3. 某些服务器不能精确的得到文件的最后修改时间(?)

2.2.3 强ETag 和 弱ETag

  • 强 ETag: 无论实体发生多么细微的变化都会改变其值
  • 弱 ETag: 只是用于提示资源是否相同, 只有资源发生了根本变化, 产生差异的时候才会改变ETag的值, 此时, 会在字段值的开始附加W/

2.3 浏览器状态码

  • 200: 强缓 Expires/Cache-Control 存失效时,返回新的资源文件
  • 200(from disk cache(磁盘缓存)/from memory cache(内存缓存)): 强缓Expires/Cache-Control两者都存在,未过期,Cache-Control优先Expires时,浏览器从本地获取资源成功
  • 304(Not Modified ):协商缓存Last-modified/Etag没有过期时,服务端返回状态码 304

2.4 启发式缓存

  • 没有任何关于缓存的字段–不设置任何缓存策略
  • 通常会去响应头中的Date减去Last-Modified值的10%作为缓存时间

2.4 实际场景

大致顺序如下:

  1. Cache-Control —— 请求服务器之前
  2. Expires —— 请求服务器之前
  3. If-None-Match (Etag) —— 请求服务器
  4. If-Modified-Since (Last-Modified) —— 请求服务器

协商缓存需要配合强缓存使用,如果不启用强缓存的话,协商缓存根本没有意义

大部分 web 服务器都默认开启协商缓存,而且是同时启用[Last-Modified,If-Modified-Since][ETag、If-None-Match]

但是下面的场景需要注意:

分布式系统里多台机器间文件的 Last-Modified 必须保持一致,以免负载均衡到不同机器导致比对失败;
分布式系统尽量关闭掉 ETag(每台机器生成的 ETag 都会不一样);

2.5 小结

总体缓存请求的流程如下:

3. 浏览器缓存位置

在上一节的最后部分, 有提到 200 状态码的不同缓存读取位置. 这节中, 来梳理一下相关的知识点.

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

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

3.1 Service Worker

Service workers 本质上充当 Web 应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步 API。 — MDN DOCS

关于 Service Worker 的详细介绍, 可以另外整理一篇文章来介绍其使用和原理. 这里我们简单的提及一些. (挖坑)

Service Worker因为涉及到请求拦截, 所以必须使用 HTTPS 协议来保障安全, Service Worker可以自由控制缓存哪些文件, 如何匹配缓存, 如何读取缓存并且建立可持续的缓存.

Service Worker实现缓存大致分为三步:

  1. 注册Service Worker,
  2. 监听install事件
  3. 缓存需要的文件, 那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存, 存在缓存的话就可以直接读取缓存文件, 否则就去请求数据.

Service Worker没有命中缓存, 我们需要调用fetch函数去获取数据, 也就是说, 如果没有在Service Worker中获取缓存, 就会根据浏览器的缓存策略去进行缓存命中, 但此时, 无论我们从何处获取到数据, 浏览器都会显示我们是从Service Worker中获取的内容.

3.2 Memory Cache

Memory Cache意为内存缓存, 主要包含当前页面中已经抓取到的资源, 比如页面上已经下载的 css, js, 图片等. 内存缓存读取速度快, 但是持久性第, 会随着进程的释放而释放. 一旦我们关闭 tab 页面, 内存中的缓存就被释放了.

内存缓存中有一块重要缓存资源时preload相关指令下载的资源, 这是一种常见的页面优化手段, 可以一边解析 js/css 文件, 一边请求下一个资源.

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

3.3 Disk Cache

Disk Cache为硬盘缓存, 读取速度慢, 容量大, 时效长, 用途广.

在所有的缓存中, 该缓存应用最广泛, 也是我们在第 2 节中缓存策略的主要存储位置. 对于什么文件存在Memory Cache, 什么文件件存在Disk Cache, 主要由浏览器的策略决定, 大致逻辑可能有:

  • 对于大文件, 大概率不保存在内存中, 反之优先
  • 当内存使用率比较高时, 文件优先存储在硬盘中.

3.4 Push Cache

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

这里有几个大概的结论以供了解(来源):

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

4. 实际场景和用户行为

  1. 频繁变动的资源: Cache-Control: no-cache
  2. 不常变化的资源: Cache-Control: max-age=31536000

用户行为主要有三种:

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

参考链接

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