缓存

一、缓存机制#

  浏览器的缓存机制就是 HTTP 缓存机制,其机制是根据 HTTP 报文的缓存标识进行的。HTTP 报文分为两种:HTTP 请求(Request)报文和 HTTP 响应(Response)报文。HTTP 缓存分为两种,即强缓存和协商缓存,强缓存利用的是 ExpiresCache-Control,而协商缓存利用的是 Last-Modified / If-Modified-SinceETag / If-None-Match (可阅读书籍《图解HTTP》进行了解)。

  当浏览器第一次向服务器发起请求并得到响应结果时,会根据响应报文中 HTTP 头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中:

首次发送请求

由上图我们可以知道:

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

二、强缓存#

强缓存不会向服务器发送请求,直接向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种(暂不分析协商缓存过程),如下:

  • 1、不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致),如下图:

    强缓存情况1

  • 2、存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存(暂不分析),如下图:

    强缓存情况2

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

    强缓存情况3

note

那么强制缓存的缓存规则是什么?

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

2.1 Expires#

  Expires 是 HTTP/1.0 控制网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间,即再次发起该请求时,如果客户端的时间小于 Expires 的值时,直接使用缓存结果。

Expires: Wed, 11 May 2020 07:20:00 GMT
tip

Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间或者处于不同的时区,可能会造成缓存失效。

2.2 Cache-Control#

Cache-Control 出现于 HTTP/1.1,优先级高于 Expires,表示的是相对时间。主要取值为:

  • public:所有内容都将被缓存(客户端和代理服务器都可缓存)
  • private:所有内容只有客户端可以缓存,Cache-Control 的默认取值
  • no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
  • no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
  • max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
note

注意:no-cache 并不是说浏览器就不再缓存数据,只是浏览器在使用缓存数据时,需要先确认一下数据是否还跟服务器保持一致,也就是协商缓存。而no-store 才表示不会被缓存,即不使用强制缓存,也不使用协商缓存。

cache-control: max-age=31536000
note

浏览器的缓存存放在哪里,如何在浏览器中判断强制缓存是否生效?

2.3 from disk cache和from memory cache#

Chrome 的网络请求的Size会出现三种情况 from disk cache(磁盘缓存)、from memory cache(内存缓存)、以及资源大小数值

状态类型说明
200from memory cache不请求网络资源,资源在内存中,一般脚本、字体、图片会存在内存中
200from disk cache不请求网络资源,资源在磁盘中,一般非脚本会存在磁盘中,如css等
200资源大小数值从服务器下载最新资源
304报文大小请求服务端发现资源没有更新,使用本地资源

浏览器读取缓存的顺序为 memory –> disk

三、协商缓存#

  协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

  • 1、协商缓存生效,返回304,如下

    协商缓存1

  • 2、协商缓存失效,返回200和请求结果结果,如下

    协商缓存2

同样,协商缓存的标识也是在响应报文的 HTTP 头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-SinceEtag / If-None-Match,其中 Etag / If-None-Match 的优先级比 Last-Modified / If-Modified-Since 高。

3.1 Last-Modified / If-Modified-Since#

Last-Modified 表示本地文件最后修改日期,浏览器会在 request header 加上 If-Modified-Since(上次返回的 Last-Modified 的值),询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。

// Response Header
...
last-modified: Wed, 11 May 2020 07:20:00 GMT
...
// Request Header
...
if-modified-since: Wed, 11 May 2020 07:20:00 GMT
...

但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag

3.2 Etag / If-None-Match#

Etag就像一个指纹,资源变化都会导致ETag变化,跟最后修改时间没有关系,ETag可以保证每一个资源是唯一的

If-None-Match的header会将上次返回的Etag发送给服务器,询问该资源的Etag是否有更新,有变动就会发送新的资源回来。

// Response Header
...
etag: W/"xxx-xxxxxxxxxxxx"
...
// Request Header
...
if-node-match: W/"xxx-xxxxxxxxxxxx"
...

ETag 的优先级比 Last-Modified 更高。

note

既生 Last-Modified,何生 ETag

  • 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
  • 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
  • 某些服务器不能精确的得到文件的最后修改时间。

总结#

  强制缓存优先于协商缓存进行,若强制缓存(ExpiresCache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-SinceEtag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存,主要过程如下:

核心逻辑图