浏览器的缓存机制

1,732 阅读5分钟

这是我参与更文挑战的第2天,活动详情查看:更文挑战

页面通过网络请求获取资源,需要经过三个步骤:发起请求、处理请求、响应返回资源。如果能够将以前获取的资源进行存储复用,就可以节省请求的等待时间和网络流量,提高网站的性能。

缓存就是一种保存资源副本并在下次请求时直接使用该副本的技术。该技术在浏览器、网关、服务器等方面均有相关应用。本文主要介绍跟浏览器相关的资源缓存机制。

缓存位置

按优先级从高到底,浏览器的资源会被缓存在以下位置:

  1. Service Worker
  2. memory cache
  3. disk cache(HTTP cache)
  4. push cache

Service Worker

Service Worker 是浏览器独立于网页在后台运行的脚本,它有着独立的 js 运行环境,协助前端页面完成需要在后台悄悄执行的任务,它的缓存是永久性的。除非手动调用 api 或者容量超过限制才会被浏览器清除。

经过 Service Worker 的 fetch() 方法获取的资源,即便它并没有命中 Service Worker 缓存,甚至实际走了网络请求,在 Chrome 的 Network 面板也会被标注为 from ServiceWorker

memory cache

几乎所有的网络请求资源都会被浏览器自动加入 memory cache,即缓存在内存中。一般情况下,浏览器 tab 关闭,该页面相关的 memory cache 就会失效。虽然 memory cache 是无视 HTTP 请求头的,但是 no-store 除外。在设置了 Cache-Control: no-store 的情况下,该资源不进行任何缓存。

disk cache

HTTP 协议头的缓存相关字段,限定的都是 disk cache 相关的缓存策略,它是持久存储,实际存在于文件系统中的,而且它允许相同的资源跨会话,甚至跨站点的情况下使用。浏览器会根据自己的算法自动清理最老的和最可能过时的。

push cache

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

请求资源后的缓存流程

  1. 根据 Service Worker 的 handler 决定是否存入 cache storage;
  2. 根据 HTTP 头部决定是否存入 disk cache,强缓存优先于协商缓存;
  3. memory cache 保存一份资源的引用,以备下次使用。

强缓存(Cache-control 和 Expires)

浏览器根据 Expires 和 Cache-Control 字段判断资源缓存是否过期,如果没有过期则直接使用缓存的资源。过期的话则进入协商缓存。

Expires 是 HTTP1.0 的字段,表示缓存到期时间,是一个绝对时间,依赖客户端本地的时间判断,可能会出现客户端和服务端时间不一致(比如修改了本地时间)。

Cache-Control 是 HTTP1.1 的字段,表示缓存资源的最大有效时间,是一个相对时间。

Cache-Control 优先级高于 Expires,当两者同时存在时,以 Cache-Control 作为判断依据。

协商缓存

当强缓存失效时,浏览器会根据以下两组字段进行协商缓存的验证,由服务器判断缓存内容是否失效。

Last-Modified & If-Modified-Since

Last-Modified 是该资源文件最后一次更改时间。

  • 服务器通过 Last-Modified 告诉客户端,资源最后一次的修改时间,浏览器将这个值和资源记录在缓存数据库;
  • 下一次请求相同资源时,将上次的 Last-Modified 的值写入到请求头的 If-Modified-Since 字段中;
  • 服务器会将请求头中的 If-Modified-Since 的值与 Last-Modified(该资源的最后修改时间)字段进行对比,如果相等,返回 304 状态码,表明资源可以继续使用,浏览器直接取缓存的资源;反之,则表示缓存过期,返回 200 状态码和新的资源。

ETag & If-None-Match

ETag 是服务器根据当前文件内容,给文件生成的唯一标识,只要内容改动就会发生变化。

  • 服务器通过响应头将 ETag 传给浏览器;
  • 浏览器会在下次请求时,将这个值作为 If-None-Match 这个字段的内容,放在请求头中,然后发给服务器;
  • 服务器接收到 If-None-Match 后,会跟服务器上该资源的 ETag 进行对比。如果一致,返回 304 状态码,否则返回 200 状态码和新的资源。

ETag 的优先级高于 Last-Modified,精确度也高于 Last-Modified。如果文件在 1 秒内进行了多次修改,Last-Modified 的值不会改变。但 ETag 性能稍差,需要经过算法计算得到。

启发式缓存

如果 Expires,Cache-Control: max-age,或 Cache-Control: s-maxage 都没有在响应头中出现,并且设置了Last-Modified时,浏览器默认会采用一个启发式的算法,即启发式缓存。通常会取响应头的 Date 减去 Last-Modified 值的 10% 作为缓存时间,即 (Date - Last-Modified) * 10%。

总结

浏览器请求资源时调用缓存顺序:

  1. 调用 Service Worker 的 fetch 事件
  2. 查看 memory cache
  3. 查看 disk cache:若有强缓存且未失效,则不请求服务器,且状态码为 200;若强缓存失效则使用协商缓存,通过对比协议头来确定是 304 还是 200

服务器资源响应后:

  1. 如果配置了http响应头,则把响应内容存入disk cache;
  2. 把响应内容的引用传入memory cache(无视任何http头信息,除了no-store);
  3. 把响应内容存入 Service Worker 的 Cache Storage( Service Worker 脚本调用了 put 方法 )。

参考

一文读懂前端缓存,by 小蘑菇小哥

深入理解浏览器的缓存机制,by 浪里行舟