Skip to content

Latest commit

 

History

History
executable file
·
205 lines (126 loc) · 13.1 KB

web缓存.md

File metadata and controls

executable file
·
205 lines (126 loc) · 13.1 KB

前言

在计算机技术中,缓存是一种特殊的存储技术,它可以存储计算机中的数据,以便快速访问。

回到浏览器,当浏览器加载页面的时候,会把一些静态资源(图片、css、js)缓存到本地,这样下次打开页面的时候,就不用再次请求服务器,而是直接从本地读取缓存。

一来节省了流量消耗,二来也减少了服务器的压力,三来提高首屏加载速度,优化用户体验。

而缓存比较关键的点是需要有一定的策略保证缓存的失效性,否则缓存的数据就会一直存在,导致数据不准确。

本文将从四个方面回答 web 缓存相关的问题:

  • 缓存的类型(强缓存、协商缓存)
  • 缓存的位置(Service Worker、Memory Cache、Disk Cache、Push Cache)
  • 缓存的过程
  • 缓存策略的实际运用

缓存的类型

强缓存

强缓存是利用 http 头中的 Expires 或 Cache-Control 两个字段来控制的,强缓存表示在缓存期间不需要请求,state code 为 200。

Expires(HTTP/1.0)

HTTP/1.0 引入了一些简单的缓存机制,其中最主要的就是通过服务器端的 Expires 头。当服务器返回资源时,通过设置 Expires 头,指定了资源的过期时间。浏览器在请求该资源时,会检查本地缓存,如果资源仍在有效期内,浏览器将直接使用缓存而不发出请求,从而减少了对服务器的访问。

Expires: Fri, 30 Dec 2022 23:59:59 GMT

上述头部表示资源的过期时间为 2022 年 12 月 30 日 23:59:59 GMT。在过期时间之前,浏览器将直接使用缓存,而不会向服务器发起请求。

然而,Expires 头存在一些问题和挑战。其中之一是时间戳的管理。如果服务器和浏览器的时钟不同步,或者用户在不同时区访问网站,可能导致缓存的失效时间不准确。 这可能导致浏览器不恰当地使用过期的缓存,或者在实际资源仍然有效时不使用缓存。

max-age 与 Cache-Control

HTTP/1.1 引入了 max-age, 相比于 Expires 头, max-age 使用的是相对时间,而不是绝对时间。

同时为了提供更灵活和细粒度的缓存控制, HTTP/1.1 还引入了 Cache-Control 头。Cache-Control 头提供了更多的指令和选项,使得服务器和浏览器可以更精确地定义缓存策略。

以下是一些常见的 Cache-Control 指令:

  • max-age: 指定资源的最大缓存时间,以秒为单位。例如,max-age=60 表示资源在 60 秒后过期。
  • no-store: 指示不应存储任何关于客户端请求和服务器响应的内容。每次都需要重新获取完整的响应。
  • no-cache: 表示缓存必须重新验证资源的有效性,即需要向服务器发送请求确认资源是否过期。
  • public: 表示响应可以被任何缓存存储,包括代理服务器。
  • private: 表示响应仅能被单个用户缓存,不允许代理服务器缓存。

下面是 Cache-Control: max-age 和 Expires 的对比:

特性 Cache-Control: max-age Expires
时间表示方式 相对时间(从请求时间开始计算的秒数) 绝对时间(指定的日期和时间)
示例 Cache-Control: max-age=3600 Expires: Wed, 21 Oct 2023 07:28:00 GMT
优先级 如果两者同时存在,具有更高优先级 如果存在Cache-Control: max-age,则被忽略
灵活性 提供各种缓存控制指令 相对较简单,只指定过期日期/时间
常见用途 现代、更灵活 较老的方法,使用较少

