Web缓存基础:术语、HTTP报头和缓存策略
简介
对于您的站点的访问者来说,智能化的内容缓存是提高用户体验最有效的方式之一。缓存,或者对之前的请求的临时存储,是HTTP协议实现中最核心的内容分发策略之一。分发路径中的组件均可以缓存内容来加速后续的请求,这受控于对该内容所声明的缓存策略。
在这份指南中,我们将讨论一些Web内容缓存的基本概念。这主要包括如何选择缓存策略以保证互联网范围内的缓存能够正确的处理您的内容。我们将谈一谈缓存带来的好处、副作用以及不同的策略能带来的性能和灵活性的最大结合。
什么是缓存(caching)?
缓存(caching)是一个描述存储可重用资源以便加快后续请求的行为的术语。有许多不同类型的缓存,每种都有其自身的特点,应用程序缓存和内存缓存由于其对特定回复的加速,都很常用。
这份指南的主要讲述的Web缓存是一种不同类型的缓存。Web缓存是HTTP协议的一个核心特性,它能最小化网络流量,并且提升用户所感知的整个系统响应速度。内容从服务器到浏览器的传输过程中,每个层面都可以找到缓存的身影。
Web缓存根据特定的规则缓存相应HTTP请求的响应。对于缓存内容的后续请求便可以直接由缓存满足而不是重新发送请求到Web服务器。
好处
有效的缓存技术不仅可以帮助用户,还可以帮助内容的提供者。缓存对内容分发带来的好处有:
- 减少网络开销:内容可以在从内容提供者到内容消费者网络路径之间的许多不同的地方被缓存。当内容在距离内容消费者更近的地方被缓存时,由于缓存的存在,请求将不会消耗额外的网络资源。
- 加快响应速度:由于并不是必须通过整个网络往返,缓存可以使内容的获得变得更快。缓存放在距用户更近的地方,例如浏览器缓存,使得内容的获取几乎是瞬时的。
- 在同样的硬件上提高速度:对于保存原始内容的服务器来说,更多的性能可以通过允许激进的缓存策略从硬件上压榨出来。内容拥有者们可以利用分发路径上某个强大的服务器来应对特定内容负载的冲击。
- 网络中断时内容依旧可用:使用某种策略,缓存可以保证在原始服务器变得不可用时,相应的内容对用户依旧可用。
术语
在面对缓存时,您可能对一些经常遇到的术语可能不太熟悉。一些常见的术语如下:
- 原始服务器:原始服务器是内容的原始存放地点。如果您是Web服务器管理员,它就是您所管理的机器。它负责为任何不能从缓存中得到的内容进行回复,并且负责设置所有内容的缓存策略。
- 缓存命中率:一个缓存的有效性依照缓存的命中率进行度量。它是可以从缓存中得到数据的请求数与所有请求数的比率。缓存命中率高意味着有很高比例的数据可以从缓存中获得。这通常是大多数管理员想要的结果。
- 新鲜度:新鲜度用来描述一个缓存中的项目是否依旧适合返回给客户端。缓存中的内容只有在由缓存策略指定的新鲜期内才会被返回。
- 过期内容:缓存中根据缓存策略的新鲜期设置已过期的内容。过期的内容被标记为“陈旧”。通常,过期内容不能用于回复客户端的请求。必须重新从原始服务器请求新的内容或者至少验证缓存的内容是否仍然准确。
- 校验:缓存中的过期内容可以验证是否有效以便刷新过期时间。验证过程包括联系原始服务器以检查缓存的数据是否依旧代表了最近的版本。
- 失效:失效是依据过期日期从缓存中移除内容的过程。当内容在原始服务器上已被改变时就必须这样做,缓存中过期的内容会导致客户端发生问题。
还有许多其他的缓存术语,不过上面的这些应该能帮助您开始。
什么能被缓存?
某些特定的内容比其他内容更容易被缓存。对大多数站点来说,一些适合缓存的内容如下:
- Logo和商标图像
- 普通的不变化的图像(例如,导航图标)
- CSS样式表
- 普通的Javascript文件
- 可下载的内容
- 媒体文件
这些文件更倾向于不经常改变,所以长时间的对它们进行缓存能获得好处。
一些项目在缓存中必须加以注意:
- HTML页面
- 会替换改变的图像
- 经常修改的Javascript和CSS文件
- 需要有认证后的cookies才能访问的内容
一些内容从来不应该被缓存:
- 与敏感信息相关的资源(银行数据,等)
- 用户相关且经常更改的数据
除上面的通用规则外,通常您需要指定一些规则以便于更好地缓存不同种类的内容。例如,如果登录的用户都看到的是同样的网站视图,就应该在任何地方缓存这个页面。如果登录的用户会在一段时间内看到站点中用户特定的视图,您应该让用户的浏览器缓存该数据而不应让任何中介节点缓存该视图。
Web内容缓存的位置
Web内容会在整个分发路径中的许多不同的位置被缓存:
- 浏览器缓存:Web浏览器自身会维护一个小型缓存。典型地,浏览器使用一种策略指示缓存最重要的内容。这可能是用户相关的内容或可能会再次请求且下载代价较高。
- 中间缓存代理:任何在客户端和您的基础架构之间的服务器都可以按期望缓存一些内容。这些缓存可能由ISP(网络服务提供者)或者其他独立组织提供。
- 反向缓存:您的服务器基础架构可以为后端的服务实现自己的缓存。如果实现了缓存,那么便可以在处理请求的位置返回相应的内容而不用每次请求都使用后端服务。
上面的这些位置通常都可以根据它们自身的缓存策略和内容源的缓存策略缓存一些相应的内容。
缓存头部
缓存策略依赖于两个不同的因素。所缓存的实体本身需要决定是否应该缓存可接受的内容。它可以只缓存部分可以缓存的内容,但不能缓存超过限制的内容。
缓存行为主要由缓存策略决定,而缓存策略由内容拥有者设置。这些策略主要通过特定的HTTP头部来清晰地表达。
经过几个不同HTTP协议的变化,出现了一些不同的针对缓存方面的头部,它们的复杂度各不相同。下面列出了那些你也许应该注意的:
- **
Expires
**:尽管使用范围相当有限,但Expires
头部是非常简洁明了的。通常它设置一个未来的时间,内容会在此时间过期。这时,任何对同样内容的请求都应该回到原始服务器处。这个头部或许仅仅最适合回退模式(fall back)。 - **
Cache-Control
**:这是Expires
的一个更加现代化的替换物。它已被很好的支持,且拥有更加灵活的实现。在大多数案例中,它比Expires
更好,但同时设置两者的值也无妨。稍后我们将讨论您可以设置的Cache-Control
的详细选项。 - **
ETag
**:ETag
用于缓存验证。源服务器可以在首次服务一个内容时为该内容提供一个独特的ETag
。当一个缓存需要验证这个内容是否即将过期,他会将相应的ETag
发送回服务器。源服务器或者告诉缓存内容是一致的,或者发送更新后的内容(带着新的ETag
)。 Last-Modified
:这个头部指明了相应的内容最后一次被修改的时间。它可能会作为保证内容新鲜度的验证策略的一部分被使用。- **
Content-Length
**:尽管并没有在缓存中明确涉及,Content-Length
头部在设置缓存策略时很重要。某些软件如果不提前获知内容的大小以留出足够空间,则会拒绝缓存该内容。 - **
Vary
**:缓存系统通常使用请求的主机和路径作为存储该资源的键。当判断一个请求是否是请求同样内容时,Vary
头部可以被用来提醒缓存系统需要注意另一个附加头部。它通常被用来告诉缓存系统同样注意Accept-Encoding
头部,以便缓存系统能够区分压缩和未压缩的内容。
Vary头部的隐语
Vary
头部提供给您存储同一个内容的不同版本的能力,代价是降低了缓存的容量。
在使用Accept-Encoding
时,设置Vary
头部允许明确区分压缩和未压缩的内容。这在服务某些不能处理压缩数据的浏览器时很重要,它可以保证基本的可用性。Vary
的一个典型的值是Accept-Encoding
,它只有两到三个可选的值。
一开始看上去User-Agent
这样的头部可以用于区分移动浏览器和桌面浏览器,以便您的站点提供差异化的服务。但User-Agent
字符串是非标准的,结果将会造成在中间缓存中保存同一内容的许多不同版本的缓存,这会导致缓存命中率的降低。Vary
头部应该谨慎使用,尤其是您不具备在您控制的中间缓存中使请求标准化的能力(也许可以,比如您可以控制CDN的话)。
缓存控制标志怎样影响缓存
上面我们提到了Cache-Control
头部如何被用与现代缓存策略标准。能够通过这个头部设定许多不同的缓存指令,多个不同的指令通过逗号分隔。
一些您可以使用的指示内容缓存策略的Cache-Control
的选项如下:
no-cache
:这个指令指示所有缓存的内容在新的请求到达时必须先重新验证,再发送给客户端。这条指令实际将内容立刻标记为过期的,但允许通过验证手段重新验证以避免重新下载整个内容。no-store
:这条指令指示缓存的内容不能以任何方式被缓存。它适合在回复敏感信息时设置。public
:它将内容标记为公有的,这意味着它能被浏览器和其他任何中间节点缓存。通常,对于使用了HTTP验证的请求,其回复被默认标记为private
。public
标记将会覆盖这个设置。private
:它将内容标记为私有的。私有数据可以被用户的浏览器缓存,但不能被任何中间节点缓存。它通常用于用户相关的数据。max-age
:这个设置指示了缓存内容的最大生存期,它在最大生存期后必须在源服务器处被验证或被重新下载。在现代浏览器中这个选项大体上取代了Expires
头部,浏览器也将其作为决定内容的新鲜度的基础。这个选项的值以秒为单位表示,最大可以表示一年的新鲜期(31536000秒)。s-maxage
:这个选项非常类似于max-age
,它指明了内容能够被缓存的时间。区别是这个选项只在中间节点的缓存中有效。结合这两个选项可以构建更加灵活的缓存策略。must-revalidate
:它指明了由max-age
、s-maxage
或Expires
头部指明的新鲜度信息必须被严格的遵守。它避免了缓存的数据在网络中断等类似的场景中被使用。proxy-revalidate
:它和上面的选项有着一样的作用,但只应用于中间的代理节点。在这种情况下,用户的浏览器可以在网络中断时使用过期内容,但中间缓存内容不能用于此目的。no-transform
:这个选项告诉缓存在任何情况下都不能因为性能的原因修改接收到的内容。这意味着,缓存不允许压缩接收到的内容(没有从原始服务器处接收过压缩版本的该内容)并发送。
这些选项能够以不同的方式结合以获得不同的缓存行为。一些互斥的值如下:
no-cache
,no-store
以及由其他前面未提到的选项指明的常用的缓存行为public
和private
如果no-store
和no-cache
都被设置,那么no-store
会取代no-cache
。对于非授权的请求的回复,public
是隐含的设置。对于授权的请求的回复,private
选项是隐含的。他们可以通过在Cache-Control
头部中指明相应的相反的选项以覆盖。
开发一种缓存策略
在理想情况下,任何内容都可以被尽可能缓存,而您的服务器只需要偶尔的提供一些验证内容即可。但这在现实中很少发生,因此您应该尝试设置一些明智的缓存策略,以在长期缓存和站点改变的需求间达到平衡。
常见问题
在许多情况中,由于内容被产生的方式(如根据每个用户动态的产生)或者内容的特性(例如银行的敏感数据),这些内容不应该被缓存。另一些许多管理员在设置缓存时可能面对的问题是外部缓存的数据未过期,但新版本的数据已经产生。
这些都是经常遇到的问题,它们会影响缓存的性能和您提供的数据的准确性。然而,我们可以通过开发提前预见这些问题的缓存策略来缓解这些问题。
一般性建议
尽管您的实际情况会指导您选择的缓存策略,但是下面的建议能帮助您获得一些合理的决定。
在您担心使用哪一个特定的头部之前,有一些特定的步骤可以帮助您提高您的缓存命中率。一些建议如下:
- 为图像、CSS和共享的内容建立特定的文件夹:将内容放到特定的文件夹内使得您可以方便的从您的站点中的任何页面引用这些内容。
- 使用同样的URL来表示同样的内容:由于缓存使用内容请求中的主机名和路径作为键,因此应保证您的所有页面中的该内容的引用方式相同,前一个建议能让这点更加容易做到。
- 尽可能使用CSS图像拼接:对于像图标和导航等内容,使用CSS图像拼接能够减少渲染您页面所需要的请求往返,并且允许对拼接缓存很长一段时间。
- 尽可能将主机脚本和外部资源本地化:如果您使用Javascript脚本和其他外部资源,如果上游没有提供合适的缓存头部,那么您应考虑将这些内容放在您自己的服务器上。您应该注意上游的任何更新,以便更新本地的拷贝。
- 对缓存内容收集文件摘要:静态的内容比如CSS和Javascript文件等通常比较适合收集文件摘要。这意味着为文件名增加一个独特的标志符(通常是这个文件的哈希值)可以在文件修改后绕开缓存保证新的内容被重新获取。有很多工具可以帮助您创建文件摘要并且修改HTML文档中的引用。
对于不同的文件正确地选择不同的头部这件事,下面的内容可以作为一般性的参考:
- 允许所有的缓存存储一般内容:静态内容以及非用户相关的内容应该在分发链的所有节点被缓存。这使得中间节点可以将该内容回复给多个用户。
- 允许浏览器缓存用户相关的内容:对于每个用户的数据,通常在用户自己的浏览器中缓存是可以被接受且有益的。缓存在用户自身的浏览器能够使得用户在接下来的浏览中能够瞬时读取,但这些内容不适合在任何中间代理节点缓存。
- 将时间敏感的内容作为特例:如果您的数据是时间敏感的,那么相对上面两条参考,应该将这些数据作为特例,以保证过期的数据不会在关键的情况下被使用。例如,您的站点有一个购物车,它应该立刻反应购物车里面的物品。依据内容的特点,可以在
Cache-Control
头部中使用no-cache
或no-store
选项。 - 总是提供验证器:验证器使得过期的内容可以无需重新下载而得到刷新。设置
ETag
和Last-Modified
头部将允许缓存向原始服务器验证内容,并在内容未修改时刷新该内容新鲜度以减少负载。 - 对于支持的内容设置长的新鲜期:为了更加有效的利用缓存,一些作为支持性的内容应该被设置较长的新鲜期。这通常比较适合图像和CSS等由用户请求用来渲染HTML页面的内容。和文件摘要一起,设置延长的新鲜期将允许缓存长时间的存储这些资源。如果资源发生改变,修改的文件摘要将会使缓存的数据无效并触发对新的内容的下载。那时,新的支持的内容会继续被缓存。
- 对父内容设置短的新鲜期:为了使得前面的模式正常工作,容器类的内容应该相应的设置短的新鲜期,或者设置不全部缓存。这通常是在其他协助内容中使用的HTML页面。这个HTML页面将会被频繁的下载,使得它能快速的响应改变。支持性的内容因此可以被尽量缓存。
关键之处便在于达到平衡,一方面可以尽量的进行缓存,另一方面为未来保留当改变发生时从而改变整个内容的机会。您的站点应该同时具有:
- 尽量缓存的内容
- 拥有短的新鲜期的缓存内容,可以被重新验证
- 完全不被缓存的内容
这样做的目的便是将内容尽可能的移动到第一个分类(尽量缓存)中的同时,维持可以接受的缓存命中率。