缓存

164 阅读9分钟

1 分类

HTTP缓存,也叫浏览器缓存

  • 广义: 私有缓存 共享缓存
  • 狭义: 用户浏览缓存 中间代理服务器缓存 网关缓存(CDN) 数据库缓存(后端)

2 缓存目标

  • 一个检索请求的成功响应: 对于 GET请求,响应状态码为:200,则表示为成功。一个包含例如HTML文档,图片,或者文件的响应;
  • 不变的重定向: 响应状态码:301;
  • 可用缓存响应:响应状态码:304,这个存在疑问,Chrome会缓存304中的缓存设置,Firefox;
  • 错误响应: 响应状态码:404 的一个页面;
  • 不完全的响应: 响应状态码 206,只返回局部的信息;
  • 除了 GET 请求外,如果匹配到作为一个已被定义的cache键名的响应;

3 强缓存与协商缓存

  • 强缓存: 在缓存未过期的时候访问资源,获取缓存中的资源,返回200
  • 协商缓存: 缓存过期之后,像服务器校验缓存是否仍然可用,可用返回304,不可用返回200,新的资源以及缓存方式
  • 启发式缓存:响应头中没有和缓存过期相关的设置,这是时候取(Date-LastModified)*10%的值作为缓存有效期时间。

4 相关字段

1 通用首部字段

字段名称说明
Cache-control控制缓存的具体行为
PragmaHTTP/1.0遗留字段,当值为"no-cache"时表示需要缓存服务器验证缓存是否可用
Date表示创建报文的时间(启发式缓存阶段用到)

2 响应首部字段

字段名称说明
Etag服务器生成资源的唯一标识
Vary代理服务器缓存的管理信息
Age资源在缓存服务器中贮存的时长

3 请求首部字段

字段名称说明
If-Match条件请求,携带上一次请求的资源的Etag值,服务器判断文件是否被修改
If-None-Match当缓存过期时,服务器使用该字段验证文件是否有新的修改
If-Modified-Since当缓存过期时,验证这段时间内文件是否有修改
If-Unmodified-Since验证文件是否有修改

4 实体首部字段

字段名称说明
Expireshttp/1.0遗留,缓存过期的绝对时间
Last-Modified文件最后修改的时间

5 浏览器缓存控制 字段详解

1 Cache-Control

作为请求首部时

指令参数说明
no-cache告知代理服务器不直接使用缓存,而是向源服务器验证,之后再返回
no-store所有内容都不会被缓存
max-agedelta-seconds告知服务器客户端希望接收一个存在时间不超过delta-second时间的资源
max-stale[=delta-second]告知(代理)服务器客户端愿意接收一个超过缓存时间的资源,如果有定义delta-second,则表示可以超出delta-second秒,如果没定义表示可以超出任意时间
min-freshdelta-second告知(代理)服务器,客户端希望接收一个在小于delta-second时间被更新过的资源
no-transform告知代理服务器,客户端希望获取实体数据没有被转换过,如GZIP压缩
only-if-cache告知代理服务器,客户端希望获取缓存的内容(若有),若没有不必去源服务器获取返回504状态码
cache-extention自定义拓展值,如果服务器不识别则会忽略

作为响应首部时

指令参数说明
public表示任何情况下都可以缓存该资源
private[="field-name"]表明报文中的某些用户做缓存使用,其他用户不能缓存
no-cache不直接使用缓存,向服务器发起校验请求
no-store所有内容不缓存
no-transform告知客户端缓存时不能对文件实体数据做任何改变
must-revalidation如果缓存失效, 强制重新向服务器(或代理)发起验证(因为max-stale等字段可能改变缓存的失效时间)
proxy-revalidation和上述类似,但只能应用于共享缓存,如代理
max-agedelta-seconds表示缓存有效期
s-maxagedelta-seconds同上,依赖public设置,覆盖max-age,只在代理服务器上有效,最大Age值
cache-extention自定义拓展值,如果服务器不识别则会忽略

注意

客户端总是采用最保守的缓存策略,会选择最短的有效时间作为缓存有效期。

2 Pragma

Pragma:no-cache

只能这样设置,no-cache。在请求头中和cache-control作用一样,在响应头中不一定。为了向后兼容,优先级最高。

3 Expires

Expires:Wed, 05 Apr 2017 00:55:35 GMT

即到期时间, 以服务器时间为参考系, 其优先级比 Cache-Control:max-age 低, 两者同时出现在响应头时, Expires将被后者覆盖. 如果Expires, Cache-Control: max-age, 或 Cache-Control:s-maxage 都没有在响应头中出现, 并且也没有其它缓存的设置, 那么浏览器默认会采用一个启发式的算法, 通常会取响应头的Date_value - Last-Modified_value值的10%作为缓存时间。

4 ETag

ETag:"fcb82312d92970bdf0d18a4eca08ebc7efede4fe"

