资讯专栏INFORMATION COLUMN

图说 HTTP 缓存

lx1036 / 740人阅读

摘要:缓存主要通过首部来控制。表示当前响应数据是单个用户所独占的,只能被客户端缓存,不能被代理服务器缓存。其值是任意整数,和负数表示缓存过期,正数值加上当前响应头中的首部值即为过期时间。客户端收到后,直接使用缓存的,同时更新缓存有效期。

无论是软件应用还是硬件应用,缓存都扮演着重要的角色,其对提升性能的重要性无可置疑。

本文主要介绍 HTTP 缓存,涉及其原理和应用。HTTP 缓存主要通过 HTTP 首部来控制。

缓存示例

先看一个简单的缓存示例:

浏览器首次请求 app.js 时,服务器会返回资源内容和相关头部,其中 Cache-Control: max-age=120 告诉浏览器说,这个资源的缓存有效期为 120 秒,从当前时间 Date: Mon, 05 Mar 2018 08:00:00 GMT 开始算起。浏览器收到资源后便将 app.js 及其相应头部存储在本地。

如果在 05 Mar, 2018 08:02:00 GMT 之前再次请求 app.js ,则浏览器会直接使用存储在本地的资源,而不用再次向服务器发起请求。

这个过程中,我们就说 app.js缓存且命中了。

基本概念

在进一步理解缓存之前,先看下跟缓存相关的几个概念:

命中:请求数据不需再次下载,可以直接使用缓存数据

过期:缓存数据超过设置的有效时间,将被标记为“陈旧”

验证:判断过期缓存是否仍然有效,需要与服务器交互

失效:缓存数据不再有效,需要从服务端重新下载新数据

基本理解

HTTP 缓存涉及到请求-响应链上的多个角色,包括客户端(本文指浏览器)、代理和服务器。
其中,浏览器自身也实现了缓存功能。浏览器在请求资源时,总是先从本地缓存中查找,如果找到未过期资源,则直接使用,否则向服务器发起请求。
代理也是服务器的一种,但一般情况下不会把它多带带抽出来分析,只有在跟它有关的地方会把它区分于源服务器。所以,下文的示例图中将不会把它列进去。

HTTP 缓存的理解基本上可以总结为三个问题:

缓存数据可以存储在哪些设备上?(WHERE)

缓存数据如何判断过期?(HOW)

过期缓存内容是否真的需要重新下载?(WHETHER)

问题 1 说明存储缓存数据的设备是多样的,可以存储于各级代理服务器,也可以存储于浏览器本地。

问题 2 说明使用什么办法来判断缓存数据是否已经过期,当然是比较时间啦,那么如何比较呢?

问题 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 缓存策略主要使用 Cache-Control 实现,它是目前最新的缓存首部,用于取代较老的缓存首部如 PragmaExpires 等。所以应用中应该倾向于使用 Cache-Control 。但是为了支持只实现了 HTTP/1.0 的客户端设备,服务端通常还是都会同时设置 ExpiresPragmaCache-Control 等,此时 Cache-Control 会有更高的优先级。提醒一下,现代浏览器都已支持 Cache-Control

Cache-Control

Cache-Control通用首部,这意味着它既可以出现在请求中,也可以出现在响应中。

Cache-Control 的值可由多个字段组合而成,以逗号分隔,如 Cache-Control: private,max-age=3600 。下面对常用的可取字段进行说明。

public: 表示当前响应数据所有用户共享的,可以被任何设备缓存,包括客户端、代理服务器等。

private: 表示当前响应数据是单个用户所独占的,只能被客户端缓存,不能被代理服务器缓存。

max-age=: 指定缓存的有效时间,单位为秒。其值是任意整数,0 和负数表示缓存过期,正数值加上当前响应头中的 Date 首部值即为过期时间。

max-stale[=]: 只用于请求,表示客户端仍然愿意接受过期缓存,只要过期时间没超过指定时间,如果未指定时间,则表示任何过期的时间。

min-fresh=: 只用于请求,表示客户端愿意接受还剩余多少秒过期的缓存。

s-maxage=: 功能与 max-age 一致,但它仅作用于共享缓存,对私有缓存无效。

no-cache: 并非字面意思,它并非禁止缓存,而是强制在使用已缓存数据之前,需要去服务端验证一下是否可以使用缓存数据。

no-store: 真正的禁止缓存,任何设备都不允许缓存,每次请求都需要向服务端重新获取数据。

