第7题-浏览器缓存命中策略

2,226 阅读7分钟

面试题目:

浏览器缓存相关的头字段有哪些?http请求时,它们之间是如何互相协作的?

答案解析:

浏览器缓存是性能优化中简单高效的一种方式,按照缓存位置划分为以下几种类型:

  1. service Worker
  2. Memory Cache
  3. Disk Cache
  4. Push Cache

浏览器请求时,会按照如上的优先级顺序,进行查找缓存,都没有命中时,才会去请求网络。

面试过程中问到的浏览器缓存相关问题,一般是针对Disk Cache而言的,也就是硬盘缓存,下面我们主要对硬盘缓存进行详细解析下。

硬盘缓存分为以下两种类型

  1. 强缓存
  2. 协商缓存

强缓存

强缓存特点是:不需要向服务器发出请求,直接从硬盘缓存中读取想要的资源。在Chrome调试面板 Network面板中,能看到该类请求会返回200状态码,Size 字段显示 from disk cache,如下图所示:

硬盘缓存

当我们自己打开一个web页面查看的时候,会发现第一次打开一般都是请求的服务器,然后点击刷新按钮,刷新当前页面,发现 Size 字段并没有出现from disk cache,而是显示from memory cache,这是为什么呢?

Memory Cache

这是因为浏览器请求资源时,命中了memory cache,也就是所谓的内存缓存,内存缓存的优先级高于硬盘缓存,也比硬盘缓存的速度快。

如何复现出 disk cache 呢?

重新打开一个浏览器的tab栏,输入页面地址打开,这个时候就会看到 disk cache 了。原因是每个tab栏都会启动新的进程,所以就不会共享内存,也就不会命中memory cache了。

强缓存有两个头字段控制:ExpiredCache-Control

Expired

Expired 是 Http/1.0的产物,表示的资源的过期时间(服务器端的时间)。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

比如Expires:Thu,31 Dec 2037 23:59:59 GMT。这个时间代表着这个资源的失效时间,也就是说在2037年12月31日23点59分59秒之前都是有效的,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当客户端本地时间被修改以后,服务器与客户端时间偏差变大以后,就会导致缓存混乱。于是发展出了Cache-Control。

Expired

Cache-Control

Cache-Control是一个相对时间,例如Cache-Control:3600,代表着资源的有效期是3600秒。由于是相对时间,并且都是与客户端时间比较,所以服务器与客户端时间偏差也不会导致问题。 Cache-ControlExpires可以在服务端配置同时启用或者启用任意一个,同时启用的时候Cache-Control优先级高。

Cache-Control 可以由多个字段组合而成,主要有以下几个取值:

  1. max-age 指定一个时间长度,在这个时间段内缓存是有效的,单位是s。例如设置 Cache-Control:max-age=31536000,也就是说缓存有效期为(31536000 / 24 / 60 * 60)天,第一次访问这个资源的时候,服务器端也返回了 Expires 字段,并且过期时间是一年后。在没有禁用缓存并且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,不会向服务器请求资源而是直接从浏览器缓存中取。

Cache-Control

  1. s-maxage 同 max-age,覆盖 max-age、Expires,但仅适用于共享缓存,在私有缓存中被忽略。

  2. public 表明响应可以被任何对象(发送请求的客户端、代理服务器等等)缓存。

  3. private 表明响应只能被单个用户(可能是操作系统用户、浏览器用户)缓存,是非共享的,不能被代理服务器缓存。

  4. no-cache 强制所有缓存了该响应的用户,在使用已缓存的数据前,发送带验证器的请求到服务器。不是字面意思上的不缓存。

  5. no-store 禁止缓存,每次请求都要向服务器重新获取数据。

  6. must-revalidate指定如果页面是过期的,则去服务器进行获取。这个指令并不常用,就不做过多的讨论了。

协商缓存

若未命中强缓存,则浏览器会将请求发送至服务器。服务器根据http头信息中的Last-Modified/If-Modified-SinceEtag/If-None-Match来判断是否命中协商缓存。如果命中,则http返回码为304,浏览器从缓存中加载资源。

Last-Modified/If-Modified-Since

浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-Modified是一个时间标识该资源的最后修改时间,例如Last-Modified: Thu,31 Dec 2037 23:59:59 GMT

Last-Modified

当浏览器再次请求该资源时,发送的请求头中会包含If-Modified-Since,该值为缓存之前返回的Last-Modified。服务器收到If-Modified-Since后,根据资源的最后修改时间判断是否命中缓存。

如果命中缓存,则返回http304,并且不会返回资源内容,并且不会返回Last-Modify。由于对比的服务端时间,所以客户端与服务端时间差距不会导致问题。但是有时候通过最后修改时间来判断资源是否修改还是不太准确(资源变化了最后修改时间也可以一致)。于是出现了ETag/If-None-Match

ETag/If-None-Match

Last-Modified/If-Modified-Since不同的是,Etag/If-None-Match返回的是一个校验码(ETag: entity tag)。ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。ETag值的变更则说明资源状态已经被修改。服务器根据浏览器上发送的If-None-Match值来判断是否命中缓存。

既然last-modify可以识别出资源是否更新,为什么还需要ETag呢?

  1. Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

  2. 如果某些文件会被定期生成,但内容并没有任何变化,Last-Modified却改变了,导致该文件被认为更新了,无法使用缓存。

由于ETag是服务器生成的唯一标识符,能够更加准确的控制缓存,所以ETag的优先级高于last-Modified

总结

浏览器请求时的大致过程描述如下:

  1. 浏览器发出http请求前,先根据Cache-ControlExpired 字段判断缓存是否过期(两个字段同时存在的情况下,根据Cache-Control判断,该字段不存在时根据Expired判断)。如果没有过期,浏览器不发出http请求,直接从硬盘缓存中读取资源;如果已过期,则进行第2步逻辑;
  2. 向服务器发出http请求,其中request headers上会携带If-none-matchIf-Modified-since字段,服务器收到请求后,会将If-none-match字段和本地的资源的ETag进行比较,如果二者不相等,则资源已更新,会返回200状态码和资源数据,响应头也会带上最新的资源ETag;如果二者相等,则命中缓存,返回304,浏览器收到响应后会读取本地缓存资源。如果If-none-match字段不存在,服务器会根据if-Modified-since字段进行比对资源的最后修改时间,处理逻辑基本和If-none-match一致。

流程图

补充

浏览器缓存行为还有用户的行为有关!!!

用户操作 Expires/Cache-Control Expires/Cache-Control
地址栏回车 有效 有效
页面链接跳转 有效 有效
新开窗口 有效 有效
前进、后退 有效 有效
F5刷新 无效 有效
Ctrl+F5刷新 无效 无效

扫一扫 关注我的公众号【前端名狮】,更多精彩内容陪伴你!

【前端名狮】