重用已经获取的资源能够有效的提升网站与应用的性能。Web 缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间。借助 HTTP 缓存,Web 站点变得更具有响应性。

各种各样的缓存

缓存是指存储指定资源的一份拷贝,并在下次请求该资源时提供该拷贝的技术。当 web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。这样带来的好处有:缓解服务器端压力,提升性能(获取资源的耗时更短了)。对于网站来说,缓存是达到高性能的重要组成部分。缓存需要合理配置,因为并不是所有资源都是永久不变的:重要的是对一个资源的缓存应截止到其下一次发生改变(即不能缓存过期的资源)。

缓存的种类有很多,其大致可归为两类:私有与共享缓存。共享缓存存储的响应能够被多个用户使用。私有缓存只能用于单独用户。本文将主要介绍浏览器与代理缓存,除此之外还有网关缓存、CDN、反向代理缓存和负载均衡器等部署在服务器上,为站点和 web 应用提供更好的稳定性、性能和扩展性。

What a cache provide, advantages/disadvantages of shared/private caches.

私有服务器缓存

私有缓存只能用于单独用户。你可能已经见过浏览器设置中的“缓存”选项。浏览器缓存拥有用户通过 HTTP 下载的所有文档。这些缓存为浏览过的文档提供向后/向前导航,保存网页,查看源码等功能,可以避免再次向服务器发起多余的请求。它同样可以提供缓存内容的离线浏览。

共享代理缓存

共享缓存可以被多个用户使用。例如,ISP 或你所在的公司可能会架设一个 web 代理来作为本地网络基础的一部分提供给用户。这样热门的资源就会被重复使用,减少网络拥堵与延迟。

缓存操作的目标

虽然 HTTP 缓存不是必须的,但重用缓存的资源通常是必要的。然而常见的 HTTP 缓存只能存储 GET 响应,对于其他类型的响应则无能为力。缓存的关键主要包括request method和目标URI(一般只有GET请求才会被缓存)。 普遍的缓存案例:

  • 一个检索请求的成功响应: 状态码:200,一个包含例如HTML文档,图片,或者文件的响应.
  • 不变的重定向: 响应状态码:301.
  • 错误响应: 响应状态码:404 的一个页面.
  • 不完全的响应: 响应状态码 206,只返回局部的信息.
  • 除了 GET 请求外,如果匹配到作为一个已被定义的cache键名的响应.

针对一些特定的请求,也可以通过关键字区分多个存储的不同响应以组成缓存的内容。具体参考下文关于 Vary 的信息 .

缓存控制

Cache-control

HTTP/1.1定义的 Cache-Control 头用来区分对缓存机制的支持情况, 请求头和响应头都支持这个属性。通过它提供的不同的值来定义缓存策略。

完全不支持缓存

缓存中不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容。

Cache-Control: no-store
Cache-Control: no-cache, no-store, must-revalidate

不缓存内容

在释放缓存内容前向服务端源地址发送请求以验证缓存是否有效。

Cache-Control: no-cache

私有缓存和公共缓存

public:响应可以被任何请求来源缓存。针对需要进行http身份验证的页面或者一些不能被顺利缓存的响应码,通过定义public以支持缓存。

private:响应的内容只能被唯一的用户缓存,不可以被共享缓存存储。隐私模式下的浏览器会通过这种方式存储缓存。

Cache-Control: private
Cache-Control: public

缓存过期

判断缓存是否过期的一个最常使用的标志是max-age。相对Expires而言,max-age是距离请求发起的时间的秒数。针对应用中那些不会改变的文件,通常可以手动设置一定的时长以保证缓存有效,例如图片、css、js等静态资源。

详情看下文关于缓存有效性的内容。

Cache-Control: max-age=31536000

缓存验证

must-revalidate:  在使用一些老的资源前强制验证状态判断其是否过期。详情看下文关于缓存校验的内容。

Cache-Control: must-revalidate

Pragma 头

Pragma 是HTTP/1.0标准中定义的一个header属性,请求中包含Pragma的效果跟在头信息中定义Cache-Control: no-cache相同,但是HTTP的响应头不支持这个属性,所以它不能拿来完全替代HTTP/1.1中定义的Cache-control头。通常定义Pragma以向后兼容基于HTTP/1.0的客户端。

缓存有效性

一旦一个资源文件被存入缓存,理论上来说这个文件就永远处于缓存中。但是缓存的存储空间通常有限,这也意味着定期会移除一部分缓存文件。这个过程称作缓存抛弃。另一方面,针对那些在服务端发生改变的资源,应该做响应的缓存内容更新。因为HTTP协议在C-S架构中是无状态的,服务端在资源发生变化时无法通知到客户端,通过定义过期时间以同步两端的缓存资源。在过期时间前,资源缓存是有效的,反之则缓存失效。通过不停抛弃过期的缓存资源以保证资源的实时性。注意,旧的缓存不会被抛弃或者忽略;当发起一个针对旧缓存资源的请求时,会在请求头里带上If-None-Match用来判断缓存是否还有效。如果有效,服务端返回304(Not Modified)和空的body以节省一部分带宽。

