HTTP Caching 缓存

547 阅读8分钟
原文链接: www.shuaihua.cc

应用缓存技术能极大提升网站和应用的性能,重复利用之前获取的资源在浏览器的缓存而不需向服务器重新请求完整的资源。web缓存大大减少网络请求和字节传输,因此网页呈现在用户面前的所需的时间缩短了,自然提升用户体验。这一切得益于HTTP缓存技术协议的提出。

不同类型的缓存

缓存是将经常请求的但资源内容不常变动的资源存储在本地的技术,当用户请求被缓存了的资源时,用户代理阻断向服务器发送的请求,取而代之将本地缓存的资源返回给用户,避免资源的重新下载。这一机制有许多:缓存机制实际将用户代理与服务器的距离拉的更近。需要合理的配置缓存,不需要让所有资源都永久缓存起来,适当的在资源发生改变时更新缓存是很重要的提升性能的方法。

缓存的类型有多种,大体可分为两种类型的缓存:私有缓存和共享缓存。共享缓存可以被超过一个用户重新使用,私有缓存被单一用户代理专有。本篇文章主要介绍浏览器缓存与代理缓存,除了这两种缓存外,还有gateway缓存、CDN、反向代理缓存和部署在服务器上的负载平衡技术,提供更好的可靠性、更强性能和具有弹性的网站与应用。


私有的浏览器缓存

私有缓存为单一用户专有。你也许在浏览器的设置页面见过“缓存”设置项,浏览器缓存持有通过HTTP协议传输的文档,这一机制是为了从历史记录回退到之前浏览过的web页面上,可以不向服务器发送额外请求,这很像离线浏览网页或应用。


共享的代理缓存

共享的缓存是指将缓存的资源作为响应共享给其他的用户,而不需再次向服务器发送请求。举个例子,ISP网络服务提供商或你的公司可能设置了网络代理作为本地网络基础设施,如此一来,代理可以将受欢迎的资源缓存起来,当局域网内的计算机再请求这些资源时,无需向这些资源真正存放的服务器发起请求,而直接使用代理缓存起来的数据即可,这样减少了网络传输时间也节约了宽带流量。可能你已经注意到这样存在一个问题,那就是如果真正存放资源的服务器修改了该资源,代理服务器如何知道缓存是否过时了?是否需要重新向真正存放资源的服务器发起请求并更新缓存,这个问题下来会讲到。

缓存限制条件

是否启用HTTP缓存是可选的,通常情况下,典型的HTTP缓存限制在请求方法为GET的时候,会限制其他请求方法的缓存的使用。使用缓存需要满足以下条件:

  • 1、成功接收请求的响应:响应代码为200(OK)的包含HTML文档、图像或文件的GET请求。
  • 2、永久重定向:响应代码为301(Moved Permanently)的响应。
  • 3、错误响应:404(Not Found)页面。
  • 4、未完全传输的结果:响应代码为206(Partial Content)响应。
  • 5、不为GET请求,但响应头定义了缓存标志的情况。

缓存控制

Cache-control 响应头

Cache-Control 请求头在HTTP1.1协议中定义,该header可以应用于请求头和响应头中,使用该header可以定义缓存信息。

  • No cache storage at all

代理不会缓存任何客户端请求和服务器响应,当向服务器发送一个请求,代理会完整的接收任何响应。

Cache-Control: no store
Cache-Control: no-cache, no-store, must-revalidate
  • No caching

缓存将将发送请求到原始服务器,来验证是否要释放缓存。

Cache-Control: no-cache
  • Private and public caches

public 指令表示响应将会被任何缓存缓存起来,这很有用,当某页面需要HTTP认证时或者响应状态码并非常规的可缓存内容时,如此设置,便可缓存。另一方面, private 表示响应不可被共享缓存存储而只能为单一用户使用。这种情况下私有浏览器缓存可以存储缓存。

Cache-Control: private
Cache-Control: public
  • Expiration

