强缓存
首次打开🍑宝首页(浏览器未有缓存资源),打开开发者工具我们可以看到资源的加载情况
Size 和 Time 列的数据,Size 列表示浏览器从服务器获取资源的大小,Time 列表示资源加载耗时。因为几乎每一个资源都需要从服务器获取并加载,所以网页打开速度会受到影响,这里浏览器用了 753ms 加载完了页面的所有资源(图片、脚本、样式等),1.1 MB 的数据被传输到了本地。
😶 从强缓存的角度来看,访问网页时浏览器做了如下事情:
# 强缓存的生成过程
图中,当浏览器发起 HTTP 请求时,会向浏览器缓存进行一次询问,若浏览器缓存没有该资源的缓存数据,那么浏览器便会向服务器发起请求,服务器接收请求后将资源返回给浏览器,浏览器会将资源的响应数据存储到浏览器缓存中,这便是强缓存的生成过程。
第二次访问🍑宝,继续观察开发者工具中原来的几项指标。
对比发现。Size 一列大部分由原先的资源加载大小变成了 disk cache(磁盘缓存),而变成这一数据对应的 Time 列资源加载速度异常之快,加载总耗时由原来的 753ms 变成了 365ms,而传输到本地的数据降到了 323KB(受网速影响该数据每次都不一样,只用做对比参考)。这便是强缓存生效导致的现象。
强缓存的生效流程如下图所示:
图中我们可以看到浏览器并没有和服务器进行交互,而是在发起请求时浏览器缓存告诉浏览器它那有该资源的缓存数据并且还没有过期,于是浏览器直接加载了缓存中的数据资源。
# 内存缓存
我们不关闭 Tab 页,重新刷新下🍑宝页面,再观察下 Network 面板中的变化。
开发者工具中的 Size 列大部分变成了 memory Cache,其对应的 Time 列变成了 0ms。可见,memory Cache 比 disk cache 更快。
按照缓存位置的读取顺序,相比 disk cache,浏览器会优先读取 memory Cache。通过对以上开发者工具图例的对比不难得出,读取磁盘缓存会存在稍许的耗时,而读取内存缓存是及时性的,不存在耗时。
# max-age/s-maxage
按照上图所示报头的 Cache-Control 首部,根据上一章节介绍的知识点,此资源将被浏览器缓存 7200 秒(即 2 小时),2小时之内我们再次访问,该资源都将从浏览器缓存中读取,这不难理解。但是需要注意图中首部值还包括了 s-maxage=3600 秒:
- s-maxage 仅在代理服务器中生效
- 在代理服务器中 s-maxage 优先级高于 max-age,同时出现时 max-age 会被覆盖
该资源其实是一个 CDN 资源,属于代理服务器资源,在其服务器中的缓存时间并不是 2小时,而是 3600 秒(1 个小时),所以当浏览器缓存 2个小时之后重新向 CDN 服务器获取资源时,此时 CDN 缓存的资源也已经过期,会触发回源机制,即向源服务器发起请求更新缓存数据。
# expires/max-age
Expires 设置的缓存过期时间是一个绝对时间,所以会受客户端时间的影响而变得不精准。例如浏览器资源中expires设置为Expires: Wed, 11 May 2022 03:50:47 GMT,可以将该资源缓存至 2022年5月11日的上述时间点,把电脑客户端时间修改为 2023 年8月28日,此时再次访问网页你会发现浏览器重新向服务器获取了该资源,原来的缓存失效了。
expires “不精准” 是因为它的值是一个绝对时间,而 max-age 与其相反却是一个相对时间,由于 max-age 优先级更高,表示浏览器可以将该资源缓存 3153600 秒(365天),起始时间是从浏览器获取并缓存该资源的时间开始算起。那么此时我们修改电脑客户端时间为 1 年后,该缓存是否就不会失效了?缓存还是会失效。
食品是否新鲜 = 食品保质期 > 食品使用期
那么回归强缓存,上述计算食品是否新鲜的公式同样也适用于强缓存。我们只需要把食品改为强缓存,把食品保质期修改为缓存新鲜度:
强缓存是否新鲜 = 缓存新鲜度 > 缓存使用期
按如上公式所示,强缓存是否新鲜取决于两个关键词:缓存新鲜度和缓存使用期。
# 缓存新鲜度
缓存新鲜度 = max-age || (expires - date)
当 max-age 存在时缓存新鲜度等于 max-age 的秒数,是一个时间单位,就像保质期为 6 个月一样。当 max-age 不存在时,缓存新鲜度等于 expires - date 的值,expires 我们应该已经熟悉,它是一个绝对时间,表示缓存过期的时间。
Date 表示创建报文的日期时间,可以理解为服务器(包含源服务器和代理服务器)返回新资源的时间,和 expires 一样是一个绝对时间,比如
date:Wed, 25 Aug 2021 13:52:55 GMT
那么过期时间(expires)减去创建时间(date)就可以计算出浏览器真实可以缓存的时间(默认已经转化为秒数),即缓存的保质期限(缓存新鲜度)。
# 缓存使用期
字面意思: 缓存使用期可以理解为浏览器已经使用该资源的时间
相比食品的使用期与当前日期和生产日期有关,缓存使用期主要与响应使用期、传输延迟时间和停留缓存时间有关,计算公式如下:
缓存使用期 = 响应使用期 + 传输延迟时间 + 停留缓存时间
# 响应使用期
响应使用期可以通过以下两种方式进行计算:
- max(0, response_time - date_value)
- age_value
第一种方式中的 response_time(浏览器缓存收到响应的本地时间)是电脑客户端缓存获取到响应的本地时间,而 date_value(响应首部 date 值) 上面已经介绍过是服务器创建报文的时间,两者相减与 0 取最大值。
第二种方式直接获取 age_value (响应首部 age 值)。
以下是 MDN (opens new window) 中的介绍
Age
Age 消息头里包含对象在缓存代理中存贮的时长,以秒为单位。
Age的值通常接近于0。表示此对象刚刚从原始服务器获取不久;其他的值则是表示代理服务器当前的系统时间与此应答中的通用头 Date 的值之差。
如下所示:
最终我们可以将以上两种方式进行组合,组合后的计算公式为:
apparent_age = max(0, response_time - date_value)
响应使用期 = max(apparent_age, age_value)
# 传输延迟时间
因为 HTTP 的传输是耗时的,所以传输延迟时间是存在的,传输延迟时间可以理解为浏览器缓存发起请求到收到响应的时间差,其计算公式为:
传输延迟时间 = response_time - request_time
- response_time: 代表浏览器缓存收到响应的本地时间
- request_time: 代表浏览器缓存发起请求的本地时间,两者相减便得到了传输延迟时间。
# 停留缓存时间
停留缓存时间表示资源在浏览器上已经缓存的时间,其计算公式为:
停留缓存时间 = now - response_time
- now 代表电脑客户端的当前时间
- response_time 代表浏览器缓存收到响应的本地时间,两者相减便得到了停留缓存时间。
# max-age 仍然受到本地时间影响
通过上述字段及公式的介绍,最终我们总结出影响强缓存使用期的因素有以下几个:
- age_value:响应首部 age 值
- date_value:响应首部 date 值
- request_time:浏览器缓存发起请求的本地时间
- response_time:浏览器缓存收到响应的本地时间
- now:客户端当前时间
需要注意的是以上 request_time、response_time 和 now 取的都是客户端本地时间,而 now 则是修改客户端本地时间直接导致强缓存失效的“罪魁祸首”。
request_time 和 response_time 是在改变本地时间前就确定了的,因为确定了才能有缓存,有了缓存后再改变本地时间为未来时间,此时 request_time 和 response_time并不会改变,只有now改变,导致停留缓存时间变长从而影响到强缓存的使用期。
因此一旦修改了电脑客户端本地时间为未来时间,缓存使用期的计算便会受到影响,主要是停留缓存时间会变大,从而导致缓存使用期超出缓存新鲜度范围(强缓存失效)。 这便是 max-age 仍然受到本地时间影响的原因所在。