下面是上述缓存处理过程的一个图示:

Show how a proxy cache acts when a doc is not cache, in the cache and fresh, in the cache and stale.

对于含有特定头信息的请求,会去计算缓存寿命。比如Cache-control: max-age=N的请求头,相应的缓存的寿命就是N。通常情况下,对于不含这个属性的请求则会去查看是否包含Expires属性,通过比较Expires的值和头里面Date属性的值来判断是否缓存还有效。如果max-age和expires属性都没有,找找头里的Last-Modified信息。如果有,缓存的寿命就等于头里面Date的值减去Last-Modified的值除以10(译者注:Last-Modified的值除以10存疑)。

缓存时长计算公式如下:

缓存时长 = 响应时间 + 缓存寿命 - 当前时间

响应时间指浏览器接收到服务端的响应的时间。

加速资源

更多地利用缓存资源,可以提高网站的性能和相应速度。为了优化缓存,过期时间设置得尽量长是一种很好的策略。对于定期或者平凡更新的资源,这么做是比较稳妥的,但是对于那些长期不更新的资源会有点问题。这些固定的资源在一定时间内受益于这种长期保持的缓存策略,但一旦要更新就会很困难。特指网页上引入的一些js/css文件,当它们变动时需要尽快更新线上资源。

web开发者发明了一种 Steve Sounders 称作加速(译者注:revving)的技术[1] 。不频繁更新的文件会使用特定的命名方式:在URL后面(通常是文件名后面)会加上版本号。加上版本号后的资源就被视作一个完全新的独立的资源,同时拥有一年甚至更长的缓存过期时长。但是这么做也存在一个弊端,所有引用这个资源的地方都需要更新链接。web开发者们通常会采用自动化构建工具在实际工作中完成这些琐碎的工作。当低频更新的资源(js/css)变动了,只用在高频变动的资源文件(html)里做入口的改动。

这种方法还有一个好处:同时更新两个缓存资源不会造成部分缓存先更新而引起新旧文件内容不一致。对于互相有依赖关系的css和js文件,避免这种不一致性是非常重要的。

加在加速文件后面的版本号不一定是一个正式的版本号字符串,如1.1.3这样或者其他固定自增的版本数。它可以是任何防止缓存碰撞的标记例如hash或者时间戳。

缓存验证

用户点击刷新按钮时会开始缓存验证。如果缓存的响应头信息里含有"Cache-control: must-revalidate”的定义,在浏览的过程中也会触发缓存验证。另外,在浏览器偏好设置里设置Advanced->Cache为强制验证缓存也能达到相同的效果。

当缓存的文档过期后,需要进行缓存验证或者重新获取资源。只有在服务器返回强校验器或者弱校验器时才会进行验证。

ETags

作为缓存的一种强校验器,ETag 响应头是一个对用户代理(User Agent, 下面简称UA)不可知的值。对于像浏览器这样的HTTP UA,不知道ETag代表什么,值是多少。如果资源请求的响应头里含有ETag, 客户端可以在后续的所有请求的头中带上 If-None-Match 头来验证缓存。

Last-Modified 响应头可以作为一种弱校验器。说它弱是因为它是一次性的。如果响应头里含有这个信息,客户端可以在后续的一次请求中带上 If-Modified-Since 来验证缓存。

当向服务端发起缓存校验的请求时,服务端会返回 200 ok表示返回正常的结果或者 304 Not Modified(不返回body)表示浏览器可以使用本地缓存文件。304的响应头也可以同时更新缓存文档的过期时间。

带Vary头的响应

Vary HTTP 响应头决定了对于后续的请求头,如何判断是请求一个新的资源还是使用缓存的文件。

当缓存服务器收到一个请求,只有当前的请求和原始(缓存)的请求头跟缓存的响应头里的Vary都匹配,才能使用缓存的响应。

The Vary header leads cache to use more HTTP headers as key for the cache.

使用vary头有利于内容服务的动态多样性。例如,使用Vary: User-Agent头,缓存服务器需要通过UA判断是否使用缓存的页面。如果需要区分移动端和桌面端的展示内容,利用这种方式就能避免在不同的终端展示错误的布局。另外,它可以帮助google或者其他搜索引擎更好地发现页面的移动版本,并且告诉搜索引擎没有引入Cloaking

Vary: User-Agent

因为移动版和桌面的客户端的请求头中的User-Agent不同, 缓存服务器不会错误地把移动端的内容输出到桌面端到用户。

参考

文档标签和贡献者

标签: 
 最后编辑者: marsoln,