谷歌 Web 开发最佳实践手册(4.2.3):HTTP 缓存

【伯乐在线提示】:① 5月6日,谷歌开发者中心推出了一个 Web 开发最佳实践手册。伯乐在线资源频道摘编该资源后,已邀请一些关注 Web 开发的朋友参与翻译手册。② 由于译者朋友几乎都是已在职,都是在工作之余参与,每位的翻译进度会不一样(请理解),所以手册中文版不会按照英文版章节顺序发布。③ 手册中文版尚不完整,请不要转载,谢谢合作。


【导读】:在互联网上获取内容是相当耗时和昂贵的:大的响应需要在客户端和服务器之间有很多次通信来回,这延缓了他们被浏览器使用和处理的时机,同时也增加了访问者的数据流量开销。因此,缓存和重用已获取的资源成为了性能优化很关键的一方面。

本课内容

  • 使用ETags验证已缓存的响应
  • Cache-Control
  • 定义最优Cache-Control策略
  • 废弃和更新已缓存的响应
  • 缓存备忘录

好消息是每个浏览器都自带HTTP缓存的实现!我们所要做的就是保证每个服务器响应提供正确的HTTP头指令信息来指导浏览器何时进行缓存以及缓存保留多长时间。

注意:
如果你是在应用中使用Webview来获取和显示Web内容,你可能需要提供额外的配置项来保证HTTP缓存的启用,并且根据你的应用场景设置合理的缓存大小,然后这些缓存才能被持久化下来。你可以查看平台的文档来确认你的设置。

 

http-request

服务器返回一个响应的同时也发出了一个HTTP头的集合,用来描述响应的类型,长度,缓存指令,验证令牌(validation token)等。举个例子,在上图的交互中,服务器返回了一个1024字节的响应,指示客户端将它缓存120秒,提供了一个验证令牌(“x234dff”),它可以在响应过期之后用来验证资源是否更新过。

使用ETags验证缓存的响应

学习重点

  • 验证令牌通过ETag HTTP头与服务器通信
  • 验证令牌提供了快速资源更新检查:如果资源没有被更新,则不需要传输数据

让我们假设浏览器在获取一个资源120秒之后又对相同的资源发出请求。首先,浏览器会检查本地缓存并找到之前的响应,不幸的是这个响应已经“过期”不能用了。这时,浏览器原本可以直接发出一个新的请求来获取完整的响应,但是这并不是最高效的,因为如果这个资源没有被更改过,我们就没有理由再去下载一个跟缓存中完全一样的资源。

这就是ETag头信息中放入验证令牌所要解决的问题:服务器生成并返回一个随机令牌,通常是文件内容的哈希(hash)或者其他指纹码(fingerprint)。客户端不需要知道指纹码是如何生成的,它只需要将它在下一个请求中发回给服务器:如果指纹码仍然一致,说明资源没有被修改,我们就可以跳过下载。

http-cache-control

 

在上面的例子中,客户端自动在“If-None-Match”请求头中提供了ETag令牌,服务器端针对当前的资源检查令牌,如果没有被修改过,就返回“304 Not Modified”响应,告诉浏览器,缓存中的响应没有被修改过,可以延用下一个120秒。注意到我们不需要再次下载响应——这节约了时间和带宽。

作为一个Web开发人员,你该如何利用高效的加签(revalidation)?浏览器已经为我们做了很多:它自动检测验是否已经拥有验证令牌,它会将验证令牌附加到发出的请求上,会根据服务器的响应在必要的时候更新缓存时间戳。事实上,唯一留给我们要做的就是保证服务器提供必要的ETag令牌:阅读你的服务器文档并设置必要的配置项。

小贴士:HTML5 样板项目包含了所有最流行的服务器的配置文件样例,并为每一个配置项都提供了详细的注释:找到你最喜欢的服务器,查找适当的设置项,然后拷贝/确认你的服务器使用了推荐的设置。

Cache-Control

