HTTP 缓存机制
Web 缓存大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存。
浏览器缓存也包含很多内容: HTTP
缓存、 indexDB
、 cookie
、 localstorage
等等。
这里要说的是 http
缓存。
使用缓存的好处
- 减少了冗余的数据传输
- 缓解了网络瓶颈的问题
- 降低了对原始服务器的要求
- 降低了距离时延
术语
缓存命中率:从缓存中得到数据的请求数与所有请求数的比率。理想状态是越高越好。
过期内容:超过设置的有效时间,被标记为“陈旧”的内容。通常过期内容不能用于回复客户端的请求,必须重新向源服务器请求新的内容或者验证缓存的内容是否仍然准备。
验证:验证缓存中的过期内容是否仍然有效,验证通过的话刷新过期时间。
失效:失效就是把内容从缓存中移除。当内容发生改变时就必须移除失效的内容。
机制
策略
1)缓存存储策略
缓存存储策略决定了客户端是否应该存储 http
的 response
。与缓存存储有关的 http header 主要为 response header
中的 Cache-Control
。该 header
有下面几个对应的值: Public
、 Private
、 no-cache
、 max-age
、 no-store
。除了 no-store
,其它几种都会表明 response
应该被客户端缓存。
指令 | 说明 |
---|---|
Public | 所有内容都将被缓存(客户端和代理服务器都可缓存) |
Private | 内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存) |
max-age = xxx (xxx is numeric) | 缓存的内容将在 xxx 秒后失效,失效前可以直接使用本地缓存,失效后必须向服务器确认资源是否已经改变。 |
no-store | 完全不在客户端缓存 |
no-cache | 可以认为等同于 max-age=0 的情况,即将 response 缓存在客户端,但是之后每次都向服务器确认资源是否已经改变 |
通过 Cache-Control
: Public
设置我们可以将 HTTP
响应数据存储到本地,但此时并不意味着后续浏览器会直接从缓存中读取数据并使用, 因为它无法确定本地缓存的数据是否可用(可能已经失效),需通过缓存过期策略来判断
2)缓存过期策略
缓存过期策略决定了客户端存储在本地的缓存数据是否已过期,如未过期则可以直接使用本地存储的数据,否则就需要发请求到服务端尝试重新获取数据。 与缓存过期策略有关的 http header 为 Expires
。
Expires
表示缓存数据有效的绝对时间,告诉客户端到了这个时间点后本地缓存就失效了,在这个时间内客户端可以不请求服务器而直接从本地缓存中使用已存储的结果。
需要注意的是: no-cache
和 max-age=xxx
的优先级高于 Expires
,当它们同时存在的时候,后者会被覆盖掉。其次, 缓存数据过期只是告诉客户端不能再直接从本地读取缓存了,而是需要再发一次请求到服务器去确认。具体什么情况下本地存储的数据还可以继续使用就与缓存对比策略有关了。
3)缓存对比策略
将缓存在客户端的数据标识发往服务端,服务端通过标识来判断客户端 缓存数据是否仍有效,进而决定是否要重发数据。 客户端检测到数据过期或浏览器刷新后,会重新发起一个 http 请求到服务器,服务器此时并不急于返回数据,而是看请求头有没有带标识( If-Modified-Since
、 If-None-Match
)过来,如果判断标识仍然有效,则返回 304 告诉客户端取本地缓存数据来用即可(这里要注意的是你必须要在首次响应时输出相应的头信息( Last-Modified
、 ETags
)到客户端)。 本地缓存数据即使被认为过期,并不等于数据从此就没用了。
缓存过期取值
存储策略里面 no-cache
等同于 max-age=0
,假如服务端返回的响应中没有指明 max-age
、 no-cache
或 Expires
时,客户端是否会缓存 http response 呢 ?通过 Fiddler
、Charles 等抓包工具可以发现,客户端一样会进行缓存
其取值值为响应头中的 Date
与 Last-Modified
之间的差值的 10%作为缓存有效时间
在 Fiddler
的 Caching
面板中可以看到
HTTP/1.1 Cache-Control Header is present: private
HTTP Last-Modified Header is present: Tue, 08 Nov 2016 06:59:00 GMT
No explicit HTTP Cache Lifetime information was provided.
Heuristic expiration policies suggest defaulting to: 10% of the delta between Last-Modified and Date.
That's '05:15:02' so this response will heuristically expire 2016/11/11 0:46:01.
用一副图来表示
缓存的控制
1)强制缓存
可以通过 Expires
, Cache-Control
来设定, Expires
指缓存过期的时间,超过了这个时间点就代表资源过期。有一个问题是由于使用具体时间,如果时间表示出错或者没有转换到正确的时区都可能造成缓存生命周期出错。
并且 Expires
是 HTTP/1.0
的标准,现在更倾向于用 HTTP/1.1 中定义的 Cache-Control
。两个同时存在时也是 Cache-Control 的优先级更高。
2)协商缓存
缓存的资源到期了,并不意味着资源内容发生了改变,如果和服务器上的资源没有差异,实际上没有必要再次请求。客户端和服务器端通过某种验证机制验证当前请求资源是否可以使用缓存。 浏览器第一次请求数据之后会将数据和响应头部的缓存标识存储起来。再次请求时会带上存储的头部字段,服务器端验证是否可用。如果返回 304 Not Modified,代表资源没有发生改变可以使用缓存的数据,获取新的过期时间。反之返回 200 就相当于重新请求了一遍资源并替换旧资源。
Last-modified/If-Modified-Since
Last-modified: 服务器端资源的最后修改时间,响应头部会带上这个标识。第一次请求之后,浏览器记录这个时间,再次请求时,请求头部带上 If-Modified-Since
即为之前记录下的时间。服务器端收到带 If-Modified-Since
的请求后会去和资源的最后修改时间对比。若修改过就返回最新资源,状态码 200
,若没有修改过则返回 304
。
Etag/If-None-Match
由服务器端上生成的一段 hash
字符串,第一次请求时响应头带上 ETag: abcd
,之后的请求中带上 If-None-Match: abcd
,服务器检查 ETag
,返回 304
或 200
。
关于 last-modified 和 Etag 区别
• 某些服务器不能精确得到资源的最后修改时间,这样就无法通过最后修改时间判断资源是否更新。
• Last-modified
只能精确到秒。
• 一些资源的最后修改时间改变了,但是内容没改变,使用 Last-modified
看不出内容没有改变。
• Etag
的精度比 Last-modified
高,属于强验证,要求资源字节级别的一致,优先级高。如果服务器端有提供 ETag 的话,必须先对 ETag
进行 Conditional Request
。
注意:实际使用 ETag/Last-modified
要注意保持一致性,做负载均衡和反向代理的话可能会出现不一致的情况。计算 ETag
也是需要占用资源的,如果修改不是过于频繁,看自己的需求用 Cache-Control
是否可以满足。
实际应用
首先要明确哪些内容适合被缓存哪些不适合。
考虑缓存的内容: css
样式文件, js
文件, logo
、图标, html
文件,可以下载的内容
一些不应该被缓存的内容: 业务敏感的 GET 请求
可缓存的内容又分为几种不同的情况:
不经常改变的文件: 给 max-age
设置一个较大的值,一般设置 max-age=31536000
比如引入的一些第三方文件、打包出来的带有 hash
后缀 css
、 js
文件。一般来说文件内容改变了,会更新版本号、 hash
值,相当于请求另一个文件。 标准中规定 max-age
的值最大不超过一年,所以设成 max-age=31536000
。至于过期内容,缓存区会将一段时间没有使用的文件删除掉。
[完]