浏览器缓存策略

211 阅读5分钟

浏览器缓存策略

首先,浏览器缓存主要分为两种:

  1. 强缓存
  2. 协商缓存

先判定 强缓存,然后判定 协商缓存

缓存位置

首先,我们来看一下缓存之后的资源存在哪里。

我们浏览器支持以下的缓存位置:

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

Disk Cache 和 Memory Cache

我们来详细看一下 Disk Cache 和 Memory Cache 的共同点和不同点:

Memory CacheDisk Cache
存储位置存储在 RAM 中,访问速度快存储在磁盘中,适合较为长期的存储
存储时长仅在当前会话有效,进程退出后被清除跨会话有效,资源到期前保留
读取性能非常快较慢
优点高速响应 适合需要频繁读取的资源节省内存空间,适合长期缓存和大文件
缺点易被清除,关闭浏览器进程就会退出访问速度慢,需要 IO 开销
缓存适用的资源类型JS、字体、小图片等CSS、大图片、音视频、静态 HTML 等资源

为什么 js 存在 memory cache,css 存在disk cache 呢?

答:因为 css 文件加载一次就可以渲染出来,我们不会进行频繁的读取,所以不适合存在 memory cache 中。但是 js 脚本有可能随时被读取,如果在 disk cache 中的话,会导致 IO 开销很大,从而导致浏览器可能失去响应。

这些都是浏览器进行分类(根据使用频率,资源类型,资源大小,生命周期等)管理的,但是我们通常认为 不需要进行频繁读取的资源 倾向于存在 Disk Cache

缓存命中优先级

  1. 优先查找 Memory Cache;
  2. 若没有找到,查找 Disk Cache;
  3. 若没有找到,进行网络请求。

强缓存

当请求资源的时候,资源是之前请求过并使用强缓存,那么不会向服务器端再次发送资源请求,浏览器直接通过缓存(根据缓存优先级)获取资源(不管资源有没有进行过改动),从而加快了资源加载的速度。

是否命中强缓存主要依据 header 中的两个字段:expiresCache-Control

Expires

这个字段主要是 http1.0 的规范,它是一个 绝对时间 的时间字符串,比如:Expires:Mon,18 Oct 2066 23:59:59 GMT 。这个时间代表资源失效的时间,在这个时间之前,会命中强缓存。

这个方式的缺点是:因为是绝对时间,如果服务端和客户端时间偏差大的时候,会导致内存混乱。

Cache-Control

这个字段是 http1.1 出现的 header 信息,主要是根据这个字段中的 max-age 值进行判断,它是一个 相对时间,比如:Cache-Control:max-age=3600,代表资源有效时间是 3600 秒。除了这个字段,Cache control 还拥有以下较为常用的字段:

  • No-cache: 需要进行协商缓存。
  • No-store: 禁止使用缓存。
  • Public: 可以被所有用户缓存,包括终端用户和 CDN 等中间代理器。
  • Private(默认): 只允许终端用户缓存,不允许 CDN 等中间代理器缓存。

可以在服务端同时启用 Expires 和 Cache-Control。Cache Control的优先级高于 expires。

协商缓存

当请求资源的时候,强缓存过期或者未采用强缓存,客户端向服务端请求的时候带上特殊字段。如果协商缓存生效,服务端返回 304 状态码(但是不带任何的数据),来表示服务端对资源未进行更新。

客户端在请求 header 中可以带上 Last-Modify/If-Modify-SinceETag/If-None-Match

Last-Modify/If-Modify-Since

Last-Modify 是一个时间标识代表资源最后更改的时间。这个字段会在浏览器第一次请求的时候,服务端的返回放在 header 中带过来。

当浏览器再次请求这个资源的时候,请求的 header 中带上这个字段。服务端收到之后进行比较,决定是否命中协商缓存。

缺点

  1. 周期性变化:如果资源改了两次又改回原来的样子了,这个字段的返回不会命中协商缓存(尽管资源跟客户端目前所有的是一致的)。
  2. 时间精度:最低到秒,如果在很短的时间内发生变化,这个字段不会发生改变。

ETag/If-None-Match

这个字段返回的是一个校验码,ETag 可以保证每个资源都是唯一的,资源的改变都会导致 ETag 的变化。其流程跟 last-modify 一致。

可以同时启用 Last-Modify 和 ETag。ETag 的优先级高于 Last-Modify。服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。

总结

当浏览器再次访问一个已经访问过的资源时,它会这样做:

  1. 看看是否命中强缓存,如果命中,就直接使用缓存了。
  2. 如果没有命中强缓存,就发请求到服务器检查是否命中协商缓存。
  3. 如果命中协商缓存,服务器会返回 304 告诉浏览器使用本地缓存。
  4. 否则,返回最新的资源。

如果资源命中强缓存了,但是如果更新了资源,怎么做?

在文件的 URL 后面添加一个唯一标识(版本号、哈希值或时间戳)。

例如,style.css?v=20240101

我们常见的构建工具 (比如 webpack,vite 等)等就支持自动为文件生成唯一的哈希标识作为版本号。