学习重点:

  • 每一个资源都可以通过Cache-Control HTTP头来定义自己的缓存策略
  • Cache-Control指令控制谁,在什么情况下,可以多久缓存响应

最好的请求是不需要和服务器交互的请求:一个本地的响应拷贝可以帮助我们消除所有的网络等待和数据传输费用。为了达到这个目的,HTTP规范允许服务器返回一系列不同的Cache-Control指令来控制一个响应在浏览器或者其他中继缓存中如何被缓存以及缓存多久。

小贴士:Cache-Control头定义在HTTP/1.1规范中,取代了之前用来定义响应缓存策略的头信息(如:Expires)。所有的现代浏览器都支持Cache-Control,因此使用它就够了。

http-cache-control-highlight

 

“no-cache”和“no-store”

“no-cache”指明当前返回的响应在后续相同的URL请求时必须先与服务器确认响应是否被修改,之后才能被用作后续请求响应的缓存。因此,如果我们有一个合适的验证令牌(ETag),no-cache会增加一次与服务器的通信来验证和确认缓存的响应,但是可以避免重复下载不曾更新的响应。

相比之下,“no-store”更加简单,它直接禁止所有的浏览器和中继缓存存储任何版本的响应——比如:一个包含了个人隐私信息或者银行信息的响应。每次用户请求这些资源的时候,都会发送一个请求到服务器并且下载完整的响应内容。

“public”和“private”

如果响应被标志为“public”,那么它是可以被缓存的,即使它有HTTP认证信息,甚至响应状态码不是正常的可缓存。大多数情况下,“public”不是必须的,因为明确的缓存信息(比如“max-age”)已经说明响应是可以被缓存的。

相比之下:“private”响应可以被浏览器缓存,但是通常只为单个用户缓存,一次不允许任何中继缓存件对其进行缓存——比如,一个包含用户私人信息的HTML页面可以被这个用户的浏览器所缓存,但是不能被CDN缓存。

“max-age”

这个指令指明获取的响应从当前请求开始允许被重用的最大时间限度(单位为秒)。例如:“max-age=60”说明响应在接下来的60秒内可以被缓存和重用。

定义最优Cache-Control策略

http-cache-decision-tree根据上面的决策树来为你的应用使用的单个资源或资源集设置最优的缓存策略。理想情况下,你应该旨在在客户端尽可能长时间地缓存尽可能多的响应内容,并且为每个响应提供验证令牌来启用快速验证。

Cache-Control指令 说明
max-age=86400 响应可以在浏览器或任何中继缓存中(如果它是”public”的)被缓存最多一天(60秒 x 60分 x 24小时)
private,max-age=600 响应可以在用户的浏览器上缓存十分钟(60秒 x 10分)
no-store 缓存不允许被缓存,每个请求都要重新获取

根据HTTP文档,在排名最高的300,000个网站中(Alexa评分),几乎下载的响应中有一半可以被浏览器缓存,这在重复的页面浏览和访问来说是一个巨大的节省!当然,这并不意味着你特定的应用就一定有50%的资源可以被缓存:有些网站可以缓存90%以上的资源,而有些则有很多私人的或者时间敏感的数据以至于根本不能被缓存。

审查你的页面来识别出哪些资源可以被缓存,并确保他们返回正确的Cache-Control和ETag头信息。

废弃和更新已缓存的响应

学习重点:

  • 本地缓存的响应会被持续使用知道资源“过期”
  • 在URL中嵌入一个文件内容指纹码可以帮助我们强制浏览器更新新版本的响应
  • 每个应用都要定义它自己的缓存结构从而达到最优的性能

一个浏览器创建的HTTP请求会首先被路由到浏览器的缓存来查看是否有存在有效的响应缓存来直接答复请求。如果有匹配的响应,它就会直接从缓存中读取,这样我们就省去了传输所带来的网络等待和数据流量。然而,我们该如何更新或者废弃一个已缓存的请求?

