目录
- 1,缓存的分类
- 1.1,按缓存位置
- 1,Service Worker
- 2,Memory Cache
- 3,Disk Cache
- 4,Push Cache
- 1.2,按缓存类型
- 强缓存
- Expires
- Cache-control
- 协商缓存
- Last-Modified & If-Modified-Since
- Etag & If-None-Match
- 2,缓存读取规则
- 3,浏览器的一些行为
- 4,缓存最佳实践
- 频繁变动的资源
- 不常变化的资源
1,缓存的分类
1.1,按缓存位置
1,Service Worker
MDN参考
它是运行在浏览器后台的独立线程,可以将它理解为是一个代理服务器。可以拦截所有的网络请求,也可以缓存数据。主要是为了创建有效的离线体验,比如后台同步,离线渲染,推送通知等。
它的缓存机制也比较特殊,可以自由的控制:缓存内容,匹配规则,读取规则,有效期等。
实现缓存的大致步骤:
- 注册 Service Worker。
- 监听
install
事件提前缓存资源。 - 监听
fetch
事件,拦截网络请求并返回已缓存的资源。
2,Memory Cache
它是内存中的缓存,主要缓存当前页面中已经抓取到的资源,例如已经下载的样式、脚本、图片等,保存一份资源的引用,以备下次使用。
注意,Memory Cache 是浏览器为了加快读取缓存速度而进行的自身的优化行为,不受开发者控制,也不受 HTTP Header 的约束,算是一个黑盒。
访问页面后,再次刷新,可以看到很多数据来自内存缓存:
特点:读取速度快,但时效性很短,会随着进程结束而释放。比如关闭标签页,内存中的缓存就被释放了。
而因为计算机可使用的内存一般都比较小,操作系统对内存的使用会更精细化,所以可供浏览器使用的并不会很多。
作用:该缓存机制保证了一个页面有2个以上相同的资源请求时,实际最多只会被请求一次,如上图所示。
3,Disk Cache
重点
它是存储在硬盘中的缓存,是覆盖面最大的缓存机制,会根据 HTTP Header 中的字段(Cache-Control等)来判断:
- 哪些资源需要缓存
- 哪些资源可以不请求直接使用
- 哪些资源已经过期需要重新请求
并且在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再去请求数据。一般来说,绝大部分缓存都来自 Disk Cache。
特点:读取速度较慢,但时效性很好(可设置)。
因为时效性的原因,Disk Cache 也算是持久性存储,所以也会面对容量增长的问题。
而浏览器解决这个问题,是通过特殊的算法把 “最老的” 和 “最有可能过时” 的资源一个一个的删除。
4,Push Cache
称为推送缓存或启发式缓存,是HTTP2 新增的内容。
上面3种缓存都没有命中时,它才会被使用,并且时效性很短,只在 session 会话中存在,会话结束就被释放了。
这个我了解的不多,网上也没有找到合适的文章,就不多做介绍了。
1.2,按缓存类型
其实是对 Disk Cache 进行的分类。
强缓存
当浏览器请求资源后,会优先访问强缓存。存在则返回,否则请求服务器,响应后再次写入强缓存。
作用:直接减少请求数量,是提升最大的缓存策略。通过缓存优化网页性能时是应该首先被考虑的。
实现:通过 HTTP Header 中的字段:Expires 和 Cache-control。
Expires
是 HTTP 1.0 的字段,表示缓存到期的时间,是一个绝对时间(当前时间+缓存时间)
Expires: Wed, 20 Dec 2023 23:09:07 GMT
在响应头中设置该字段,告诉浏览器在过期之前不要再次请求。
但是这个字段有明显的缺点:
- 写法复杂,不利于解析。一旦缺少字母或空格就会变为非法属性而失效。
对比正常的时间格式
new Date() // Wed Dec 20 2023 23:09:07 GMT+0800 (中国标准时间)
new Date('Wed Dec 20 2023 23:09:07 GMT+0800') // Wed Dec 20 2023 23:09:07 GMT+0800 (中国标准时间)
- 由于是绝对时间,用户可以修改本地时间,导致浏览器判断缓存失效而重新请求资源。
即便不考虑本地修改的问题,时间误差等因素也会导致浏览器和服务器之间的时间不一致,导致缓存失效。
Cache-control
在已知 Expires 的缺点后,在 HTTP 1.1 中增加该字段,是一个相对时间,表示资源缓存的最大有效时间,该时间内不需要再次向服务器发送请求。
Cache-Control: max-age=3600
下面列举一些 Cache-control 的常用值,更多的字段参考 MDN
-
max-age,最大有效时间
-
must-revalidate(响应头信息),如果超过了 max-age 的时间,浏览器必须向服务器发送请求,验证资源是有效性。
-
no-cache,客户端会缓存资源,但每次都得发送请求验证有效性,等价于
max-age=0 must-revalidate
。 -
no-store,真正意义上的“不要缓存”。所有内容都不走缓存,包括强缓存和协商缓存。
-
private(默认值),所有的内容只有客户端才可以缓存,代理服务器不能缓存。
这些值可以混合使用,优先级如下:(网图,我翻译了一下)
其他一些注意点:
- 在 HTTP 1.1 之前,如果想使用 no-cache,通常设置 Pragma: no-cache(这也是 Pragma 字段唯一的取值)。但这个字段只是浏览器约定俗成的实现,缺乏可靠性,只是作为兼容字段出现。
- 自 HTTP 1.1 开始,Expires 逐渐被 Cache-control 取代。而为了兼容 HTTP 1.0 和 HTTP 1.1 ,实际上这2个字段都会配置。
协商缓存
当强缓存超时失效后,就需要使用协商缓存,有服务器来决定缓存资源是否失效(也就是强缓存中的有效性判断。)
服务器资源为更新时,验证请求返回 304,大致流程如下图示:
而如果资源更新了,验证请求返回 200
特点:协商缓存并不会减少请求的数量。但如果是304,请求只会返回一个状态码,没有实际的资源内容,所以会减少响应体体积,来缩短网络传输时间。
协商缓存作为强缓存失效的后备解决方案,一般会和强缓存一起使用。通过2组字段来实现:
- Last-Modified & If-Modified-Since
- Etag & If-None-Match
Last-Modified & If-Modified-Since
Last-Modified 是响应头,If-Modified-Since 是请求头。
- 首先,服务器通过 Last-Modified 字段告知浏览器,资源最后一次被修改的时间。
Last-Modified: Thu, 21 Dec 2023 00:28:00 GMT
- 浏览器将资源和这个字段的值保留在缓存中。
- 再次请求相同资源时,浏览器从自己的缓存中找出 “不确定是否过期的” 缓存(也就是强缓存失效,已经命中协商缓存了)。并在请求头的 If-Modified-Since 字段中写入保存的 Last-Modified 的值。
- 服务器会将接收到的 If-Modified-Since 的值与服务端的 Last-Modified 字段进行对比。相等则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。
缺点:因为它的时间单位是秒,所以如果资源的更新速度是秒以下的单位,则无法使用该缓存。
Etag & If-None-Match
为了解决上面的问题,所以在出现了这组新的字段。
ETag 是响应头,If-None-Match 是请求头。
ETag 是资源的标识符,一般为一个 hash 值,在服务器存储。
整体流程和 Last-Modified & If-Modified-Since 类似,只是做了替换 Last-Modified --> ETag ;If-Modified-Since --> If-None-Match。
流程如下图示:
二者对比:
- 精度:Etag > Last-Modified,因为 Etag 是一个 hash 值,每次都会改变从而确保精度。
- 性能:Last-Modified > Etag,因为 Etag 需要服务器通过算法来计算 Hash 值。而 Last-Modified 只需要记录时间。
- 优先级:服务器校验优先考虑 Etag。
Disk Cache 整体流程图:
2,缓存读取规则
当浏览器要请求资源时:
-
从 Service Worker 中获取内容(如果设置了 Service Worker)。
-
查看 Memory Cache。
-
查看 Disk Cache。细分为:
-
如果有强制缓存且未失效,则使用强制缓存,不请求服务器。这时的状态码全都是 200。
-
如果有强制缓存但已失效,使用协商缓存,比较后确定 304 还是 200。
-
-
发送网络请求,等待网络响应。
-
把响应内容存入 Disk Cache(如果 HTTP 响应头信息有相应配置的话)。
-
把响应内容的引用存入 Memory Cache(无视 HTTP 头信息的配置)。
-
把响应内容存入 Service Worker 的 Cache Storage(如果设置了 Service Worker)。
3,浏览器的一些行为
用户对浏览器的不同操作,会触发不同的缓存读取策略。
常见的有3种:
- 在地址栏输入地址访问时,浏览器会查找 Disk Cache 中是否有匹配。有则使用,没有发送则发送网络请求。
- 普通刷新(F5),因为标签页没有关闭,所以 Memory Cache 是可用的,会优先使用它。其次才是 Disk Cache。
- 强制刷新(Ctrl / Shift + F5),浏览器不使用缓存,因此在 请求头 Request Headers 中会自动添加 Cache-Control: no-cache(为了兼容还会有Pragma: no-cache),服务器直接返回200和最新内容。
4,缓存最佳实践
频繁变动的资源
Cache-Control: no-cache
或(二者等效)
Cache-Control: max-age=0, must-revalidate
表示浏览器可以缓存资源,但每次使用缓存资源前都必须重新验证其有效性(通过 ETag 或者 Last-Modified)。
这样虽然每次都会发起 HTTP 请求,但当缓存内容仍然有效时可以跳过 HTTP 响应体的下载,来减少响应数据的大小。
不常变化的资源
Cache-Control: max-age=31536000
这类资源,可以设置一个很大的有效时间 31536000(一年)。这样浏览器之后请求相同 url 时会命中强制缓存。
由此带来的更新问题,可以在文件名后添加 hash,版本号等字符,从而达到更改资源 url 的目的。
注意之前的强制缓存并未失效,只是不再使用了而已。
以上。
service-worker-cache 参考