浏览器缓存机制介绍与缓存策略剖析
Chrome 官方给出的解释
通过网络获取内容既速度缓慢又开销巨大。较大的响应需要在客户端与服务器之间进行多次往返通信,这会延迟浏览器获得和处理内容的时间,还会增加访问者的流量费用。因此,缓存并重复利用之前获取的资源的能力成为性能优化的一个关键方面。
浏览器缓存机制有四个方面,它们按照获取资源时请求的优先级依次排列如下:
- Memory Cache
- Service Worker Cache
- HTTP Cache
- Push Cache
HTTP 缓存机制探秘
强缓存 vs 协商缓存
强缓存优先级高 , 强缓存命中失败后才会走协商缓存
强缓存
利用 http 头中的 Expires 和 Cache-Control 两个字段控制的. 当请求再次发出, 浏览器会根据其中的 expires 和 cache-control 判断目标资源是否命中强缓存 , 若命中, 则直接从缓存中获取资源 , 不会再与服务端发生通讯
命中强缓存的特征 : httpCode === 200 (form disk cache)
expires 原理
expires: Wed, 11 Sep 2019 16:12:18 GMT
expries 是一个时间戳 , 如果试图向服务器请求资源 , 就会先对比本地时间和 expries 时间戳 , 如果小雨过期时间 , 就去强缓存中获取资源 , 由于过期时间是服务器定义的 , 本地的时间取值在客户端 , 因此 expires 的工作机制对客户端时间和服务器时间之间的一致性有要求 , 若服务器和客户端存在时差 , 将带来意料之外的结果
max-age
http1.1 将缓存配置收敛进 cache-control , max-age 可以看作是对 expries 能力的补位/替换 当下普遍倾向于 max-age , 如果对向下兼容有强诉求 那么 expries 仍是不可或缺的
cache-control: max-age=31536000
max-age 不是一个时间戳,而是一个时间长度。在本例中,max-age 是 31536000 秒,它意味着该资源在 31536000 秒以内都是有效的
max-age 是一个相对时间,这就意味着它有能力规避掉 expires 可能会带来的时差问题:max-age 机制下,资源的过期判定不再受服务器时间戳的限制。客户端会记录请求到资源的时间点,以此作为相对时间的起点,从而确保参与计算的两个时间节点(起始时间和当前时间)都来源于客户端,由此便能够实现更加精准的判断。
Cache-Control 的 max-age 配置项相对于 expires 的优先级更高。当 Cache-Control 与 expires 同时出现时,我们以 Cache-Control 为准。
Cache-Control 应用分析
cache-control: max-age=3600, s-maxage=31536000
s-maxage 优先级高于 max-age,两者同时出现时,优先考虑 s-maxage。如果 s-maxage 未过期,则向代理服务器请求其缓存内容。
s-maxage 就是用于表示 cache 服务器上(比如 cache CDN)的缓存的有效时间的,并只对 public 缓存有效。
public 与 private
public 和 private 都是 cache-control 的值
public 与 private 是针对资源是否能够被代理服务缓存而存在的一组对立概念。
如果我们为资源设置了 public,那么它既可以被浏览器缓存,也可以被代理服务器缓存;如果我们设置了 private,则该资源只能被浏览器缓存。private 为默认值。但多数情况下,public 并不需要我们手动设置
协商缓存
协商缓存机制下,浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。
如果服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是 304。
协商缓存的实现 从 Last-Modified 到 Etag
Last-Modified 是一个时间戳,如果我们启用了协商缓存,它会在首次请求时随着 Response Headers 返回:
Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT
随后我们每次请求时,会带上一个叫 If-Modified-Since 的时间戳字段,它的值正是上一次 response 返回给它的 last-modified 值:
If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT
服务器接收到这个时间戳后,会比对该时间戳和资源在服务器上的最后修改时间是否一致,从而判断资源是否发生了变化。如果发生了变化,就会返回一个完整的响应内容,并在 Response Headers 中添加新的 Last-Modified 值;否则,返回如上图的 304 响应,Response Headers 不会再添加 Last-Modified 字段。
Last-Modified 存在一些弊端
- 编辑了文件 , 但是文件的内容没有改变 , 服务端不清楚我们是否真正的改变了文件. 他仍然通过最后修改时间来判断 就会被当作新的资源
- If-Modified-Since 只能检查到以秒为单位的时间差 如果修改文件速度很快 就无法感知
这两个场景其实指向了同一个 bug——服务器并没有正确感知文件的变化
Etag
Etag 是服务器为每个资源生成的唯一标识字符串 content hash
Etag 和 Last-Modified 类似,当首次请求时,我们会在响应头里获取到一个最初的标识符字符串,举个 🌰,它可以是这样的:
ETag: W / "2a3b-1602480f459";
那么下一次请求时,请求头里就会带上一个值相同的、名为 if-None-Match 的字符串供服务端比对了
If-None-Match: W/"2a3b-1602480f459"
Etag 的生成过程需要服务器额外付出开销,会影响服务端的性能,这是它的弊端。因此启用 Etag 需要我们审时度势。正如我们刚刚所提到的——Etag 并不能替代 Last-Modified,它只能作为 Last-Modified 的补充和强化存在。 Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。
缓存决策
Chrome 官方给出的这张
当我们的资源内容不可复用时,直接为 Cache-Control 设置 no-store,拒绝一切形式的缓存;
否则考虑是否每次都需要向服务器进行缓存有效确认,如果需要,那么设 Cache-Control 的值为 no-cache;
否则考虑该资源是否可以被代理服务器缓存,根据其结果决定是设置为 private 还是 public;然后考虑该资源的过期时间,设置对应的 max-age 和 s-maxage 值;最后,配置协商缓存需要用到的 Etag、Last-Modified 等参数。
MemoryCache
MemoryCache,是指存在内存中的缓存。从优先级上来说,它是浏览器最先尝试去命中的一种缓存。从效率上来说,它是响应速度最快的一种缓存。
内存缓存是快的,也是“短命”的。它和渲染进程“生死相依”,当进程结束后,也就是 tab 关闭以后,内存里的数据也将不复存在。
Base64 格式的图片,几乎永远可以被塞进 memory cache,这可以视作浏览器为节省渲染开销的“自保行为”;此外,体积不大的 JS、CSS 文件,也有较大地被写入内存的几率——相比之下,较大的 JS、CSS 文件就没有这个待遇了,内存资源是有限的,它们往往被直接甩进磁盘。
Service Worker Cache
Server Worker 对协议是有要求的,必须以 https 协议为前提
Push Cache
Push Cache 是指 HTTP2 在 server push 阶段存在的缓存
- Push Cache 是缓存的最后一道防线。浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问 Push Cache。
- Push Cache 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。
- 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。