实体标签, 服务器资源的唯一标识符, 浏览器可以根据ETag值缓存数据, 节省带宽. 如果资源已经改变, etag可以帮助防止同步更新资源的相互覆盖. ETag 优先级比 Last-Modified 高.

5 If-Match

If-Match: ETag_value 或者 If-Match: ETag_value, ETag_value, …

缓存校验字段, 其值为上次收到的一个或多个etag 值. 常用于判断条件是否满足, 如下两种场景:

  • 对于 GET 或 HEAD 请求, 结合 Range 头字段, 它可以保证新范围的请求和前一个来自相同的源, 如果不匹配, 服务器将返回一个416(Range Not Satisfiable)状态码的响应.
  • 对于 PUT 或者其他不安全的请求, If-Match 可用于阻止错误的更新操作, 如果不匹配, 服务器将返回一个412(Precondition Failed)状态码的响应.

6 If-None-Match

If-None-Match: ETag_value 或者 If-None-Match: ETag_value, ETag_value, …

存校验字段, 结合ETag字段, 常用于判断缓存资源是否有效, 优先级比If-Modified-Since高.

  • 对于 GET 或 HEAD 请求, 如果其etags列表均不匹配, 服务器将返回200状态码的响应, 反之, 将返回304(Not Modified)状态码的响应. 无论是200还是304响应, 都至少返回 Cache-Control, Content-Location, Date, ETag, Expires, and Vary 中之一的字段.
  • 对于其他更新服务器资源的请求, 如果其etags列表匹配, 服务器将执行更新, 反之, 将返回412(Precondition Failed)状态码的响应.

7 Last-Modified

Last-Modified: Tue, 04 Apr 2017 10:01:15 GMT

用于标记请求资源的最后一次修改时间, 格式为GMT(格林尼治标准时间). 如可用 new Date().toGMTString()获取当前GMT时间. Last-Modified 是 ETag 的fallback机制, 优先级比 ETag 低, 且只能精确到秒, 因此不太适合短时间内频繁改动的资源. 不仅如此, 服务器端的静态资源, 通常需要编译打包, 可能出现资源内容没有改变, 而Last-Modified却改变的情况.

8 If-Modified-Since

语法同上

缓存校验字段, 其值为上次响应头的Last-Modified值, 若与请求资源当前的Last-Modified值相同, 那么将返回304状态码的响应, 反之, 将返回200状态码响应.

9 If-Unmodified-Since

语法同上

表示资源未修改则正常执行更新, 否则返回412(Precondition Failed)状态码的响应. 常用于如下两种场景:

  • 不安全的请求, 比如说使用post请求更新wiki文档, 文档未修改时才执行更新.
  • 与 If-Range 字段同时使用时, 可以用来保证新的片段请求来自一个未修改的文档.

10 Age

Age:2383321
Date:Wed, 08 Mar 2017 16:12:42 GMT

出现此字段, 表示命中代理服务器的缓存. 它指的是代理服务器对于请求资源的已缓存时间,代理服务器在2017年3月8日16:12:42时向源服务器发起了对该资源的请求, 目前已缓存了该资源2383321秒.

11 Date

指的是响应生成的时间. 请求经过代理服务器时, 返回的Date未必是最新的, 通常这个时候, 代理服务器将增加一个Age字段告知该资源已缓存了多久.

12 Vary

Vary:Accept-Encoding,User-Agent

对于服务器而言, 资源文件可能不止一个版本, 比如说压缩和未压缩, 针对不同的客户端, 通常需要返回不同的资源版本. 比如说老式的浏览器可能不支持解压缩, 这个时候, 就需要返回一个未压缩的版本; 对于新的浏览器, 支持压缩, 返回一个压缩的版本, 有利于节省带宽, 提升体验. 那么怎么区分这个版本呢, 这个时候就需要Vary了.

服务器通过指定Vary: Accept-Encoding, 告知代理服务器, 对于这个资源, 需要缓存两个版本: 压缩和未压缩. 这样老式浏览器和新的浏览器, 通过代理, 就分别拿到了未压缩和压缩版本的资源, 避免了都拿同一个资源的尴尬.

如上设置, 代理服务器将针对是否压缩和浏览器类型两个维度去缓存资源. 如此一来, 同一个url, 就能针对PC和Mobile返回不同的缓存内容.

怎么让浏览器不缓存静态资源

实际上, 工作中很多场景都需要避免浏览器缓存, 除了浏览器隐私模式, 请求时想要禁用缓存, 还可以设置请求头: Cache-Control: no-cache, no-store, must-revalidate .

当然, 还有一种常用做法: 即给请求的资源增加一个版本号, 如下:

<link rel="stylesheet" type="text/css" href="../css/style.css?version=1.8.9"/>

不仅如此, HTML也可以禁用缓存, 即在页面的\节点中加入\标签, 代码如下:

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>

上述虽能禁用缓存, 但只有部分浏览器支持, 而且由于代理不解析HTML文档, 故代理服务器也不支持这种方式.