性能主题

Web缓存教程

  这是一篇针对网站站长、Web开发者与运营维护人员有关缓存Cache的教程。Web缓存是指存在多个Web服务器和客户端之间的缓存,将对请求的响应保存复制拷贝,比如HTML页面、图片和文件,如果从同样的URL有另外一个请求进来,将首先从Web缓存中获得该URL的响应拷贝,而不是再直接向原始服务器获取。

使用Web缓存有两个理由:

  • 能够降低延时 — 因为请求的结果(表现界面)可以从缓存中获得,而这个缓存相比原始服务器是最靠近客户端的,这样就在获取途径上花费更少的时间,使得你的Web网站更具有响应性。
  • 降低网络流量 — 因为表现界面可以重用,它降低了客户端使用的带宽,因为客户端可以在自己本地获取缓存拷贝,不必要到服务器获取,这节约了流量,等于节省了金钱。

 

Web缓存种类

浏览器缓存  

  如果你检查一下任何现代浏览器的首选项对话框(如Internet Explorer , Safari或Mozilla) ,你可能会注意到一个“缓存”的设置。这让你设置你的电脑硬盘上的一部分空间来存储你浏览过的页面。浏览器缓存根据非常简单的规则运作。它会进行检查以确保该页面是新鲜,通常一个会话一次(即在浏览器的当前调用中)。

  这个缓存是非常有用的,当用户点击“后退”按钮或点击一个链接,这样就能查看他们刚刚看过的页面。另外,如果你在你的网站上使用相同的导航方式,这些页面也会使用浏览器的缓存提供这种服务,从而加速加载。

 

代理缓存

  Web的代理缓存的工作原理和浏览器缓存原理是一样的,但规模更大。代理服务数可以同样的方式服务几百个或数千个用户;大型企业和互联网服务供应商ISP往往将它们设置在他们的防火墙,还是作为独立的代理设备(也称为中介) 。

  因为代理缓存不是在客户端或源服务器的一部分,而是在网络上,请求必须以某种方式路由转发到它们上面。一种方法是手工设置浏览器的代理配置,配置到相应的代理服务器;另一种方法是使用拦截。拦截代理会通过基础网络本身将Web请求重定向到他们上面,客户就不必对它们进行手工配置,甚至不知道他们的存在。

  代理缓存是一种共享缓存;它们通常有大量的用户,并且就是因为这一点,访问量越大,缓存的效果越好,大幅度减少用户的等待时间和网络流量。这是因为越是访问次数多的页面重复使用的次数越高。

 

网关缓存

  也被称为“反向代理缓存”或“surrogate 代理缓存, ”网关缓存也是中间人,但不是由系统网络管理员出于节省带宽而部署,它们通常是由Webmaster网站站长等自己部署的,这样让自己的网站更具可扩展性,可靠性和性能更好。

  请求可以被通过许多方法来路由到网关高速缓存,但通常它是某种形式的负载平衡器。

  内容交付网络Content delivery networks (CDN) 通过互联网分发网关缓存,把缓存卖给网站。

本篇主要聚焦浏览器和代理缓存。

 

Web缓存的好处

  Web缓存是互联网上最容易被误解的技术之一。尤其是网站管理者害怕失去对网站的控制权,因为代理缓存可以将他们的用户“隐藏”在自己身后,使得网站管理者很难看到谁在使用该网站。不幸的是,即使网络缓存不存在的,在互联网上也有太多的变量,使得网站管理者无法确切了解用户是如何浏览他们的网站。

  另外一个问题是:缓存的内容会失效过时,变成脏数据,本教材会告诉你如何配置你的服务器来控制内容的缓存。不过,缓存失效没有一个通用的算法来解决,只能根据具体情况针对性解决。

  网站缓存会让你的网站网页内容加载得更快,减轻你的服务器和网络负载。用户如果感觉你的网站加载很快,也就会更频繁访问,同时。搜索引擎如果发现你的网站更快,将会引导更多用户访问你的网站,这对于网站的SEO是有利的。

  许多大型互联网公司在全世界各地花巨资复制它们的内容,就是为了让当地用户更快地访问,缓存也是同样的道理,它们更接近终端用户,而且你不需要支付任何费用。

  其实,代理缓存和浏览器缓存无论你喜欢或不喜欢都会被采用,如果你不正确配置你的网站使用缓存,它们会使用默认的缓存策略使用缓存。

 

