作者: 王振州 日期: 2020-06-23
一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷,提高网页的整体加载速度。
对于一条网络请求来说, 一般会经历浏览器发送请求==>>后端处理请求==>>浏览器响应请求
。 如果不使用缓存的话每条请求都会经历这个过程,若重复请求相同资源,会对服务器资源造成浪费,服务器重复读取资源,发送给浏览器,浏览器重复下载, 造成不必要的等待和消耗。而浏览器缓存发生在浏览器发送请求
和浏览器响应请求
阶段,直接使用缓存就可以避免上面请求过程的重复发生。
下面将通过缓存策略、缓存位置以及使用场景展开
浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识,如果找到缓存验证时效性,如果缓存有效则使用该缓存并且状态码为200
(这个过程为强缓存
);如果缓存无效,浏览器会发送请求并且携带缓存标识,服务器会依据请求携带的缓存标识决定是否使用缓存,使用缓存返回304
和Not Modified
,此时浏览器使用缓存,若文件被修改则会返回200
和文件内容
,浏览器放弃使用缓存,使用请求返回的内容(这个过程为协商缓存
)。
强缓存
:不会向服务器发送请求,直接从缓存中读取资源,在浏览器控制台的Network
选项中可以看到该请求返回200
的状态码,并且 Size 显示from disk cache
或from memory cache
。强缓存可以通过设置两种 HTTP Header 实现:Expires
和 Cache-Control
。
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。Expires 是 Web 服务器响应消息头字段,在响应 http 请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。
Expires 是 HTTP/1.0 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效
。
Expires: Wed, 22 Oct 2018 08:41:00 GMT
表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT
后过期,需要再次请求。
Cache-Control
是在 http/1.1 中添加的,为了解决Expires
存在的问题,该字段与Expires
的缓存思路相同,都是设置了一个过期时间,不同的是max-age
设置的是相对开始缓存时间开始往后多久,因此不存在受日期不准确情况的影响。Cache-Contro
可以在请求头或者响应头中设置,并且可以组合使用多种指令:
指令 | 指令作用 |
---|---|
public | 表示响应可以被客户端和代理服务器缓存 |
private | 表示响应只能被客户端缓存,默认值 |
max-age=30 | 缓存 30s 后过期 |
s-maxage | 覆盖 max-age,作用一样,只在代理服务器中生效 |
no-store | 所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存 |
no-cache | 资源被缓存,但是不使用(强缓存失效),请求会验证资源是否过期 |
max-stale=30 | 30s 内,即使缓存过期,也是用该缓存 |
min-fresh=30 | 希望在 30s 内获取最新的响应 |
强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
协商缓存就是强缓存失效时,浏览器请求会携带缓存标识到服务器,服务器根据该标识决定是否使用缓存,主要有两种情况:
- 协商缓存生效,返回 304 和 Not Modified
- 协商缓存失效,返回 200 和请求结果
协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。
浏览器在第一次访问资源时,服务器返回资源的同时,在 response header 中添加 Last-Modified 的 header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和 header;
Last-Modified: Mon, 25 May 2020 08:51:24 GMT
浏览器下一次请求这个资源,浏览器检测到有 Last-Modified 这个 header,于是添加 If-Modified-Since 这个 header,值就是 Last-Modified 中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回 304 和空的响应体,直接从缓存读取,如果 If-Modified-Since 的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和 200
但是 Last-Modified 存在一些弊端:
既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETag
和If-None-Match
ETag
是对资源的特殊标识
Etag: W/"e563df87b65299122770e0a84ada084f"
请求该资源成功之后,将返回的 ETag 存入If-None-Match
字段中(浏览器自动记录了该字段信息),同样在请求资源时传递给服务器,服务器查询该编码对应的资源有无更新,无更新返回 304 状态,更新返回 200 并重新请求。
ETag 是针对某个文件的特殊标识,服务器默认采用SHA256
算法生成。也可以采用其他方式,保证编码的唯一性即可。
前面我们已经提到,当强缓存命中或者协商缓存中服务器返回 304 的时候,我们直接从缓存中获取资源。那这些资源究竟缓存在什么位置呢? 浏览器中的缓存位置一共有四种,按优先级从高到低排列分别是:
Service Worker
借鉴了 Web Worker 的 思路,即让 JS 运行在主线程之外,由于它脱离了浏览器的窗体,因此无法直接访问 DOM。虽然如此,但它仍然能帮助我们完成很多有用的功能,比如离线缓存、消息推送和网络代理等功能。其中的离线缓存就是 Service Worker Cache
。
Service Worker
同时也是PWA
的重要实现机制,关于它的细节和特性,我们将会在后面的 PWA 的分享中详细介绍。
Memory Cache
指的是内存缓存,从效率上讲它是最快的。但是从存活时间来讲又是最短的,当渲染进程(tab 页)结束后,内存缓存也就不存在了。
Disk Cache
就是存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,但是他的优势在于存储容量和存储时长。稍微有些计算机基础的应该很好理解,就不展开了。
好,现在问题来了,既然两者各有优劣,那浏览器如何决定将资源放进内存还是硬盘呢?主要策略如下:
即推送缓存,这是浏览器缓存的最后一道防线。它是 HTTP/2 中的内容,虽然现在应用的并不广泛,但随着 HTTP/2 的推广,它的应用越来越广泛。
可以使用Cache-Control: no-cache
每次都向服务器发送请求并携带Etag
或Last-Modified
验证资源是否发生变化。
可以设置Cache-Control: max-age=2592000
(30 天或更长的时间间隔),这样浏览器之后请求相同的 URL 会命中强制缓存。为了避免文件更新不能及时更新的问题,可以在文件名中添加 hash、版本号等动态字符,从而改动资源的 URL,达到更改的文件能及时生效。
用户输入地址栏
,查找 disk cache 是否匹配, 不匹配发送请求。普通刷新
(Mac:command+R,windows:F5),优先使用 memory cache,其次使用 disk cache。强制刷新(Ctrl+F5)
(Mac:command+shift+R,windows:Ctrl+F5),不使用缓存。