no-transform: 表示响应的实体数据不应被转换。Content-EncodingContent-RangeContent-Type 首部也不能被修改。实际应用中,有些代理服务器会对图片资源进行格式转换以节省空间或者带宽。

作为通用首部,其部分指令值可以出现在请求首部,也可以出现在响应首部,两者可能略有区别:

指令值 请求 响应
public - 可共享数据,可被任何设备缓存
private - 用户私有数据,只能被客户端缓存
no-cache 使用前需验证 使用前需验证
no-store 禁止使用缓存数据 禁止缓存
max-age 要求资源的 age 小于这个时间 最大过期时间
min-fresh 要求资源至少还剩余多少过期时间 -
max-stale 超过过期时间多少秒内仍愿意接受 -
no-transform 不要转换格式 不要转换格式

这些指令用在请求首部的情况比较少见,最可能接触的地方是 Chrome DevTools 中的 Network 标签页。
其中,有个 Disable cache 选项,选中后 DevTools 会自动给所有请求头部加上 Cache-Control: no-cache 首部,以告诉浏览器和代理使用本地缓存之前必须先验证。

Last-Modified/If-Modified-Since

If-Modified-Since 首部比较的是资源的修改时间,精度为秒,是一种缓存过期后的常用验证方式。一般来说,验证资源是否修改过,对比资源的修改时间是一种最简单的办法。

使用过程如下:

客户端首次请求 app.js 时,服务器响应带上 Last-Modified 首部,告诉客户端当前资源的最后修改时间。客户端根据 Cache-Control: max-age=120 ,把 app.js 和响应首部缓存起来。

客户端再次发起请求 app.js 时,把之前保存的 Last-Modified 时间放入 If-Modified-Since 首部发给服务器。服务器发现资源的 Last-Modified 时间没有发生改变,于是直接响应 304 。客户端收到 304 后,直接使用缓存的 app.js ,同时更新缓存有效期。

客户端再次发起请求 app.js 时,把之前保存的 Last-Modified 时间放入 If-Modified-Since 首部发给服务器。服务器发现资源的 Last-Modified 时间已经发生改变,于是响应 200 ,将修改后的 app.js 和新的 Last-Modified 发送给客户端。客户端收到 200 后,重新下载新的 app.js ,并把新的 app.js 和响应首部缓存起来,替换原先的旧缓存。

ETag/If-Matched/If-None-Match

ETag 叫实体标签(Entity Tag),用于表示实体资源是否发生变化,其生成原理类似 MD5 ,也是一种用于验证的首部。当响应的首部信息或者消息实体发生变化时,实体标签也会改变。

使用过程如下:

客户端首次请求 app.js 时,服务器响应带上 ETag 首部,告诉客户端当前资源的实体标签。客户端根据 Cache-Control: max-age=120 ,把 app.js 和响应首部缓存起来。

客户端再次发起请求 app.js 时,把之前保存的 ETag 值放入 If-None-Match 首部发给服务器。服务器发现自己的资源 ETag 值并没有发生改变,于是直接响应 304 。客户端收到 304 后,直接使用缓存的 app.js ,同时更新缓存有效期。

客户端再次发起请求 app.js 时,把之前保存的 ETag 值放入 If-None-Match 首部发给服务器。服务器发现自己的资源 ETag已经发生改变,于是响应 200 ,将修改后的 app.js 和新的 ETag 发送给客户端。客户端收到 200 后,重新下载新的 app.js ,并把新的 app.js 和响应首部缓存起来,替换原先的旧缓存。

当客户端本地存储有多个版本的资源时,会把所有的实体标签都上传,形如 ETag: "abc","def" ,服务端会使用 ETag 首部返回匹配中的实体标签值。

实体标签分为强标签(Strong ETag)和弱标签(Weak ETag),弱标签以 W/ 开头,如 ETag: W/"1234" 。强标签使用强比较,弱标签使用弱比较。强比较意味着两个比较对象的每一个字节都相同,弱比较意味着两者语义相同(Semantic Equivalence)。举个栗子,假如响应首部包含一个渲染时间 Rendered-Time,A 响应的渲染时间为 365,B 响应的渲染时间为 345,两个响应的实体内容一致。这种情况下,我们可以说 A 和 B 弱比较相等,强比较不相等。

一般来说,静态内容使用强标签,动态生成的内容使用弱标签

由此可以看出,实体首部可以解决一些 Last-Modified 无法解决的问题:

某些服务器不能得到文件的精确的最后修改时间

