Skip to main content

HTTP之缓存

· 11 min read
fish

缓存已获取的资源能够有效的提升网站与应用的性能,Web 缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间。借助 HTTP 缓存,Web 站点变得更具有响应性。

缓存必须是由服务端设置响应头才会生效,一般客户端的请求头中只有设置了cache-control为:no-store | no-cache | max-age=0才会生效(也就是客户端不想走强缓存的时候生效)


我们先来看下与缓存相关的HTTP首部字段

请求首部字段

字段名称说明
Cache-Control控制缓存的行为
PragmaHTTP/1.0遗留物,Pragma: no-cache等同于Cache-Control: no-cache
If-Match比较ETag是否一致(如果匹配中)
If-None-Match比较ETag是否不一致(如果未匹配中)
If-Modified-Since比较Last-Modified时间是否一致
If-Unmodified-Since比较Last-Modified时间是否一致

响应首部字段

字段名称说明
Cache-Control控制缓存的行为
PragmaHTTP/1.0遗留物,Pragma: no-cache等同于Cache-Control: no-cache
Expires过期时间
Last-Modified资源最后一次修改时间
ETag资源的特定版本的标识符
Vary决定下一个请求头,应该用一个缓存的回复还是向源服务器请求一个新的回复
Pragma

Pragma是一个在 HTTP/1.0 中规定的通用首部,这个首部的效果依赖于不同的实现,所以在“请求-响应”链中可能会有不同的效果。当该字段值为no-cache的时候,与 Cache-Control: no-cache 效果一致,表示禁用缓存。

Expires

Expires的值对应一个GMT(格林尼治时间)来告诉浏览器资源缓存过期时间,如果还没到该时间点则不发请求。

Expires所定义的缓存时间是相对服务器上的时间而言的,要求客户端和服务器端的时钟严格同步。客户端的时间是可以修改的,如果服务器和客户端的时间不统一,这就导致有可能出现缓存提前失效的情况,存在不稳定性。 面对这种情况,HTTP1.1引入了 Cache-Control 头来克服 Expires 头的限制。

如果在Cache-Control响应头设置了 "max-age" 或者 "s-max-age",那么 Expires 头会被忽略。


Pragma 和 Expires 现在主要用来用来向后兼容只支持 HTTP/1.0 协议的缓存服务器。

Cache-Control

Cache-Control 是一个通用首部字段,被用于在http请求和响应中。缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中

客户端可以在HTTP请求中使用的标准 Cache-Control 指令

Cache-Control 属性值说明
no-cache告知服务器不直接使用缓存,需要验证
no-store禁止缓存,资源不会保存到缓存/临时文件
no-transform表明客户端希望获取实体数据没有被转换
max-age=[seconds]表明客户端愿意接收一个带缓存时间的资源
max-stale[=seconds]表明客户端愿意接收一个已经过期的资源。可以设置一个可选的秒数,表示响应不能已经过时超过该给定的时间
min-fresh=[seconds]表明客户端希望接收一个能在seconds秒内被更新过的资源
only-if-cached表明客户端只接受已缓存的响应,并且不向原服务器检查是否有更新

服务器可以在响应中使用的标准 Cache-Control 指令

Cache-Control 属性值说明
no-cache缓存但重新验证(要求协商缓存验证)
no-store不使用任何缓存,资源不应该存储到客户端缓存/临时文件
no-transform不能对资源进行转换或转变。HTTP头/实体 不能由代理修改
public表明响应可以被任何对象(包括:发送请求的客户端,代理服务器等)缓存,即使是通常不可缓存的内容(例如,该响应没有max-age指令或Expires消息头)
private表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)
must-revalidate一旦资源过期(比如已经超过max-age),必须向原服务器验证请求
proxy-revalidate与must-revalidate类似,但仅适用于共享缓存(如代理)
max-age=[seconds]设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)
s-maxage=[seconds]覆盖max-age,但仅限于共享缓存(如代理)

Cache-Control 允许自由组合可选值,例如:

Cache-Control: public, max-age=3600, must-revalidate

强缓存

客户端不会向服务器发送任何请求,直接从本地缓存中读取文件并返回 Status Code: 200 OK。 服务器返回 ExpiresCache-Control: max-age=31536000 时会生效

优先顺序 from memory cachefrom disk cache,最后是请求网络资源

缓存验证(协商缓存)

当缓存的文档过期后,需要进行缓存验证或者重新获取资源。只有在服务器返回 Last-ModifiedETag 时才会进行验证。 如果缓存的响应头信息里 Cache-control 含有 must-revalidateno-cache 值,也会发起缓存验证

1. Last-Modified

Last-Modified 响应头可以作为一种弱校验器,其值为资源最后更改的时间。 Last-Modified 称为弱校验器 因为它不够精准:

  • 只能精确到一秒,在1秒内资源改变,因为时间比较相同,而不会去更新最新的资源
  • 一个资源被修改了,但其实际内容根本没发生改变,会因为 Last-Modified 时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)
Last-Modified: Tue Aug 20 2019 15:47:05 GMT

如果响应头里含有Last-Modified,客户端可以在后续的请求首部带上 If-Modified-Since 或者 If-Unmodified-Since 验证Last-Modified

If-Modified-Since:若客户端传递的时间值与服务器上该资源最后修改时间是一致(If-Modified-Since==Last-Modified),则说明该资源没有被修改过,直接返回304状态码,内容为空,这样就节省了传输数据量 。如果两个时间不一致,则服务器会发回该完整资源并返回200状态码,和第一次请求时类似。这样保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。一个304响应比一个静态资源通常小得多,这样就节省了网络带宽。

当前各浏览器均是使用 If-Modified-Since 请求首部来向服务器传递保存的 Last-Modified 值

If-Unmodified-Since:该值告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端

2. ETag

ETag可以解决上述 Last-Modified 可能存在的不准确的问题,因而称它为强校验器 ETag值为一个唯一标识符,服务器利用文件修改时间、文件大小和inode号等通过某种算法计算得出

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

如果响应头里含有ETag,客户端可以在后续的请求首部带上 If-Match 或者 If-None-Match 验证 ETag

If-None-Match:如果服务器发现 ETag 不匹配(If-None-Match != ETag),那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端。若客户端传递的 etag 跟服务器的etag一致,则直接返回304知会客户端直接使用本地缓存即可; 当前各浏览器均是使用If-Match请求首部来向服务器传递保存的 ETag 值

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

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