Web缓存是如何工作的?

  所有缓存都有一系列配置来决定什么时候从缓存中获取页面,一些规则是在协议如HTTP 1.0和1.1中设置,一些是由缓存的管理员设置,或者是浏览器缓存的用户,或者是代理缓存的管理者。通常有下面这些通用的规则:

  • 如果Http响应的头部告诉浏览器缓存不要保存它,浏览器缓存就不会保存该页面
  • 如果请求是授权或加密(如 HTTPS), 那么其响应结果不会被缓存。
  • 一个已经被缓存的页面如果满足下面条件会被认为是新鲜的,所谓新鲜意思是不要检查源服务器就能发往客户端:
    • 在Http头部有失效时间或其他时间年龄控制的信息,现在该页面还存在于有效期呢。
    • 如果缓存发现这个页面是相对之前的修改是最近一次。
    新鲜的页面会直接从缓存中获得,而无需检查原始服务器。
  • 如果页面是旧的过时的,原始服务器会被要求进行校验,或告诉缓存它里面的拷贝是否正常。
  • 在某些情况下,比如网络失联情况下,缓存在不检查原始服务器的情况下会提供旧的页面。

  如果一个响应中没有出现验证器(ETag或Last-Modified头),那它就没有任何明确的刷新信息,它通常会 - 但并不总是 - 被认为不可被缓存的。

  总之,新鲜度和验证是缓存内容的工作原理与最重要途径。新鲜页面能够立即从高速缓存中获得,而一个验证表示如果它并没有被改变过,就可以避免发送整个页面的内容。

 

如何控制缓存

  有几个工具可以用于微调缓存:

HTML Meya标签和HTTP头部

  HTML产生者能够将一些标签放入Html文档的<HEAD>部分,作为其属性描述,这些meta标签经常用于告诉浏览器该Html文档是否可被缓存或在多长时间后失效。Meta标签易于使用,但是并不非常有效率,这是因为它们只被少数浏览器实现,而代理缓存不会承认,因为代理缓存几乎不会阅读文档中HTML语法,当我们试图使用Pragma: no-cache到meta标签,这不一定会让该文档一直保持新鲜度,也就是每次请求都会从原始服务器获取,原因也是因为代理缓存可能会缓存它,尽管你在浏览器保持刷新,浏览器的请求首先经过代理缓存。

  而只有HTTP协议的头部才会让你更有力地控制浏览器缓存和代理缓存,它们都不必打开文档阅读Html,通常这些HTTP协议头部信息是由Web服务器产生,比如Nginx或Tomcat,依据你的服务器,你能在某种程度上控制它们。

  Http头部会在Html之前被Web服务器发送,只能被浏览器内部等机器阅读,典型地HTTP 1.1响应头部信息如下:

HTTP/1.1 200 OK
Date: Fri, 30 Oct 1998 13:19:41 GMT
Server: Apache/1.3.3 (Unix)
Cache-Control: max-age=3600, must-revalidate
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Last-Modified: Mon, 29 Jun 1998 02:28:12 GMT
ETag: "3e86-410-3596fbbc"
Content-Length: 1040
Content-Type: text/html

HTML会跟在这些头部信息后面,它们之间使用空行分割。

很多人喜欢在HTTP头部设置Pragma: no-cache以避免缓存,这并不总是有效,HTTP规定并没有任何有关Pragma响应头部的规定,Pragma请求头正在讨论中。

使用HTTP头中Expires进行缓存刷新,它是控制缓存的基本手段,它告诉缓存其缓存的内容在多次时间以后就失效了,在失效以后,缓存会再次检查源服务器看看该页面是否已经被改变。