举例来说,假设我们已经告诉我们的访问者缓存我们的CSS样式文件24小时(max-age=86400),但是我们的设计师刚刚提交了一个更新,我们希望所有用户都能使用。那么我们该如何通知我们所有的访问者他们的缓存拷贝已经过时了,需要更新缓存?这是一个欺骗性的题目——实际上我们我们做不到,至少在不改变资源URL的情况下做不到。

一旦响应被浏览器缓存,被缓存的版本一直被使用,直到它不再新鲜,这由max-age或者expires指定,或者它因为某些原因从缓存中被删除——比如用户清理浏览器缓存。因此,不同的用户在页面被构造的时候可能使用的是不同的版本的CSS文件;最新获取资源的用户会使用更新过的版本,而缓存过之前版本(依然有效)拷贝的用户会继续使用老版本的响应。

所以,我们如何才能兼得鱼和熊掌:客户端缓存和快速更新?

很简单,我们可以在资源内容更新时改变资源的URL来强制用户下载最新的响应。典型地:可以通过在文件名上嵌入文件的指纹码,或者版本号来实现——比如 styoe.x234dff.css。

http-cache-hierarchy

能够定义单个资源级别的缓存策略让我们能够定义“缓存层级”,这让我们不但能够控制缓存有效的时间,而且还能控制新的版本何时能被访问者看到。例如,我们一起分析一下上面的例子:

  • HTML被标记成“no-cache”,这意味着浏览器在每个请求时都会重新验证文档,当内容更新后会获取最新版本。同时,在HTML标记中,我们给CSS和JavaScript资源嵌入指纹码:如果这些文件的内容发生变化,页面的HTML也会随着变化,那么新的HTML响应就会被下载。
  • CSS允许被浏览器和中继缓存(比如CDN)缓存,并且过期时间设置成1年。注意我们可以安全地将过期时间设置成1年,因为我们在文件名中使用了指纹码:如果CSS更新了,那么URL也会被更新。
  • JavaScript也被设置成一年过期,但是它被标记成私有的,也许是因为它包含了一些私有的用户数据,这些不应该被CDN缓存。
  • 图片被缓存而不包含版本信息和唯一的指纹码,过期时间为1天。

结合使用ETag,Cache-Control,和唯一的URL允许我们提供最佳的方案:长久的过期时间,控制哪些响应可以被缓存,同时按需更新。

缓存备忘录

没有一个固定的最佳缓存策略。你要根据你的通信模式,服务的数据类型,和应用特定的数据刷新需求等来定义和配置最合适的单个资源级别的设置,以及整体的“缓存层级”。

在定义缓存策略时使用的一些技巧和技术:

  1. 使用一致的URL:如果你对相同的内容使用不同的URL,那么这些内容将会获取和存储多次。提示:注意URL是大小写敏感的
  2. 确保服务器提供有效的令牌(ETag):验证令牌消除了当服务器端文件没有更改时传输相同数据的情况。
  3. 定义哪些资源可以被中继缓存:那些对于所有用户都一样的响应很适合缓存于CDN或其他中继缓存。
  4. 对每个资源定义最优的缓存周期:不同的资源可能有不同的刷新需求。为每一个资源审查并确定合适的max-age。
  5. 为你的网站定义最佳的缓存层级:对HTML文档结合使用带有内容指纹码的资源URL和短时间或no-cache生命周期,能够允许你控制客户端合适获取最新的更新。
  6. 最小化搅动(chrun):有些资源比其他的更新频繁。如果有一些特定的资源(比如JavaScript函数,或者一组CSS样式)会经常更新,考虑将他们提取到单独文件。这样做可以让其他的内容(比如那些不太更新的库文件)可以从缓存中读取,从而当内容更新时最小化需要下载的内容。
1 1 收藏 2 评论

关于作者:陈 鑫伟

(新浪微博:@陈鑫伟 ) 个人主页 · 我的文章 · 15

相关文章

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部