这里插个题外话,浏览器的硬刷新(hard reload)和普通刷新(refresh)都使用了 HTTP 请求头中的 Cache-Control 头,但是具体的指令值有所不同。

  1. 硬刷新(Hard Reload):
  • 请求头: Cache-Control: no-cache 或者 Cache-Control: no-store。

  • 作用: 这样的请求头告诉服务器不要使用缓存,每次都需要从服务器获取最新的资源。no-cache 表示需要服务器验证资源是否过期,而 no-store 表示不应存储任何关于客户端请求和服务器响应的内容,每次都需要重新获取完整的响应。硬刷新的目的是强制浏览器忽略缓存并从服务器获取最新的资源。

  1. 普通刷新(Refresh):
  • 请求头: 普通刷新一般使用默认的请求头,不会明确指定 Cache-Control 头,因此可能是默认的缓存行为。
  • 作用: 浏览器可能根据缓存策略(例如 Cache-Control 头中的设置)来判断是否使用缓存。如果资源在缓存有效期内,浏览器可能直接使用缓存而不重新请求。

协商缓存

与强缓存不同,协商缓存并不直接使用本地缓存,而是通过与服务器的交互,检查资源是否已经发生变化。

以下是协商缓存的基本原理和相关头部:

Last-Modified 和 If-Modified-Since:

  • 服务器行为: 当服务器返回资源时,会附带一个 Last-Modified 头,该头包含了资源的最后修改时间。
  • 浏览器行为: 浏览器在后续请求该资源时,会在请求头中包含一个 If-Modified-Since 头,该头的值是上一次获取资源时服务器返回的 Last-Modified 值。
  • 验证过程: 服务器收到请求后,会检查 If-Modified-Since 的值与当前资源的最后修改时间是否一致。如果一致,表示资源未发生变化,服务器返回 304 Not Modified 状态码,告诉浏览器可以使用缓存。否则,服务器返回新的资源和状态码 200 OK。

ETag 和 If-None-Match:

  • 服务器行为: 服务器可以为每个资源生成一个唯一的标识符,称为 ETag(实体标签)。当返回资源时,服务器会包含一个 ETag 头。
  • 浏览器行为: 浏览器在后续请求该资源时,会在请求头中包含一个 If-None-Match 头,该头的值是上一次获取资源时服务器返回的 ETag 值。
  • 验证过程: 服务器收到请求后,会比较 If-None-Match 的值与当前资源的 ETag 是否一致。如果一致,表示资源未发生变化,服务器返回 304 Not Modified 状态码。否则,服务器返回新的资源和状态码 200 OK。

下面是 Last-Modified 和 ETag 的对比:

特性 Last-Modified ETag
信息类型 时间戳(表示资源最后修改时间) 实体标签(表示资源的唯一标识符)
示例 Last-Modified: Fri, 20 Dec 2019 10:30:45 GMT ETag: "abc123"
验证机制 If-Modified-Since 头部用于条件 GET 请求 If-None-Match 头部用于条件 GET 请求
精确性 受秒精度限制,可能无法捕获细小的修改 高精度,可以准确捕获任何资源变化
性能开销 依赖于文件系统或服务器支持的最后修改时间 更灵活,不依赖文件系统,可以通过哈希等生成
常见用途 静态资源或文件修改较慢的场景 动态生成内容或需要更精确验证的场景

缓存的位置

上文提到,在命中了 强缓存 或者服务器返回了 304 之后, 要浏览器从缓存中过去资源, 从优先级上说,浏览器会优先从以下四个地方(优先级从高到低)查找缓存:

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

Service Worker

Service Worker 是一种在浏览器背后运行的 JavaScript 脚本,它允许开发者拦截和处理网页发起的网络请求,实现离线缓存、推送通知和后台同步等功能。

简单来说,Service Worker 有如下特征:

  • 独立线程: Service Worker 在主线程之外运行,独立于页面。这使得它可以在页面不活动或关闭的情况下继续运行,实现后台任务。
  • 网络代理: Service Worker 充当网络请求的代理,可以拦截请求并根据开发者定义的逻辑进行处理,从缓存中返回数据或者向服务器发起请求。
  • 离线缓存: 开发者可以利用 Service Worker 实现离线缓存,使得用户在离线状态下依然能够访问应用的核心资源。
  • 安全性: Service Worker 受到安全限制,必须在使用 HTTPS 协议的网站上才能使用,以防止恶意代码利用其强大的功能。

Memory Cache

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