Expires对于静态图片缓存非常有效,因为图片不总是在改变,你可以设置很长的expire时间。比如在Nginx中设置图片有效期是60天:

location ~ .*\.(gif|mp4|jpg|png|ico|css|swf|js$)(.*) {
expires 60d;
...
}

这样你在Http头部看到Expires时间是60天以后那个精确时间格式。

你也可以在Java等Tomcat中配置一个Filter插件:

<filter>
<filter-name>ExpiresFilter</filter-name>
<filter-class>com.jdon.jivejdon.presentation.filter.ExpiresFilter</filter-class>
<init-param>
<param-name>ExpiresDefault</param-name>
<param-value>access plus 60 minutes</param-value>
</init-param>
<init-param>
<param-name>ExpiresByType image</param-name>
<param-value>access plus 10 days</param-value>
</init-param>
<init-param>
<param-name>ExpiresByType text/css</param-name>
<param-value>access plus 10 days</param-value>
</init-param>
<init-param>
<param-name>ExpiresByType text/javascript</param-name>
<param-value>access plus 10 days</param-value>
</init-param>
</filter>

在ExpiresFilter中对Http头部设置Expires:

              response.setDateHeader(HEADER_EXPIRES, expirationDate.getTime());

 

尽管Expire非常有效,也有其缺点,问题是因为其日期,Web服务器的时钟和缓存位置时间必须同步,如果它们的时间不同,可能导致缓存更新不及时;另外一个问题是设置的过期失效时间如果同时在失效那个时间访问你的服务器,会增加负载和延迟。

 

Cache-Control HTTP头部

  HTTP 1.1引入了新的头部缓存控制:Cache-Control,弥补了Expire的缺点,Cache-Control有几个值总结如下:

  • max-age=[多少秒] — 规定一个页面在缓存中有效的最大时间,类似Expires, 这是一个相对现在开始的时间。
  • s-maxage=[多少秒] — 类似于max-age, 但是它只适用共享的代理缓存。
  • public — 标记授权的响应也是可缓存的,正常情况下,如果HTTP授权被激活,其响应内容将是自动设为私有的。
  • private — 允许缓存只针对一个浏览器用户有效,共享的缓存如代理缓存则无效。
  • no-cache — 不允许缓存。
  • no-store — 指示缓存在任何情况下不保存复制页面。
  • must-revalidate — 告诉缓存必须遵守任何有关有效期的信息指示,HTTP标准会让缓存在特定条件下继续使用过期的页面,通过规定这个头部,你能告诉缓存必须严格遵守你的规则。
  • proxy-revalidate — 类似must-revalidate, 但只针对代理缓存有效。

下面是一个案例:

Cache-Control: max-age=3600, must-revalidate

当Cache-Control和Expires都存在,Cache-Control优先。

 

验证器与验证

  通过使用验证器,当缓存与原始服务器通讯时,可以在当地已经存在拷贝的情况下,避免将页面完整下载。验证器非常重要,如果没有任何有效期信息如Expires或Cache-Control,也没有验证器,那么缓存就不会缓存任何内容。

  最常见的验证器是文本最后修改的时间,在Last-Modified 中设置,当缓存存储带有Last-Modified 头部的页面时,它能使用它询问服务器自从最后修改时间以后这个文档是否已经被改变过,这是使用If-Modified-Since请求实现的。

  HTTP 1.1引入了一个新的验证器,称为ETage,ETage是服务器产生的一种唯一标识,每次页面改变时都会产生,因为服务器控制ETag是如何产生的,当缓存向服务器发出If-None-Match请求时,能同时确认ETage是否匹配,如果匹配,缓存中文档和服务器的文档应该还是相同的。

  几乎所有的缓存都使用Last-Modified最后修改时间作为验证器,ETage验证也开始变得流行。

  大部分现代Web服务器都会自动产生ETag和Last-Modified头部作为静态内容的验证器,动态内容则需要自己在程序中动手设置。

 

Http缓存Last-Modified、ETag和Expires的Java终结解决之道

使用Varnish加速Web性能

缓存专题