最重要的指令就是 max-age= ,代理将该秒数表示的时长认为是响应资源可以被当作未过期的最长时间。与 Expires 不同的是,该指令中的时间相对于请求时的时间,而 Expires 指的是具体的日期。对于应用和网站中不改变的文件,可以使用缓存技术,包括诸如CSS文件和JavaScripts文件的静态文件。

Cache-Control: max-age=31536000
  • Validation

当使用 must-revalidate 指令后,在代理使用缓存的资源之前,必须先验证旧有资源的状态,如果发现资源已经过期,就不能再使用缓存的资源了。

Cache-Control: must-revalidate

Pragma header

Pragma 是HTTP/1.0提出的header。该header不能在响应头中指定,只在HTTP/1.0版本的请求中可用。因此在HTTP/1.1中不推荐使用 Pragma。尽管 Cache-Control: no-cache header和 Pragma 类似。在请求头中加入 Pragma header是为了向后兼容仅支持HTTP/1.0的客户端。

保持资源永远最新

一旦资源存储在缓存中,理论上,缓存可以被永久服务于被缓存起来的资源。缓存应该是有时间限制的,这样一来才能定期的从存储中移除缓存,这个过程叫做缓存迁出。另一方面,一些服务器中的资源会更新,因此本地缓存也需要跟着更新。由于HTTP协议规定了客户端主动请求和服务器被动响应的机制,服务器无法在资源更新时联系缓存和客户端。因此服务器必须和缓存沟通一个过期时间,在过期时间之前资源是新鲜的,过期时间之后,该资源就是过时的。当缓存中的资源被认定是过时时,代理会向原始服务器发送请求,并携带 If-None-Match header,以此检查该资源是否仍然是新鲜的,如果服务器中该资源未修改过,服务器的响应状态码为 304(Not Modified) ,并且服务器不会返回响应体(也就是不会返回资源本身),从而节约宽带。

根据几个header可以计算出是否需要向服务器发送请求判断该资源是否过期。如果指定了 Cache-Control: max-age=N ,该资源保持新鲜的时长就是 N ,如果不存在该header,代理将会检查 Expires header是否存在,如果该header存在,那么该值减去 Date header的值就是资源的生命周期时长。如果以上两个header都未定义,会检查 Last-Modified header,如果存在该header,那么该资源的生命周期等于 Date header的值减去 Last-Modified 的值,然后再除以10。

为资源添加版本号

当你应用缓存技术,网站的响应效率和性能会得到极大提升。为了优化缓存,最好为使用缓存技术的资源添加过期时间header,这个过期时间尽可能的设置更长一些。通常我们会定期更新资源或经常更新资源,但问题是,对于设置了很长的缓存时间的资源来说并不容易更新资源。通常来说CSS和JavaScript文件不会经常更新,但更新之后又希望能快速的更新缓存,而不是等待直到过期时间的到来。

WEB开发者创造了一种解决这一问题的办法,其作者 Steve Sounders 称之为 revving ,在资源的名称(通常为URL)中添加数字,改变版本号就意味着资源更新了,缓存也会跟着更新。

http://www.example.com/static/index.css?v=1

验证缓存

当用户按下浏览器刷新按钮时出发重新验证。一般情况下,当响应头中包含 Cache-Control: must-revalidate 时也会验证缓存。

当接近文档的过期时间,缓存会重新验证或再次获取,只有当服务器提供了验证器或弱验证器时才会出发验证。

ETags

ETags header属于强验证,且它对代理是不透明的,也就是说,用户代理不止带该值的具体含义。如果响应中包含该header,那个之后的请求中,用户代理可以添加 If-None-Match header,以验证缓存资源。

Last-Modified 属于弱验证,当响应头中包含该header,那么之后的请求头中可包含 If-Modified-Since header,以验证缓存文档。

当服务器接收到请求验证的请求时,服务器可以选择忽略请求中的验证部分,直接使用常见的 200 状态码,或者返回 304状态码并包含空的响应体,其后的响应头中可以包含用于更新过期时间的header。

Varying responses