一杯茶的时间,读懂浏览器的缓存机制
Posted 园子Talk
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一杯茶的时间,读懂浏览器的缓存机制相关的知识,希望对你有一定的参考价值。
前言
一个网站的性能最直观的就是看页面打开的速度。其中提高页面反应速度的一个方式就是使用缓存。
一个优秀的缓存策略可以缩短页面请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,可以减少宽带,降低网络负荷。
对于基本的网络请求包含三个步骤:请求,处理,响应,其中浏览器缓存是在请求和响应中进行。
在请求阶段浏览器通过存储结果的方式直接使用资源,直接省去了发送请求;在响应阶段浏览器与服务器共同配合,通过减少响应内容缩短传输的时间。
本文从缓存位置,缓存过程进行探讨浏览器缓存机制。
缓存位置
从缓存位置上分为四种,并且它们有优先级的。当依次查找缓存并且没有命中时才会请求网络。
Service Worker
Memory Cache
Disk Cache
Push Cache
下面根据缓存位置分别介绍:
Service Worker
使用 Service Worker 传输协议必须是 HTTPS。因为 Service Worker 涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。
Service Worker 缓存有别于浏览器内部的其他缓存,该缓存是永久性的,即:关闭 Tab 或浏览器,下次打开依然还在。
有两种情况会导致这个缓存中的资源被清除:手动调用 API 删除缓存 cache.delete(resource) 或容量超过限制,被浏览器全部清空。
如果 Service Worker 没能命中缓存,一般情况会使用 fetch() 方法继续获取资源。这时浏览器会根据存储位置的优先级进行下一次找缓存的工作。
注意:经过 Service Worker 的 fetch() 方法获取的资源,即便它没有命中 Service Worker 缓存,甚至走了网络请求,浏览器都会显示来自 Service Worker 获取的内容。
Memory Cache
Memory Cache 是存储在内存中的缓存,主要包含当前页面中已经抓取到的资源,比如页面上已经下载的图片、样式、脚本等。
读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,但是缓存持续性很短,会随着进程的释放而释放。一旦关闭 Tab 页面,内存中的缓存也就被释放了。
内存缓存虽然高效,但并不是所有的数据都放在内存中。因为计算机中的内存一定比硬盘容量小的多,操作系统需要精打细算的使用,所以能让使用的内存必然不多。
在访问页面后,再次刷新页面,可以发现很多数据都来自于内存缓存。
内存缓存有一个重要的缓存资源是 preloader 相关命令下载的资源 <link ref="prefetch">。preloader 的相关命令是页面优化的常见手段,可以一边解析 CSS 文件或 javascript 文件 ,可以一边网络请求下一个资源。
值得注意的是:内存缓存在缓存资源时并不关心返回资源的 HTTP 缓存头 Cache-control 是什么值,同时资源的匹配也并非仅仅是对 URL 做匹配,还有可能是 Content-Type,CORS 等其他特征做校验。
Disk Cache
Disk Cache 是存储在硬盘上的缓存,因此它是持久存储,实际存在于文件系统中。而且它允许相同的资源在跨会话,甚至跨站点的情况下使用。比如两个站点都使用同一张图片。
Disk Cache 会严格根据 HTTP 头信息中的各类字段来判断哪些资源可以缓存,哪些资源不可以缓存,哪些资源是仍然可用,哪些资源是过时需要重新请求的。
当命中缓存后,浏览器会从硬盘中读取资源,虽然比内存中读取慢了一些,但比起网络请求还是快了不少。绝大部分的缓存都来自 Disk Cache。关于 HTTP 的协议头中的缓存字段,在下文稍后讨论。
关于浏览器会把哪些文件放进内存中,哪些文件放进磁盘中?
这点网上各有说法,不过以下观点比较靠谱:
对于大文件来说,大概率是不存储在内存中的,反之优先。
当前系统内存使用率高的话,文件优先存储硬盘。
Push Cache
Push Cache 是 HTTP/2 中的推送缓存,当以上缓存都没命中时,才会被采用。该缓存只在会话 (session) 中存在,一旦会话结束就被释放,并且缓存时间也很短暂。
因为 HTTP/2 在国内不够普及,查看 Push Cache 相关资料推荐阅读这篇文章:HTTP/2 push is tougher than I thought[4],从这篇文章得出:
所有资源都能被推送,并且能够被缓存。但是 Edge 和 Safari 浏览器支持较差。
可以推送 no-cache 和 no-store 的资源。
一旦关闭连接,Push Cache 就被释放。
多个页面可以使用同一个 HTTP/2 的连接,也就可以使用同一个 Push Cache。这主要依赖于浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的 Tab 标签使用同一个 HTTP 连接。
Push Cache 中的缓存只能被使用一次。
浏览器可以拒绝接受已经存在的资源推送。
可以给其他域名推送资源。
如果以上四种缓存都没命中,那么只能发起请求来获取资源。
缓存过程
浏览器与服务器通信的方式为应答模式,即:浏览器发起 HTTP 请求 - 服务器响应该请求。
那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中 HTTP 头的缓存标识,决定是否缓存结果。
将请求结果和缓存标识存入浏览器缓存中,简单过程如下:
从上图可以知道:
浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识。
浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中。
以上两点结论就是浏览器缓存机制的关键,它确保了每个请求的缓存存入与读取。只要再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解,本文也将围绕这点进行详细分析。
这里根据是否需要向服务器重新发起 HTTP 请求将缓存过程分为两个部分:强制缓存和协商缓存。
强制缓存
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。其中强制缓存主要有三种情况:
不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求。如下图:
存在该缓存结果和缓存标识,并且该结果未失效,强制缓存生效,直接返回该结果。如下图:
存在该缓存结果和缓存标识,但是该结果已失效,强制缓存失效,则使用协商缓存(下面介绍该缓存)。如下图:
前面提到的强制缓存的缓存规则是:当浏览器向服务器发起请求时,服务器会将缓存规则放入 HTTP 响应报文的 HTTP 头部和请求结果一起返回浏览器。而控制强制缓存的字段有 Expires和Cache-Control。
Expires
缓存过期时间,用来指定资源到期的时间,是服务端的具体时间点。也就是说,Expires=max-age + 请求时间,需要和 last-modified 结合使用。
Expires 是 Web 服务器响应消息头字段,在响应 HTTP 请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存读取数据,无需再次请求。
Cache-Control
在 HTTP/1.1 中,Cache-Control 是最重要的规则,主要用于控制页面缓存,其中主要取值有:
public:所有内容都将被缓存(客户端和代理服务器都可缓存)。响应被缓存可以在多用户间共享。如果没有指定 public 还是 private,则默认为 public。
private:所有内容只有客户端可以缓存,Cache-Control 的默认取值。响应只作为私有的缓存,不能在用户间共享。如果要求 HTTP 认证,响应会自动设置为 private。
no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定。如果设置了 no-cache 之后并不代表浏览器不缓存,而是在缓存前要向服务器确认资源是否被更改。因此有些时候只设置 no-cach e防止缓存还是不够保险,还可以加上 private 指令,将过期时间设为过去时间。
no-store:所有内容都不会缓存,即不使用强制缓存,也不使用协商缓存,每次请求资源都要从服务器重新获取。
max-age(单位s):指定设置缓存最大的有效时间,定义的是时间长短。当浏览器向服务器发送请求后,在 max-age 这段时间里浏览器就不会再向服务器发送请求。需要注意的是:max-age 会覆盖到 Expires。
s-maxage(单位s):同max-age,只用于共享缓存(比如 CDN 缓存)。比如:当 s-maxage=3600 时,在这 3600 秒中,即时更新了 CDN 的内容,浏览器也不会进行请求。也就是说 max-age 用于普通缓存,s-maxage 用于代理缓存。如果存在 s-maxage,则会被覆盖掉 max-age 和 Expires。
must-revalidate:指定如果页面是过期的,则去服务器进行获取。
对比 Expires 与 Cache-Control:
同时存在 Cache-Control 优先级比 Expires 高。
Expires 是 HTTP/1.0 控制页面缓存的字段,其值为服务器返回该请求结果缓存的到期时间。即再次发起该请求时,如果客户端的时间小于 Expires 的值时,直接使用缓存结果。
Cache-Control 是 HTTP/1.1 控制页面缓存的字段,其值为上面提到的常用值。
现在的浏览器默认使用 HTTP/1.1,那么在 HTTP/1.1 中页面缓存 Expires 已经被 Cache-Control 替换。原因是 Expires 控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,如果客户端与服务端的时间因为某些原因(比如时区不同,客户端与服务端有一方的时间不准确)发生误差,那么强制缓存会直接失效,这样的话强制缓存的存在毫无意义。
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识是否使用缓存过程。
其中协商缓存主要有两种情况:
协商缓存生效,返回304。如下图:
协商缓存失效,返回200和请求结果。如下图:
协商缓存的标识也是在响应报文的 HTTP 头部和请求结果一起返回给浏览器,控制协商缓存的字段有 Last-Modified/If-Modified-Since 和 Etag/If-None-Match。
Last-Modified/If-Modified-Since
Last-Modified 是服务器响应请求时,返回该资源文件在服务器最后修改的时间。如:
Response Headers
last-modified: Web, 16 May 2020 01:02:55 GMT
If-Modified-Since 是客户端再次发起该请求时,携带上次请求返回的 Last-Modified 值,通过该字段值告诉服务器该资源上次请求返回的最后被修改的时间。服务器收到该请求,发现请求头含有 If-Modified-Since 字段,则会根据 If-Modified-Since 的字段值与该资源在服务器的最后被修改的时间做对比,若服务器的资源最后被修改时间大于 If-Modified-Since 的字段值,则重新返回资源,状态码为 200;否则返回 304,代表资源无更新,可继续使用缓存文件。如:
Response Headers
if-modified-since: Web, 16 May 2020 01:02:55 GMT
Etag/If-None-Match
Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成)。如:
Response Headers
etag: W/"175737-1623592687010"
其中 Etag 能解决 Last-Modified 存在的一些问题:
某些服务器不能精确得到资源的最后修改时间,这样就无法通过最后修改时间判断资源是否更新。
如果资源修改非常频繁,在秒以下的时间内进行修改,而 Last-Modified 只能精确到秒。
一些资源的最后修改时间改变了,但是内容没改变,使用 Etag 就认为资源还是没修改。
If-None-Match 是客户端再次发起该请求时,携带上次请求返回的唯一标识 Etag 值,通过该字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头含有 If-None-Match,则会根据 If-None-Match 的字段值与该资源在服务器的 Etag 值做对比,如果值一致则返回 304,代表资源无更新,继续使用缓存文件;如果值不一致则重新返回资源文件,状态码为 200。如:
Response Headers
if-none-match: W/"175737-1623592687010"
对比 Last-Modified/If-Modified-Since 和 Etag/If-None-Match:
同时存在 Etag/If-None-Match 优先级比 Last-Modified/If-Modified-Since 高。
Etag 返回当前资源文件的一个唯一标识,而 Last-Modified 返回该资源文件在服务器最后修改的时间。
Etag 根据实体内容生成一段hash字符串,标识资源的状态,由服务器产生,浏览器会将这字符串传回服务器,验证资源是否已经修改。
If-None-Match/If-Modified-Since 都是客户端再次发起该请求,携带上次请求返回的唯一标识值做对比。
浏览器缓存机制
强制缓存优先于协商缓存,若强制缓存生效则直接使用缓存,若不生效则进行协商缓存;协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;若协商缓存生效返回304,继续使用缓存。
主要过程如下:
总结
以上就是浏览器缓存的探讨。在开发中缓存是必不可少的,如何使用缓存更高效,让项目的性能更优,需要根据实际项目进行仔细择优。
———— / END / ———
参考资料
「1」浅谈Web缓存
「2」一文读懂前端缓存
「3」彻底理解浏览器的缓存机制
「4」HTTP/2 https://jakearchibald.com/2017
/h2-push-tougher-than-i-thought
喜欢本文,点个“在看”告诉我
以上是关于一杯茶的时间,读懂浏览器的缓存机制的主要内容,如果未能解决你的问题,请参考以下文章