浏览器资源缓存机制总结

浏览器缓存是大型网站架构(缓存&CDN)重要的考量环节,也是考核前端工程师对HTTP协议理解的重要知识点,下面对浏览器缓存机制做个总结。

缓存机制

浏览器读取资源文件的流程如下:

  • 浏览器发送请求前,根据请求头的expires和cache-control判断是否命中(包括是否过期)强缓存策略,如果命中,直接从缓存获取资源,并不会发送请求。如果没有命中,则进入下一步;
  • 没有命中强缓存规则,浏览器会发送请求,根据请求头的last-modified和etag判断是否命中协商缓存,如果命中,直接从缓存获取资源。如果没有命中,则进入下一步;
  • 如果前两步都没有命中,则直接从服务端获取资源;

逻辑流程图如下:

浏览器缓存

强缓存

强缓存即不会向服务器发送请求,直接从缓存中读取资源。强制缓存的情况主要有三种:

  • 不存在缓存结果和缓存标识,直接向服务器发送请求:

浏览器缓存

  • 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果:

浏览器缓存

  • 存在缓存标识和缓存结果,但是已经失效,则使用协商缓存:

浏览器缓存

强缓存的协商规则是通过HTTP响应头和请求头来进行的。当浏览器向服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是ExpiresCache-Control,其中Cache-Control优先级比Expires高。

Expires

Expires用来指定资源到期的时间(服务器端的具体的时间点)。Expires=max-age + 请求时间,需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

1
2
Cache-Control: max-age=93312000
Expires: Mon, 22 Aug 2022 09:32:06 GMT

Expires 是 HTTP/1 的产物,浏览器会将本地时间与Expires时间进行判断,这样就容易受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

示例:

浏览器缓存

Cache Control

在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存。示例:

浏览器缓存

以上资源请求响应数据中,cache-control的值为max-age=93312000,从字面上面就能够猜出它的含义,就是服务端告诉浏览器此信息可不可以缓存,以什么样的策略进行缓存;cache-control有哪些类型的值呢?

  • private:浏览器可以缓存
  • public:浏览器和代理服务器可以缓存,因此我们中间层的服务节点,发现cache-control的值为private时,就认可只有发起请求的浏览器能够缓存,作为代理服务节点是不能够缓存的。如为public时代理服务器也可以缓存。
  • max-age缓存的内容将在xxx秒后失效
  • no-store不缓存请求的任何返回内容
  • no-cache强制向服务器端再验证一次,no-cache会缓存请求返回的内容,而no-store不缓存;但no-cache时,在下次用缓存的内容时,需要向服务器验证一下,缓存到底能不能用

在逻辑流程图中,有一个环节就是重新验证,就是验证缓存内容是否有效,那验证逻辑是什么,怎么验证

ETag验证

浏览器缓存

ETag是资源唯一标识, 一般会把请求的内容做md5加密,返回唯一的标识;服务器端会把ETag的值一起返回给浏览器;浏览器会把ETag存储下来。

1
ETag: "AFDC802903A35894189B4B1107811066"

浏览器下一次请求时将Etag一并带过去给服务器,服务器只需要比较浏览器传来的ETag跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对浏览器而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200状态码形式将新的资源(当然也包括了新的ETag)发给浏览器;如果ETag是一致的,则直接返回304状态码浏览器直接使用本地缓存即可。

那么浏览器是如何把标记在资源上的ETag传回给服务器的呢?请求报文中有两个首部字段可以带上ETag值:

  • If-None-Match: ETag-value 告诉服务端如果ETag没匹配上需要重发资源数据,否则直接回送304和响应报头即可,当前各浏览器均是使用的该请求首部来向服务器传递保存的ETag值。

浏览器缓存

  • If-Match: ETag-value 告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给浏览器。否则服务器直接忽略该字段。

需要注意的是,如果资源是走分布式服务器(比如CDN)存储的情况,需要这些服务器上计算ETag唯一值的算法保持一致,才不会导致明明同一个文件,在服务器A和服务器B上生成的ETag却不一样。

ETag可以更加精确的判断资源是否被修改,可以识别一秒内多次修改的情况;不存在版本问题,每次请求都回去服务器进行校验。单计算ETag值需要性能损耗,同时分布式服务器存储的情况下,计算ETag的算法如果不一样,会导致浏览器从一台服务器上获得页面内容后到另外一台服务器上进行验证时发现ETag不匹配的情况。

Last-Modified验证

服务器将资源传递给浏览器时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给浏览器。

1
Last-Modified: Sat, 07 Sep 2019 09:31:44 GMT

浏览器会为资源标记上该信息,下次再次请求时,会把该信息附带在请求头中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码内容为空

1
if-Modified-Since: Sat, 07 Sep 2019 09:31:44 GMT

如果两个时间不一致,则服务器会发回该资源并返回200状态码,和第一次请求时类似。这样保证不向浏览器重复发出资源,也保证当服务器有变化时,浏览器能够得到最新的资源。

有用就打赏一下作者吧!