修改时间变了并不意味着内容的改变,比如改完保存后又改回去

修改时间只能精确到秒,一秒内的修改无法判断

If-MatchETag 的另一种用法:避免“空中碰撞”,以防编辑冲突。当客户端使用 PUT 或者 POST 更新服务端资源时,需要使用 If-Match 来携带实体标签给服务端,以确保客户端要修改的资源没有被别人修改过,避免覆盖别人的修改。不过这种用法比较少,可以不用深究。

Expires

Expires 指明资源的过期时间,如 Expires: Wed, 04 Jul 2012 08:26:05 GMT 。非法的日期格式(如 0)将会被当做过去的时间,表示该资源已经过期。

如果 ExpiresCache-Controlmax-age 或者 s-maxage 同时出现,Expires 将被忽略

Age

Age 表示资源在代理服务器上已经缓存了多久时间,单位为秒。如果是 Age: 0 ,表明该资源刚刚从服务器获取。它的计算方式一般使用代理服务器当前的时间减去缓存资源的 Date 时间。

Pragma

Pragma 是 HTTP/1.0 中引入的首部,现在使用时一般用于向后兼容 HTTP/1.0,不鼓励使用。

Pragma: no-cache 的作用与 Cache-Control: no-cache 一致,表示需要跟服务器进行验证后才能使用缓存资源。

启发式缓存策略

并不是每个服务器都会返回明确的缓存策略,这种情况下客户端会采取启发式缓存策略。注意,只有在服务端没有返回明确的缓存策略时才会激活启发式缓存策略。

启发式缓存策略会根据其他的首部信息来计算一个过期时间,其他的首部通常是 DateLast-Modified 。此时,缓存有效期一般取两者差值的 10% 。

使用启发式缓存策略时,如果超过当前时间 24 小时且从未警告过,浏览器或者代理服务器应该在响应中产生一个警告首部字段 Warning: 113

参考资料

HTTP/1.0, RFC1945

HTTP/1.1, RFC2616

HTTP/1.1 Caching, RFC7323

HTTP 缓存, Google Developers

The-difference-between-strong-and-weak-ETags

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/61963.html

相关文章

  • 图说 Firefox 全新 CSS 引擎

    摘要:的主要组件包含了一个全新的引擎,称为量子,也称为。这个新引擎集成了四种不同浏览器的最新创新技术,创造出一个全新的超级引擎。这可以发生在多个图层上。最终,拥有最高特异性的规则会胜出。 原文:Inside a Super Fast CSS Engine: Quantum CSS(Aka Stylo), Lin Clark 注:原文发布于 2017 年 8 月,本文翻译于 2018 年 4 ...

    lsxiao 评论0 收藏0
  • 图说 WebAssembly(一):序言

    摘要:性能简史在年,被创造出来时并不是冲着性能去的。而且在之后的十年发展中,它的性能一直是很低的。的引入成就了性能提升的一个转折点,其执行速度比以往快了之多。性能提升也使得在全新的问题上使用成为可能。现在,极可能是下一个性能转折点。 你可能已经听说 WebAssembly 代码跑起来非常快。但是你知道这是为什么吗?在本系列文章中,我们将探究其原因。 何为 WebAssembly WebAss...

    codergarden 评论0 收藏0
  • DeepMind 推出贝叶斯 RNN,语言建模和图说生成超越传统 RNN

    摘要:我们还经验性地演示了贝叶斯在语言建模基准和生成图说任务上优于传统,以及通过使用不同的训练方案,这些方法如何改进我们的模型。第节和第节分别回顾了通过反向传播做贝叶斯,和通过时间做反向传播。 摘要在这项工作里,我们探讨了一种用于 RNN 的简单变分贝叶斯方案(straightforward variational Bayes scheme)。首先,我们表明了一个通过时间截断反向传播的简单变化,能...

    KunMinX 评论0 收藏0
  • 图说 WebAssembly(三):什么是汇编

    摘要:为了更好的理解,我们有必要去先理解什么是汇编,以及编译器是如何产生汇编的。什么是汇编现在,我们来看看外星人的大脑是如何工作的。这些注释就是汇编,也称为符号机器码。结束以上的内容就是什么是汇编以及它是如何从高级编程语言翻译过来的。 本文是图说 WebAssembly 系列文章的第三篇。如果您还未阅读之前的文章,建议您从第一篇入手。 为了更好的理解 WebAssembly ,我们有必要去先...

    刘福 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<