3.7 Web缓存

什么是Web缓存

Web缓存是指HTTP协议定义的缓存。它可以有效提高网站的访问速度、降低对网络带宽的消耗。

Web缓存包括网站的反向代理(Reverse Proxy)缓存,中间代理(Intermediary Proxy)缓存以及浏览器缓存,如图所示:

web-cache

我们在上一节中提到的Web缓存,专指通过网站反向代理提供的Web缓存——但Web缓存的工作原理都是一样的。

Web缓存的原理

图中,浏览器发出的HTTP请求在到达源服务器之前要经过一个中间代理和一个反向代理(一共涉及三次HTTP会话),连同浏览器本身在内一共有三个缓存。如果所请求的资源已经存在于任何一个缓存之中并且没有过期,那么这个缓存就可以用于响应对该资源的请求,而不必继续向源服务器转发请求。如果该资源没有被缓存,请求最终到达源服务器,源服务器可以给被请求的资源指定一个过期时间(通过HTTP应答头),这样当HTTP应答原路返回之后,该资源可以被路径上的缓存所缓存起来以备后续使用。另外,当缓存的资源过期后,如果客户端(可能是浏览器、中间代理或者反向代理)再次请求该资源,可以(通过HTTP请求头)附上一些对该资源的校验条件(Validator),服务器如果通过校验,则说明资源没有改变、客户端可以继续使用缓存的资源。

一个具体的例子如下:

程序从浏览器发起一个HTTP请求(比如通过XMLHttpRequest):

GET /some/resourse HTTP/1.1
Host: www.example.com

如果资源/some/resourse没有被缓存,最终它将到达www.example.com的源服务器。后者可以做如下应答:

HTTP/1.1 200 OK
Date: Sun, 07 Aug 2016 08:50:58 GMT
Cache-Control: public, max-age=3600
ETag: 167a264dd3694616870b98d613ae7700
Content-Type: application/json
Content-Length: 1234
...

应答头Cache-Control: public, max-age=3600表明:该资源可以做(公共)缓存,有效期3600秒——从Date指示的应答产生时间开始计算。这个应答会被浏览器以及浏览器和源服务器之间的代理(Proxy)所缓存下来。如果在3600秒内从浏览器再发起相同的GET /some/resourse请求,那么缓存而不是源服务器就可以做出(相同的)应答——在这个例子中浏览器缓存会在第一时间做出应答(而发起HTTP请求的程序甚至感觉不到缓存的存在)。如果超过了3600秒,缓存失效,当程序再发起同样的HTTP请求时,浏览器会发出一个Conditional GET,如下:

GET /some/resourse HTTP/1.1
Host: www.example.com
If-None-Match: 167a264dd3694616870b98d613ae7700

请求头If-None-Match表示:如果/some/resourse所代表的资源变化了就给我发一份新的。该请求头的值就是上次应答中的Etag应答头的值,它用来校验资源是否改变。因此If-None-Match这样的请求头又称作Validator。

如果资源发生了改变,服务器应当返回一个新的资源表述,否则,它应该返回代码304,指示资源没有变化,如下:

HTTP/1.1 304 Not Modified
Date: Sun, 07 Aug 2016 09:55:57 GMT
Cache-Control: public, max-age=3600
ETag: 167a264dd3694616870b98d613ae7700

这个应答不带有消息实体(Message Body)。浏览器和中间缓存收到这个应答会更新该资源的缓存过期时间,同时用原来缓存的应答(加上更新过的应答头)返回给发起HTTP请求的程序。

除了Cache-ControlETag,服务器还可以使用Expires来指明缓存过期的时间,用Last-Modified指明资源的最新修改时间——这样Conditional GET可以使用If-Modified-Since作为Validator。例如:

GET /some/resourse HTTP/1.1
Host: www.example.com
If-Modified-Since: Fri, 05 Aug 2016 10:00:00 GMT
HTTP/1.1 200 OK
Date: Sun, 07 Aug 2016 08:50:58 GMT
Expires: Sun, 07 Aug 2016 09:50:58 GMT
Last-Modified: Fri, 05 Aug 2016 16:00:00 GMT
Content-Type: application/json
Content-Length: 1234
...

除了设置过期时间和校验,HTTP还提供了一些机制来控制缓存的请求和应答:

  • 客户端可以通过指定Cache-Control: max-age=0来要求服务器提供一个非缓存的最新版本(一般地,浏览器的“刷新”操作会自动加入这个指令)
  • 缓存一般只针对GET和HEAD方法,其他方法的应答缺省不会被缓存,但服务器可以通过Cache-Control: public, ...指令来override此规则
  • 如果应答含有Authentication或者Set-Cookie应答头,该应答缺省不会被缓存,但服务器仍可以通过Cache-Control指令来override此规则,比如Cache-Control: private, ...指示该应答可以作为(浏览器的)私有缓存,而不应被某个公开代理所缓存
  • 服务器可以通过指定Cache-Control: public, no-cache=HEADER_NAME_LIST来指定一个列表,列表里的应答头都不会被缓存,应答的其它部分则会被缓存
  • 客户端或者服务器可以通过指定Cache-Control: no-store来要求应答不被缓存

Cache-Control强大而灵活,关于它的更多介绍请参考HTTP协议文本14.9 Cache-Control,在这之前我推荐读者先阅读上面提到的HTTP协议文本13 Caching in HTTP。Web缓存是HTTP协议的重要组成部分,也是最复杂的部分。如果读者要透彻地理解其原理,HTTP协议文本是首要读物。

如何利用Web缓存来加速网站访问

在你还没有意识到的时候,浏览器已经在主动地利用Web缓存来工作了;另外,Web服务器,如Apache、Nginx,缺省地会为网站的静态文件,如.css、.js、.png等,加上ETag等应答头,这样客户端第二次及以后请求这些文件就可以利用Conditional GET来工作了。但我们还可以主动利用Web缓存把事情做得更好:

  • 我们可以给网站设置Web缓存,比如通过反向代理,来缓存服务器动态生成的内容
  • 我们可以给一些动态资源显示地指定一个过期时间
  • 我们应当小心处理Cookie和Authentication,避免在不必要地方使用它们,以使动态内容可以被公开(public)缓存;在最坏的情况下,我们仍然能让带有这些应答头的应答作为私有(private)缓存

善用Web缓存,提升用户体验。

results matching ""

    No results matching ""