Memory Cache 的特点是:

  • 读取效率高,但是持续时间短,会随着进程的释放而释放(一旦关闭 Tab 页面就会被释放,甚至有时候没关闭前,排在前面的缓存就已经失效了)
  • 几乎所有的请求资源都能进入 memory cache,细分来说主要分为 preloaderpreload 这两块
  • 在从 memory cache 读取缓存时,浏览器会忽视 Cache-Control 中的一些 max-age、no-cache 等头部设置,除非设置了 no-store 这个头部设置

Disk Cache

将资源文件存储在计算机的硬盘上,以便在后续访问相同资源时可以更快地获取。

与 Memory Cache 的对比:

特征 Memory Cache Disk Cache
访问速度 相对较慢
容量 较小,用于短期缓存 较大,适用于长期缓存
存储位置 内存 硬盘
持久性 会随着会话结束而清空 具有相对较长的持久性,可以跨会话
使用场景 频繁访问的核心资源,短期缓存 相对较大、不频繁更改的资源,长期缓存

Push Cache

Push Cache 是一种缓存机制,通常与 HTTP/2 或 HTTP/3 协议一同使用。它允许服务器在收到浏览器请求时,主动推送资源到浏览器,而浏览器可以将这些资源缓存到 Push Cache 中。

Push Cache 通常具有较低的优先级,只有在其他缓存位置未找到匹配的资源时才会考虑使用。另外需要注意使用 Push Cache 需要确保服务器的支持,并合理配置推送的资源。

主要特征如下:

  • 服务器推送: Push Cache 是在服务器推送资源给浏览器时使用的缓存。服务器可以通过 HTTP/2 或 HTTP/3 协议向浏览器推送一些可能在未来页面加载中需要的资源。

  • 低优先级: Push Cache 通常具有较低的优先级,仅在其他缓存位置未找到匹配的资源时才会考虑使用。

  • 独立于页面请求: Push Cache 是独立于页面请求的,即使页面没有请求相应的资源,服务器也可以推送资源到浏览器的 Push Cache 中。

  • 仅存储推送过来的资源: Push Cache 主要用于存储由服务器推送过来的资源,而不是存储通过页面请求获取的资源。

浏览器缓存过程

浏览器缓存过程可以分为以下关键步骤:

  1. 初次请求:
  • 浏览器发起 HTTP 请求,检查本地缓存是否有相应的结果和缓存标识。
  • 若未在浏览器缓存中找到缓存结果和标识,向服务器发起 HTTP 请求。
  1. 服务器响应:
  • 服务器返回请求结果以及缓存规则,通常使用 Last-Modified 或 ETag 标识资源的最后修改时间或实体标签。
  1. 缓存存储:
  • 浏览器将响应内容存入 Disk Cache(硬盘缓存),并将响应内容的引用存入 Memory Cache(内存缓存)。
  • 如果页面中使用了 Service Worker,并且 Service Worker 的脚本中调用了 cache.put() 方法,则将响应内容存入 Service Worker 的 Cache Storage。 后续请求:
  1. 下一次请求相同资源时,浏览器经过以下判断:
  • 调用 Service Worker 的 fetch 事件响应。
  • 查看 Memory Cache,如果存在且未过期,直接返回缓存内容。
  • 查看 Disk Cache
    • 若有强缓存并且未失效,则使用强缓存,不请求服务器,状态码为 200。
    • 若有强缓存但已失效,使用协商缓存,向服务器发送请求,根据服务器的响应状态码(304 或 200)决定是否返回新的内容。

缓存策略的实际使用场景

不常变化的资源

对于不常变化的资源:

Cache-Control: max-age=31536000

通常给 Cache-Control 设置一个很大的值(比如一年),但是有时候为了解决更新问题,我们需要在文件上添加一个 hash,这样就达到了更改引用 URL 的目的。

常变化的资源

对于经常变化的资源:

Cache-Control: no-cache

我们可以不使用强缓存,每次都向浏览器发送请求,然后配合 ETag 或者 Last-Modified 来验证资源缓存是否有效。

本节对应的代码在_demo/browser-cache目录下。