无论是软件应用还是硬件应用,缓存都扮演着重要的角色,其对提升性能的重要性无可置疑。
缓存示例
先看一个简单的缓存示例:
HTTP 缓存的理解基本上可以总结为三个问题:
- 缓存数据可以存储在哪些设备上WHERE)
- 缓存数据如何判断过期HOW)
- 过期缓存内容是否真的需要重新下载WHETHER)
问题 1 说明存储缓存数据的设备是多样的,可以存储于各级代理服务器,也可以存储于浏览器本地。
问题 2 说明使用什么办法来判断缓存数据是否已经过期,当然是比较时间啦,那么如何比较呢/p>
问题 3 说明缓存虽然过期了,但是其内容仍然可能与服务端一致,这时就没必要重新下载相同数据,只需要向服务端询问下是否可以继续使用缓存即可。
带着上面三个问题去理解 HTTP 缓存头部设置会更有助于理解和记忆。
有人根据是否需要进行问题 3 中的重新验证把缓存策略的设置分为强缓存和协商缓存,强缓存无须再次验证的缓存策略,协商缓存是需要再次验证的缓存策略。
两者的区别在于,协商缓存多发起了一次 HTTP 请求。
缓存首部
HTTP 缓存主要通过 HTTP 首部来实现缓存控制。这些与缓存相关的 HTTP 首部这里统称为缓存首部,具体首部如下表所示。
首部字段 | 首次定义 | 首部类型 |
---|---|---|
Pragma | HTTP/1.0 | 通用首部 |
Age | HTTP/1.1 | 响应首部 |
Expires | HTTP/1.0 | 实体首部 |
Cache-Control | HTTP/1.1 | 通用首部 |
Etag | HTTP/1.1 | 响应首部 |
If-Match | HTTP/1.1 | 请求首部 |
If-None-Match | HTTP/1.1 | 请求首部 |
If-Modified-Since | HTTP/1.0 | 请求首部 |
Last-Modified | HTTP/1.0 | 实体首部 |
其中,“首次定义”是指首次出现在哪个 HTTP 版本。之所以列出这项内容,是因为实际应用需要考虑兼容旧版 HTTP 。
现代的 HTTP 缓存策略主要使用 实现,它是目前最新的缓存首部,用于取代较老的缓存首部如 、 等。所以应用中应该倾向于使用 。但是为了支持只实现了 HTTP/1.0 的客户端设备,服务端通常还是都会同时设置 、 和 等,此时 会有更高的优先级。提醒一下,现代浏览器都已支持 。
Cache-Control
是通用首部,这意味着它既可以出现在请求中,也可以出现在响应中。
的值可由多个字段组合而成,以逗 分隔,如 。下面对常用的可取字段进行说明。
: 表示当前响应数据所有用户共享的,可以被任何设备缓存,包括客户端、代理服务器等。
: 表示当前响应数据是单个用户所独占的,只能被客户端缓存,不能被代理服务器缓存。
: 指定缓存的有效时间,单位为秒。其值是任意整数,0 和负数表示缓存过期,正数值加上当前响应头中的 首部值即为过期时间。
: 只用于请求,表示客户端仍然愿意接受过期缓存,只要过期时间没超过指定时间,如果未指定时间,则表示任何过期的时间。
: 只用于请求,表示客户端愿意接受还剩余多少秒过期的缓存。
: 功能与 一致,但它仅作用于共享缓存,对私有缓存无效。
: 并非字面意思,它并非禁止缓存,而是强制在使用已缓存数据之前,需要去服务端验证一下是否可以使用缓存数据。
: 真正的禁止缓存,任何设备都不允许缓存,每次请求都需要向服务端重新获取数据。
: 表示响应的实体数据不应被转换。 、 和 首部也不能被修改。实际应用中,有些代理服务器会对图片资源进行格式转换以节省空间或者带宽。
作为通用首部,其部分指令值可以出现在请求首部,也可以出现在响应首部,两者可能略有区别:
指令值 | 请求 | 响应 |
---|---|---|
– | 可共享数据,可被任何设备缓存 | |
– | 用户私有数据,只能被客户端缓存 | |
使用前需验证 | 使用前需验证 | |
禁止使用缓存数据 | 禁止缓存 | |
要求资源的 age 小于这个时间 | 最大过期时间 | |
要求资源至少还剩余多少过期时间 | – | |
超过过期时间多少秒内仍愿意接受 | – | |
不要转换格式 | 不要转换格式 |
这些指令用在请求首部的情况比较少见,最可能接触的地方是 中的 标签页。
其中,有个 选项,选中后 会自动给所有请求头部加上 首部,以告诉浏览器和代理使用本地缓存之前必须先验证。
- 客户端首次请求 时,服务器响应带上 首部,告诉客户端当前资源的最后修改时间。客户端根据 ,把 和响应首部缓存起来。
- 客户端再次发起请求 时,把之前保存的 时间放入 首部发给服务器。服务器发现资源的 时间没有发生改变,于是直接响应 304 。客户端收到 304 后,直接使用缓存的 ,同时更新缓存有效期。
- 客户端再次发起请求 时,把之前保存的 时间放入 首部发给服务器。服务器发现资源的 时间已经发生改变,于是响应 200 ,将修改后的 和新的 发送给客户端。客户端收到 200 后,重新下载新的 ,并把新的 和响应首部缓存起来,替换原先的旧缓存。
ETag/If-Matched/If-None-Match
叫实体标签(Entity Tag),用于表示实体资源是否发生变化,其生成原理类似 MD5 ,也是一种用于验证的首部。当响应的首部信息或者消息实体发生变化时,实体标签也会改变。
使用过程如下:
- 客户端首次请求 时,服务器响应带上 首部,告诉客户端当前资源的实体标签。客户端根据 ,把 和响应首部缓存起来。
- 客户端再次发起请求 时,把之前保存的 值放入 首部发给服务器。服务器发现自己的资源 值并没有发生改变,于是直接响应 304 。客户端收到 304 后,直接使用缓存的 ,同时更新缓存有效期。
- 客户端再次发起请求 时,把之前保存的 值放入 首部发给服务器。服务器发现自己的资源 值已经发生改变,于是响应 200 ,将修改后的 和新的 发送给客户端。客户端收到 200 后,重新下载新的 ,并把新的 和响应首部缓存起来,替换原先的旧缓存。
当客户端本地存储有多个版本的资源时,会把所有的实体标签都上传,形如 ,服务端会使用 首部返回匹配中的实体标签值。
实体标签分为强标签(Strong ETag)和弱标签(Weak ETag),弱标签以 开头,如 。强标签使用强比较,弱标签使用弱比较。强比较意味着两个比较对象的每一个字节都相同,弱比较意味着两者语义相同(Semantic Equivalence)。举个栗子,假如响应首部包含一个渲染时间 ,A 响应的渲染时间为 365,B 响应的渲染时间为 345,两个响应的实体内容一致。这种情况下,我们可以说 A 和 B 弱比较相等,强比较不相等。
一般来说,静态内容使用强标签,动态生成的内容使用弱标签。
由此可以看出,实体首部可以解决一些 无法解决的问题:
- 某些服务器不能得到文件的精确的最后修改时间
- 修改时间变了并不意味着内容的改变,比如改完保存后又改回去
- 修改时间只能精确到秒,一秒内的修改无法判断
Expires
指明资源的过期时间,如 。非法的日期格式(如 0)将会被当做过去的时间,表示该资源已经过期。
如果 和 的 或者 同时出现, 将被忽略。
Age
表示资源在代理服务器上已经缓存了多久时间,单位为秒。如果是 ,表明该资源刚刚从服务器获取。它的计算方式一般使用代理服务器当前的时间减去缓存资源的 时间。
Pragma
是 HTTP/1.0 中引入的首部,现在使用时一般用于向后兼容 HTTP/1.0,不鼓励使用。
的作用与 一致,表示需要跟服务器进行验证后才能使用缓存资源。
启发式缓存策略
并不是每个服务器都会返回明确的缓存策略,这种情况下客户端会采取启发式缓存策略。注意,只有在服务端没有返回明确的缓存策略时才会激活启发式缓存策略。
启发式缓存策略会根据其他的首部信息来计算一个过期时间,其他的首部通常是 和 。此时,缓存有效期一般取两者差值的 10% 。
使用启发式缓存策略时,如果超过当前时间 24 小时且从未警告过,浏览器或者代理服务器应该在响应中产生一个警告首部字段 。
参考资料
- HTTP/1.0, RFC1945
- HTTP/1.1, RFC2616
- HTTP/1.1 Caching, RFC7323
- HTTP 缓存, Google Developers
- The-difference-between-strong-and-weak-ETags
文章知识点与官方知识档案匹配,可进一步学习相关知识 络技能树首页概览22055 人正在系统学习中 相关资源:实例讲解分布式缓存软件Memcached的Java客户端使用-其它代码类…
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!