浏览器缓存

当浏览器请求一个网站的时候,会加载各种各样的资源,比如:HTML文档、图片、CSS和JS等文件。对于一些不经常变的内容,浏览器会将他们保存在本地的文件中,下次访问相同网站的时候,直接加载这些资源,加速访问。

所以缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。那么浏览器缓存就是浏览器请求网站留下的资源副本。

缓存的好处

  • 缓解服务器压力(不用每次去请求资源);
  • 提升性能(打开本地资源速度当然比请求回来再打开要快得多);
  • 减少带宽消耗;

浏览器的缓存策略

强缓存

强缓存策略就是给浏览器缓存设置过期时间,超过这个时间之后缓存就是过期,浏览器需要重新请求。

强缓存主要是通过http请求头中的Cache-Control和Expires两个字段控制。

  • expires

expires是一个HTTP/1.0的字段,它给浏览器设置了一个绝对时间,当浏览器时间超过这个绝对时间之后,重新向服务器发送请求。

用法:它描述的是一个绝对时间,用GMT格式的字符串表示

1
Expires: Wed Feb 20 2019 11:25:41 GMT

也可在html文件的meta标签里面使用

1
<meta http-equiv="expires" content="Wed Feb 20 2019 11:25:41 GMT">

缺点:Expires返回的是服务器的时间,但判断的时候用的却是客户端的时间,这就导致Expires很被动,因为客户端时间和服务器端时间可能有误差,或者用户有可能改变客户端的时间,导致缓存时间判断出错,这也是引入Cache-Control:max-age指令的原因之一。

  • cache-control: max-age

为了解决expires存在的问题,Http1.1版本中提出了cache-control:max-age,该字段与expires的缓存思路相同,都是设置了一个过期时间,不同的是max-age设置的是相对缓存时间开始往后的多少秒,因此不再受日期不准确情况的影响。

用法:传入秒数

1
Cache-control: max-age=666

表示资源会在第一次加载之后的 666 秒后过期,需要再次请求。

  • 优先级

max-age>Expires。当两者同时出现在响应头时,Expires将被max-age覆盖。

  • 强缓存在浏览器上的表现

Firefox浏览器对强缓存表现为一个灰色的200状态码。
Chrome浏览器状态码表现为:200 (from disk cache)或是200 OK (from memory cache)

关于缓存是从磁盘中获取还是从内存中获取:Chrome会根据本地内存的使用率来决定缓存存放在哪,如果内存使用率很高,放在磁盘里面,磁盘的使用率很高会暂时放在内存里面。这就可以比较合理的解释了为什么同一个资源有时是from memory cache有时是from disk cache的问题了。

强制缓存的处理策略较为简单粗暴,如果在过期时间内缓存的资源在服务器上更新了,客服端不能及时获取最新的资源。这时怎么办?于是就有了协商缓存。

协商缓存

协商缓存解决了无法及时获取更新资源的问题。它利用http首部的两组字段,对资源做标识.然后由服务器做分析,如果资源未更新,则返回304状态码.那么浏览器则会从缓存中读取资源,否则重新请求资源。

协商缓存是利用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】这两对Header来管理的。

  • Last-Modified与If-Modified-Since

通过告知客户端上次修改资源的时间来控制缓存。

策略步骤:

  1. 浏览器第一次向服务器请求资源,服务器会在返回这个资源的同时,在response的header加上Last-Modified的header,这个header表示这个资源在服务器上的最后修改时间:Last-Modified: Wed Feb 20 2019 14:08:32 GMT
  2. 浏览器之后再向服务器请求这个资源时,在request的header上加上If-Modified-Since的header,这个header的值就是上一次请求时返回的Last-Modified的值:Last-Modified: Wed Feb 20 2019 14:08:32 GMT
  3. 服务器再次收到资源请求时,根据浏览器传过来If-Modified-Since和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回304 Not Modified,但是不会返回资源内容;如果有变化,返回200,就正常返回资源内容。

    当服务器返回304 Not Modified的响应时,response的header中不会再添加Last-Modified的header,因为既然资源没有变化,那么Last-Modified也就不会改变,这是服务器返回304时的response header.

  4. 浏览器收到304的响应后,就会从缓存中加载资源。

  5. 浏览器收到200的响应后,则从服务器加载新资源时,Last-Modified Header在重新加载的时候会被更新,下次请求时,If-Modified-Since会启用上次返回的Last-Modified值。

一般来说,在没有调整服务器时间和篡改客户端缓存的情况下,这两个header配合起来管理协商缓存是非常可靠的,但是有时候也会服务器上资源其实有变化,但是最后修改时间却没有变化的情况,而这种问题又很不容易被定位出来。又或者因为这对首部字段是以秒为单位进行更新,如果浏览器资源以小于该单位进行高频更新的话,则不适合采用该方法。

而当这种情况出现的时候,就会影响协商缓存的可靠性。所以就有了另外一对header来管理协商缓存,这对header就是 ETag 和 If-None-Match。

  • ETag与If-None-Match

通过对资源的进行特殊标识,来判断资源是否过期。

ETag服务器默认采用SHA256算法生成。

