客户端缓存

HTTP 缓存是一种客户端缓存,HTTP 协议的无状态性决定了它必须依靠客户端缓存来解决网络传输效率上的缺陷
一般适用于静态资源。

一 强制缓存

1 Expires

HTTP/1.0 协议中开始提供的 Header

HTTP/1.1 200 OK
Expires: Wed, 8 Apr 2020 07:28:00 GMT

后面跟随一个截至时间参数。当服务器返回某个资源时带有该 Header 的话,意味着服务器承诺截止时间之前资源不会发生变动,浏览器可直接缓存该数据,在此之前不再重新发请求。

缺陷:

  • 客户端可以修改本地时间,导致日期不准
  • 无法描述“不缓存” 的语义。

2 Cache-Control

HTTP/1.1 协议中定义的强制缓存 Header,语义比起 Expires 来说就丰富了很多,如果 Cache-Control 和 Expires 同时存在,并且语义存在冲突(譬如 Expires 与 max-age / s-maxage 冲突)的话,规定必须以 Cache-Control 为准。

HTTP/1.1 200 OK
Cache-Control: max-age=600

它定义了一系列的参数,且允许自行扩展(即不在标准 RFC 协议中,由浏览器自行支持的参数),其标准的参数主要包括有:

  • max-ages-maxage强制缓存,后面跟一个以秒为单位的数字, 表明相对于请求时间(在 Date Header 中会注明请求时间)多少秒以内缓存是有效的, 相对时间避免了 Expires 中采用的绝对时间可能受客户端时钟影响的问题。
    • s-maxage : “s”是 “Share” 的缩写,意味允许被 CDN、代理 等持有的缓存有效时间,用于提示 CDN 这类服务器应在何时让缓存失效。
  • publicprivate:指明是否涉及到用户身份的私有资源
    • public:可以被代理、CDN 等缓存
    • private:只能由用户的客户端进行私有缓存。
  • no-cacheno-store
    • no-cache:令强制缓存完全失效,但此时协商缓存机制依然是生效的;
    • no-store:不强制会话中相同 URL 资源的重复获取,但禁止浏览器、CDN 等以任何形式保存该资源。
  • no-transform禁止资源被任何形式地修改。譬如,某些 CDN、透明代理支持自动 GZip 压缩图片或文本,以提升网络性能。
  • min-fresh 和 only-if-cached:这两个参数是仅用于客户端的请求 Header。
    • min-fresh 建议服务器能返回一个不少于该时间的缓存资源(即包含 max-age 且不少于 min-fresh 的数字)。
    • only-if-cached 表示客户端要求服务端不必给它发送资源的具体内容,此时客户端就仅能使用事先缓存的资源来进行响应,若缓存不能命中,就直接返回 503/Service Unavailable 错误。
  • must-revalidate 和 proxy-revalidate:
    • must-revalidate 表示在资源过期后 (超过了 max-age),一定需要从服务器中进行获取(no cache)
    • proxy-revalidate 用于提示代理、CDN 等设备资源过期后的缓存行为,除对象不同外,语义与 must-revalidate 完全一致。

二 协商缓存

强制缓存是基于时效性的,但无论是人还是服务器,其实多数情况下都并没有什么把握去承诺某项资源多久不会发生变化

“协商缓存” 是基于变化检测的缓存机制,在一致性上会有比强制缓存更好的表现,但需要一次变化检测的交互开销,性能上就会略差一些。

另外,应注意在 HTTP 中协商缓存与强制缓存并没有互斥性,这两套机制是并行工作的:

  • 强制缓存存在时,直接从强制缓存中返回资源,无须进行变动检查;
  • 而当强制缓存超过时效,或者被禁止(no-cache / must-revalidate),协商缓存仍可以正常地工作。

协商缓存有两种变动检查机制:

  • 根据资源的修改时间进行检查:Last-Modified 和 If-Modified-Since
  • 根据资源唯一标识是否发生变化来进行检查:Etag 和 If-None-Match

它们都是靠一组成对出现的请求、响应 Header 来实现的:

1 资源修改时间 - Last-Modified 和 If-Modified-Since

Last-Modified 是服务器的响应 Header,用于告诉客户端这个资源的最后修改时间。

对于带有这个 Header 的资源,当客户端需要再次请求时,会通过 If-Modified-Since 把之前收到的资源最后修改时间发送回服务端

(1) 如果此时服务端发现资源在该时间后没有被修改过,就只要返回一个 304/Not Modified 的响应即可,无须附带消息体,达到节省流量的目的。

HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=600
Last-Modified: Wed, 8 Apr 2020 15:31:30 GMT

(2) 如果此时服务端发现资源在该时间之后有变动,就会返回 200/OK 的完整响应,在消息体中包含最新的资源,如下所示:

HTTP/1.1 200 OK
Cache-Control: public, max-age=600
Last-Modified: Wed, 8 Apr 2020 15:31:30 GMT

Content

2 资源唯一标识 - Etag 和 If-None-Match

Etag 是服务器的响应 Header,用于告诉客户端这个资源的唯一标识。

HTTP 服务器可以根据自己的意愿来选择如何生成这个标识,譬如 Apache 服务器的 Etag 值默认是对文件的索引节点(INode),大小和最后修改时间进行哈希计算后得到的。

对于带有这个 Header 的资源,当客户端需要再次请求时,会通过 If-None-Match 把之前收到的资源唯一标识发送回服务端。

服务端计算后发现资源的唯一标识与上传回来的一致,说明资源没有被修改过,就只要返回一个 304/Not Modified 的响应即可,无须附带消息体,达到节省流量的目的:

HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=600
ETag: "28c3f612-ceb0-4ddc-ae35-791ca840c5fa"

不一致,没修改过,返回 200 带完整资源

HTTP/1.1 200 OK
Cache-Control: public, max-age=600
ETag: "28c3f612-ceb0-4ddc-ae35-791ca840c5fa"

Content

Etag 是 HTTP 中一致性最强的缓存机制,导致文件无法有效使用缓存,譬如,

  • Last-Modified 标注 的最后修改只能精确到秒级,如果某些文件在 1 秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间;
  • 又或者如果某些文件会被定期生成,可能内容并没有任何变化,但 Last-Modified 却改变了

Etag 却又是 HTTP 中性能最差的缓存机制,体现在每次请求时,服务端都必须对资源进行哈希计算,这比起简单获取一下修改时间,开销要大了很多。

Etag 和 Last-Modified 是允许一起使用的,服务器会优先验证 Etag,在 Etag 一致的情况下,再去对比 Last-Modified,这是为了防止有一些 HTTP 服务器未将文件修改日期纳入哈希范围内。

img

三 Nginx 配置强制缓存和协商缓存

nginx 默认开启 Last-Modified 和 etag

location ^~/static/ {
   root html/project
   # 关闭强缓存
   add_header Cache-Control "no-cache, must-revalidate, max-age=0";
   add_header Pragma "no-cache";
   expires off;
   
   # 配置 etag
   # 如果是根据名称的hash生成etag, 用 etag $etag;
   etag on;
   etag_hash on;
   # 哈希算法 sha1 或 MD5 或 CRC32
   etag_hash_method sha1;
}

配置 expire

location ~.*\.(gif|jpg|jpeg|png|bmp|swf)$
  {
          expires      30d;
  }
 
location ~.*\.(js|css)?$
  {
          expires      12h;
  }

参考: