HTTP 缓存

来源:互联网 发布:java home配置 编辑:程序博客网 时间:2024/05/29 19:10

网络加载资源不仅慢而且代价高。对访问者来说,除了数据传输成本,即使资源可以用并且浏览器可以处理,大量的响应需要客户端和服务器进行多次交互,这会造成延时问题。总之,将之前获取的资源从新利用是性能优化的关键。

好消息是,每个浏览器都具备 HTTP 缓存的能力, 你唯一要做的就是确保每次相应提供了正确的 HTTP 响应头,指导浏览器能在什么时间缓存资源多久。

[caption id="attachment_144" align="alignnone" width="349"]http-request brower cache http-request brower cache[/caption]

当服务器响应时,通常带有一系列的响应头,描述了 content-type, length, 缓存级别和缓存版本号以及其他。比如, 在上图中,服务器返回 1024 字节的响应, 指示客户端缓存 120 秒, 缓存版本号 ( "x234dff" ), 该版本号用于检查在响应过期之后资源是否被修改过。

假设,第一次请求某一资源 A 之后,已经经过了 120 秒, 浏览器再次请求该资源 A。首先, 浏览器检查本地并找到了上次的缓存数据。但是很遗憾,浏览器不能使用这个数据,因为它过期了。此时,浏览器可以从新发送请求来获取该数据文件。但是这样做很不划算,因为该数据文件并没有变更过,重新请求一个已经被缓存的文件毫无道理。

这时 ETag 就排上用场啦。服务器生成并返回一个任意 token, 它可以是该文件一个特定的哈希或者指纹。客户端并不关心这个哈希是如何生存的,它唯一要做的事情就是在下一次请求头中带上这个 token, 服务器据此判断请求的资源如果已经缓存,则不会发送资源给客户端。

http cache control

在上图示的请求中, 客户端自动的提供 If-None-Match: x234dff 请求头。服务器检查该 token 是否与当前资源匹配,如果 token 没有发生变化,服务器会返回一个 304 Not Modified 的响应, 用来告诉浏览器请求的资源没有变更, 可以将过期时间向后推迟 120 秒。注意,无需重新下载,节约了时间和带宽。

作为一个程序员, 要如何利用这一机制呢 ? 浏览器已经为你搞定了绝大多数工作。浏览器自动的检查 token 是否已经存在,   然后在请求上带上这个有效的 token, 并且根据响应在必要的时候更新缓存的时间戳。剩下的唯一需要你确认的事情就是, 服务器提供了必要的 ETag, 检查你的服务器文档查看必要的配置标记。

做性能优化的角度看来, 请求最理想的状态就是不请求服务器: 一个本地的响应副本可以消除网络延迟和数据的传输开销。为达到这个目标, 服务器通过特殊的响应头 Cache-Control 控制浏览器及其他介质如何缓存以及缓存多久独立的响应体来实现。

http cache control ETag

“no-cache” 与 "no-store"

"no-cache" 表示,在请示服务器之前,不能用之前保存本地的响应副本响应后续的同 URL 请求。然而, 如果合适有效的 ETag 存在, no-cache 会发送请求去要求授权允许使用副本进行响应, 这样, 在资源没有变更的情况下, 就可以避免一次下载了。

与之对照的 no-store 就浅显易懂许多, 它不允许浏览器以及其他媒介进行任何版本的缓存 ---- 比如, 私人或者银行数据, 客户端每次请求, 服务器每次都会完整的下发一次请求的数据。

"public" 与 "private“

如果响应被标记为 "public", 则它将被缓存, 即使有 HTTP 认证关联着, 即使响应的状态码不能被正确的缓存。多数时候, ”public“ 不是必要的, 因为特定的 max-age 决定了响应无论如何都会缓存。

作为对照, 浏览器能缓存 private 的相应, 这些相应具有特殊的倾向性, 其他中间媒介不允许进行缓存。举例来说, 浏览器能缓存 private 标记的 HTTP 信息, 但是 CDN 不允许。

”max-age“

这个标记表示的是,从第一次请求成功起,缓存可以重用的最大时间范围,单位是秒。举例来说, "max-age=60" 表明, 响应能被缓存和从用 60 秒。

理想的缓存控制策略

http cache control flow tree

上面的流程图可以描述一个或多个资源的缓存路径。理想情况下, 你应该致力于在客户端缓存尽可能长时间的, 尽可能多的响应, 提供高效的有效期延迟策略。

审计你的网页弄清楚哪些资源能被缓存,确保返回合适的缓存标记 (cache-control  ETag ...)。

延迟过期以及更新响应缓存文件。

  • 本地缓存在过期之前可以一直使用
  • 在 URL 中嵌入版本号,可以强制客户端更新服务器资源
  • 每一个应用应该针对性的定义自己的缓存策略来达到性能的最优化

所有的 HTTP 请求都应该在发往服务器之前检查看看是否有对应的本地缓存可以满足, 如果可以,则从缓存中读取响应, 免去了请求服务器的网络延时和数据传输开销。

但是, 如果想更新资源或者强制缓存失效呢 ? 比如, 你告诉过请求者缓存一个 CSS 样式表文件 24 小时, 但是网站设计师刚刚提交了一个这个 CSS 的新版本你希望立刻对所有的用户生效。你要如何通知所有的用户他们的 CSS 过期了, 需要他们更新样式表文件 ? 你只有更新资源的 URL 才能办到。

当浏览器缓存了响应之后, 这个缓存在他们被标记为过期之前都将有效 ( 过期标记: max-age   expires), 又或者缓存被因为一些其他原因丢弃了 ---- 比如用户清理了他们的浏览器缓存。总之, 不同的用户将使用着不同版本的文件: 那些刚刚获取了资源的用户使用新资源, 早些时候缓存了旧版本资源的用户使用的旧版本文件。

如何两全其美呢, 客户端缓存还是快速更新 ? 你可以通过更改资源的 URL 来实现强制浏览器下载资源而忽略资源的更新的状态。典型的做法是, 在资源文件的名称中包含文件的哈希或者版本号。

这种定义资源的技巧, 可以让你忽略文件缓存多久, 直接对客户端何时收到新的版本进行完全控制。为了诠释这个, 举例如下:

  • HTML 使用 no-cache 标记, 以为了浏览器每次请求都会延时过期资源, 当内容变更的时候, 浏览器会获取最新的版本。还有, HTML 作为框架,嵌入了包含版本号的 CSS 、JavaScript资源连接: 当这些内容变更的时候, HTML 也应该做对应的变更
  • CSS 允许浏览器或者其他的中间媒介缓存 ( 比如说 CDN ), 然后设置为 1 年过期。注意, 你可以使用"far future expires" 来代替 1 年更加保险, 因为你把文件的版本号嵌入在它的文件名中: 如果 CSS 内容变更, 则 URL 也会随之变化
  • JavaScript 也可以设置 1 年过期, 但是属性是 private 的, 因为它可能包含私人的信息以至于 CDN 不允许缓存
  • 图片被缓存一天, 不带有版本号

ETag, Cache-Control, 唯一 URL 联合起来让你满足各方的需求:  最长的过期时间, 响应的缓存的控制以及按照要求的更新。

没有银弹, 三思慎行,送君几支锦琅妙计:

  • 使用一致的 URL : 如果你给相同的内容定义了不同的 URL, 则该内容会被存取多次。TIP: URL 是大小写敏感的。
  • 确认服务器提供了有效的 token  ( ETag ): 有效的 token 杜绝了当服务器上的资源没有变更的时候, 传输相同的资源的浪费。
  • 甄别哪些资源能被中间媒介缓存: 这些响应的资源对 CDN 或者其他中间媒介等候选者来说都是平等的
  • 决定各种资源的缓存周期: 不同的资源有不同的更新过期时间的需要, 审计并决定各种资源的  max-age 。
  • 为网站决定最佳的缓存优先策略: 带有版本号的资源 URL, 和可控的  HTML 缓存周期, 让客户端的更新完全可控。
  • 变动局部化原则: 有些资源的更新教其他资源频繁许多。如果某一资源 (比如, JavaScript 方法 或一段样式表)的特定部分频繁变动, 考虑将这一部分独立成一个单独的文件,这样剩下的不易变动的部分可以很好的利用缓存优势。

原文:

https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#cache-control

转载: https://hackoops.com/yi-http-huancun/

原创粉丝点击