策略步骤:

  1. 浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在response的header加上ETag的header,这个header是服务器根据当前请求的资源生成的一个唯一标识,这个唯一标识是一个字符串,ETag: shotcat-66666只要资源有变化这个串就不同,跟最后修改时间没有关系,所以能很好的补充Last-Modified的问题。
  2. 浏览器再次跟服务器请求这个资源时,在request的header上加上If-None-Match的header,这个header的值就是上一次请求时返回的ETag的值If-None-Match: shotcat-66666。
  3. 服务器再次收到资源请求时,根据浏览器传过来If-None-Match和然后再根据资源生成一个新的ETag,如果这两个值相同就说明资源没有变化,否则就是有变化;如果没有变化则返回304 Not Modified,但是不会返回资源内容;如果有变化,则返回200,并正常返回资源内容。与Last-Modified不一样的是,当服务器返回304 Not Modified的响应时,由于ETag重新生成过,response header中还会把这个ETag返回,即使这个ETag跟之前的没有变化。
  4. 浏览器收到304的响应后,就会从缓存中加载资源。
  5. 浏览器收到200的响应后,则从服务器加载新资源时,ETag在重新加载的时候会被更新,下次请求时,If-None-Match会启用上次返回的ETag值。
  • 优先级

ETag与If-None-Match > Last-Modified与If-Modified-Since, 同时存在时, 前者覆盖后者。

有一种场景需要注意:

分布式系统里多台机器间文件的Last-Modified必须保持一致,以免负载均衡到不同机器导致比对失败。

分布式系统尽量关闭掉ETag(每台机器生成的ETag都会不一样)。

强缓存和协商缓存的区别

协商缓存跟强缓存不一样,强缓存不发请求到服务器,所以有时候资源更新了浏览器还不知道,但是协商缓存会发请求到服务器,所以资源是否更新,服务器肯定知道。

启发式缓存

当请求头中确定缓存过期时间的字段一个都没有,此时则会默认触发浏览器启发式缓存:

浏览器会根据响应头中2个时间字段 Date 和 Last-Modified 之间的时间差值,取其值的10%作为缓存时间周期。

缓存的优先级

在缓存策略上:强缓存>协商缓存>启发式缓存

Cache-Control > Expires > ETag > Last-Modified

如果Cache-Control为no-store.则浏览器 所有内容都不会缓存,强制缓存,协商缓存统统都不会触发。

浏览器缓存判断流程

http文件缓存判断机制流程

HTTP中和缓存相关的首部字段

HTTP报文,主要由以下两部分构成:

  1. 首部(header):包含了很多字段,比如:cookie、缓存、报文大小、报文格式等等)。
  2. 主体(body):HTTP请求真正要传输的部分,比如:一个HTML文档,一个js文件。

  3. 通用首部字段

字段名称 说明
Cache-Control 控制缓存具体的行为
Pragma HTTP1.0时的遗留字段,当值为”no-cache”时强制验证缓存
Date 创建报文的日期时间(启发式缓存阶段会用到这个字段)
  1. 响应首部字段
字段名称 说明
ETag 服务器生成资源的唯一标识
Vary 代理服务器缓存的管理信息
Age 资源在缓存代理中存贮的时长(取决于max-age和s-maxage的大小)
  1. 请求首部字段
字段名称 说明
If-Match 条件请求,携带上一次请求中资源的ETag,服务器根据这个字段判断文件是否有新的修改
If-None-Match 和If-Match作用相反,服务器根据这个字段判断文件是否有新的修改
If-Modified-Since 比较资源前后两次访问最后的修改时间是否一致
If-Unmodified-Since 比较资源前后两次访问最后的修改时间是否一致
  1. 实体首部字段
字段名称 说明
Expires 告知客户端资源缓存失效的绝对时间
Last-Modified 资源最后一次修改的时间

用户操作行为对缓存的影响

操作 说明
打开新窗口 如果指定cache-control的值为private、no-cache、must-revalidate,那么打开新窗口访问时都会重新访问服务器。而如果指定了max-age值,那么在此值内的时间里就不会重新访问服务器,例如:Cache-control: max-age=5 表示当访问此网页后的5秒内不会去再次访问服务器.
在地址栏回车 如果值为private或must-revalidate,则只有第一次访问时会访问服务器,以后就不再访问。如果值为no-cache,那么每次都会访问。如果值为max-age,则在过期之前不会重复访问。
按后退按扭 如果值为private、must-revalidate、max-age,则不会重访问,而如果为no-cache,则每次都重复访问.
按刷新按扭 无论为何值,都会重复访问.(可能返回状态码:200、304,这个不同浏览器处理是不一样的,FireFox正常,Chrome则会启用缓存(200 from cache))
按强制刷新按钮 当做首次进入重新请求(返回状态码200)

合理应用缓存

于大部分的场景都可以使用强缓存配合协商缓存解决,但是在一些特殊的地方可能需要选择特殊的缓存策略

  • 对于某些不需要缓存的资源,可以使用 Cache-control: no-store ,表示该资源不需要缓存
  • 对于频繁变动的资源,可以使用 Cache-Control: no-cache 并配合 ETag 使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新。
  • 对于代码文件来说,通常使用 Cache-Control: max-age=31536000 并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件。

参考资料