缓存过程分析
浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中,简单的过程如下图:
由上图我们可以知道:
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
以上两点结论就是浏览器缓存机制的关键,它确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了,本文也将围绕着这点进行详细分析。为了方便大家理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强缓存和协商缓存。
强缓存 | 协商缓存 | |
---|---|---|
HTTP状态码 | 200 | 304 |
缓存位置 | 浏览器 | 浏览器 |
谁来决定 | 浏览器 | 服务器 |
强缓存
强缓存不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache
或from memory cache
。
浏览器中强缓存的response header
不是来自服务器,而是来自之前缓存的header
。
可选值 | 优先级 | 优缺点 | |
---|---|---|---|
Cache-Control | max-age: xx秒,相对时间,强缓存必备 no-cache:不直接使用缓存,开始服务器新鲜度判定 no-store:每次都下载最新资源 public/private:是否只能被单个用户保存 |
高 | 无 |
Expires | GMT时间 | 低 | 服务器和本地时间不一定统一 |
强制缓存的情况主要有三种,如下:
不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)。
存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存。
存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果。
强缓存可以通过设置两种HTTPHeader
实现:Expires
和Cache-Control
。
Expires
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age +
请求时间,需要和Last-modified
结合使用。Expires
是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。
Expires
是HTTP/1的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT
表示资源会在Wed, 22 Oct 2018 08:41:00 GMT
后过期,需要再次请求。
Cache-Control
在HTTP/1.1中,Cache-Control
是最重要的规则,主要用于控制网页缓存。比如当Cache-Control:max-age=300
时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。
Cache-Control
可以在请求头或者响应头中设置,并且可以组合使用多种指令:
指令 | 说明 |
---|---|
public | 表示当前响应可以被任何设备(发送请求的客户端、代理服务器等等)缓存 |
private | 表示当前响应数据是单个用户所独占的,只能被客户端缓存,不能被代理服务器缓存。Cache-Control的默认取值。 |
no-store | 真正的禁止缓存,任何设备都不允许缓存,每次请求都需要向服务端重新获取数据。 |
no-cache | 它并非禁止缓存,而是强制在使用已缓存数据之前,需要去服务端验证一下是否可以使用缓存数据。 |
max-age | 指定缓存的有效时间,单位为秒。其值是任意整数,0 和负数表示缓存过期,正数值加上当前响应头中的 Date 首部值即为过期时间。 |
s-maxage | 同max-age作用一样,只在代理服务器中生效(比如CDN缓存)。比如当s-maxage=60时,在这60秒中,即使更新了CDN的内容,浏览器也不会进行请求。max-age用于普通缓存,而s-maxage用于代理缓存。s-maxage的优先级高于max-age。如果存在s-maxage,则会覆盖掉max-age和Expires header。 |
max-stale | 只用于请求,表示客户端仍然愿意接受过期缓存,只要过期时间没超过指定时间,如果未指定时间,则表示任何过期的时间。 |
min-fresh | 只用于请求,表示客户端愿意接受还剩余多少秒过期的缓存。 |
Expires和Cache-Control两者对比
其实这两者差别不大,区别就在于Expires
是http1.0的产物,Cache-Control
是http1.1的产物,两者同时存在的话,Cache-Control
优先级高于Expires
;在某些不支持HTTP1.1的环境下,Expires
就会发挥用处。所以Expires
其实是过时的产物,现阶段它的存在只是一种兼容性的写法。
强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
协商缓存
协商缓存都是成对出现的。
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
协商缓存生效,返回304和Not Modified
协商缓存失效,返回200和请求结果
协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。
可选值 | 优先级 | 优缺点 | |
---|---|---|---|
Last-Modify If-Modify-Since |
GMT时间 | 依次比较,排序靠后 | 修改并不意味着改变 秒级判断 |
ETag If-Mone-Match |
校验值 | 依次比较,先比较 | 使用系统默认的Hash算法,在分布式部署中会导致不同服务器的ETag值不一致 |
Last-Modified和If-Modified-Since
浏览器在第一次访问资源时,服务器返回资源的同时,在response header
中添加Last-Modified
的header
,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header
。
浏览器下一次请求这个资源,浏览器检测到有Last-Modified
这个header
,于是添加If-Modified-Since
这个header
,值就是Last-Modified
中的值;服务器再次收到这个资源请求,会根据If-Modified-Since
中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since
的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200。
但是Last-Modified
存在一些弊端:
- 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成
Last-Modified
被修改,服务端不能命中缓存导致发送相同的资源 - 因为
Last-Modified
只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在HTTP / 1.1出现了ETag
和If-None-Match
。
ETag和If-None-Match
Etag
是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag
就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag
值放到request header
里的If-None-Match
里,服务器只需要比较客户端传来的If-None-Match
跟自己服务器上该资源的ETag
是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag
匹配不上,那么直接以常规GET 200
回包形式将新的资源(当然也包括了新的ETag
)发给客户端;如果ETag
是一致的,则直接返回304知会客户端直接使用本地缓存即可。
两者之间对比:
首先在精确度上,Etag
要优于Last-Modified
。
Last-Modified
的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified
其实并没有体现出来修改,但是Etag / If-None-Match
每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified
也有可能不一致。
第二在性能上,Etag / If-None-Match
要逊于Last-Modified / If-Modified-Since
,毕竟Last-Modified / If-Modified-Since
只需要记录时间,而Etag / If-None-Match
需要服务器通过算法来计算出一个hash
值。
第三在优先级上,服务器校验优先考虑Etag / If-None-Match
,同时存在则只有Etag / If-None-Match
生效。
总结
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control
)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since
和Etag / If-None-Match
),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。具体流程图如下:
如果什么缓存策略都没设置,浏览器会采用一个启发式的算法,通常会取响应头中的Date
减去Last-Modified
值的10%作为缓存时间。
最佳优化策略-消灭304
最佳优化策略:因为协商缓存本身也有http请求的损耗,所以最佳优化策略是要尽可能的将静态文件存储为较长的时间,多利用强缓存而不是协商缓存,即消灭304。
但是给文件设置一个很长的Cacha-Control
也会带来其他的问题,最主要的问题是静态内容更新时,用户不能及时获得更新的内容。这时候就要使用hash
的方法对文件进行命名,通过每次更新不同的静态文件名来消除强缓存的影响。