HTTP学习笔记:缓存

Web缓存是可以自动保存常见文档副本的HTTP设备。当Web请求抵达缓存时,如果本地有“已缓存的”副本,就可以从本地存储设备而不是原始服务器中提取这个文档。使用缓存可以减少冗余的数据传输,环节网络瓶颈的问题,降低对原始服务器的要求以及降低距离时延。

本文解释了缓存怎样提供性能降低费用,如何去衡量其有效性以及将缓存置于何处可以发挥它的最大作用。此外还会解释HTTP如何保持已缓存副本的新鲜度,缓存如何与其他缓存和服务器通信等问题。

1. 使用缓存的意义

很多客户端访问一个流行的原始服务器页面时,服务器会多次传输同一份文档,一些相同的字节会在网络中一遍遍地传输。这些冗余的数据传输会消耗昂贵的网络带宽,降低传输速度。有了缓存,就可以保留第一条服务器响应的副本,后继请求就可以由缓存副本来应对了。缓存还可以缓解网络的瓶颈问题,因为本地网络提供的带宽远比远程服务器的带宽要宽,所以如果本地网络中有缓存,则可以显著提高性能。此外,缓存在应对“瞬间拥塞”即短时间内访问量激增的情况显得非常重要。

每台网络路由器都会增加因特网流量的时延,即使客户端和服务器之间没有太多的路由器,光速自身也会造成显著的时延。这种时延尤其体现在跨国之间的数据传输,而一份在附近机房的缓存可以将文件传输距离从数千公里缩短为数十米。

2. 缓存命中

缓存不能保存世界上每份文档的副本。当可以用已有的副本为某些到达缓存的请求提供服务,则称为缓存命中(cache hit),到达缓存的请求可能因为没有副本可用而被转发到原始服务器,称为缓存未命中(cache miss)。原始服务器的内容可能随时发生变化,缓存要不时对其进行检测看保存的副本是否仍是服务器上最新的副本,这种“新鲜度检测”称为HTTP再验证(revalidatioin)。

缓存进行再验证时,最常用的是将If-Modified-Since首部加到GET请求中。服务器收到该请求会发生以下三种情况:

  • 再验证命中:如果内容未被修改,服务器会以304 Not Modified响应,只要缓存知道副本仍然有效,就会再次将副本标识为暂时新鲜,并将副本提供给客户端。
  • 再验证未命中:服务器向客户端发送一条普通的带有完整内容的HTTP 200 OK响应
  • 对象被删除:如果服务器对象已被删除,服务器会回送404 Not Found,缓存也会将副本删除。

由缓存提供服务的请求所占的比例称为缓存命中率。对现在中等规模的Web缓存来说,40%的命中率是比较合理的。还有一种字节命中率表示缓存提供的字节在传输的所有字节所占的比例。文档命中率说明阻止了多少通往外部网络的Web事务,字节命中率说明阻止了多少字节传向因特网,提高字节命中率对节省带宽很有利。

HTTP没有为用户提供一种手段来区分响应是否来自缓存。可以使用Date首部来间接地判断,如果响应中的日期值比当前时间早,则可以认为这是一条缓存的响应。

3. 缓存的拓扑结构

缓存可以是单个用户专用的私有缓存,也可以是包含某个用户团体常用页面的公有缓存。私有缓存不需要很大的存储空间,可以将其做得很小很便宜,比如很多Web浏览器都有内建的私有缓存。公有缓存是特殊的共享代理服务器,它会接受来自多个用户的访问,可以更好地减少冗余流量。

在实际应用中,层次化的缓存是很有意义的。在较小的缓存中未命中的请求会被导向较大的父缓存,在靠近客户端的地方使用小型廉价缓存,更高层次中逐步采用更大,功能更强的缓存来装载多用户共享的文档。此外还有更加复杂的网状缓存,它们会根据URL在父缓存或原始服务器之间进行动态选择,也会决定选择何种路由对内容进行访问、管理和传送。

4. 缓存的处理步骤

Web缓存的基本处理步骤包括7个步骤,如下图所示。

Web缓存的基本处理步骤

  1. 接收:缓存检测到一条网络连接上的活动,读取输入数据。高性能的缓存会同时从多条输入连接上读取数据,在整条报文抵达之前开始处理事务。
  2. 解析:将请求报文解析为片段,将首部各个部分放入易于操作的数据结构中。
  3. 查找:查找本地副本(可能位于内存、本地磁盘甚至另一台计算机中)。专业级的缓存会使用快速算法来确定本地缓存中是否有某个对象。
  4. 新鲜度检测:缓存会将服务器文档的副本保留一段时间,在“新鲜度限值”的时间之内都认为文档是“新鲜的”。超过限值的要和服务器进行再验证,以查看文档是否发生变化。新鲜度检测是一个非常复杂的问题,本文剩余部分将讨论这个问题。
  5. 创建响应:缓存会对服务器响应的基础首部进行修改和扩充,例如将低版本的HTTP转换成HTTP/1.1响应。缓存还会向首部插入新鲜度信息(Cache-Control、Age、Expires首部等)。
  6. 发送:高性能缓存会尽力高效发送数据,通常可以避免在本地缓存和网络I/O缓冲区之间进行文档内容的复制。
  7. 日志:保存于缓存使用有关的一些统计数据,如更新缓存命中和未命中数目数据等。

5. 保持副本的新鲜

不是所有的缓存副本都与服务器上的文档一致,因为服务器上的文档可能随时都会变化,缓存数据必须要与服务器保持一致。HTTP有一些简单的机制可以在不要求服务器记住有哪些缓存拥有其文档副本的情况下,保持已缓存数据与服务器之间充分一致。通过特殊的HTTP Cache-Control:max-age(HTTP/1.1首部,定义了文档的最大试用期)和Exipires(HTTP/1.0+首部,指定一个绝对的过期日期)首部,原始服务器向每个文档附加一个“过期日期”,说明了再多长时间内可以将这些内容视为新鲜的。

过了过期日期之后只是意味着缓存要和服务器进行再验证。若再验证显示内容发生了变化,缓存会获取一份新的副本并存储在旧文档的位置上;若没有发生变化,缓存只需要获取新的过期日期并更新就可以了。

正如第2节所说的,缓存可以向原始服务器发送一个“条件GET”来进行再验证。最常见的缓存再验证首部是If-Modified-Since:Date(IMS请求)。只有自某个日期之后资源发生了变化,GET才会成功执行回送新的文档。该首部可以与Last-Modified服务器响应首部配合工作。原始服务器会将最后的修改日期附加到所提供的文档上去。当缓存要再验证时,就会发送携带最后修改日期的IMS请求。

有一些情况仅使用最后修改日期进行再验证是不够的,例如:有的文档可能会被周期性地重写,尽管内容没变,但修改日期会变;有的文档可能被修改了,但所做修改没必要让世界范围内的缓存都重新获取数据;有的服务器无法准确判定其页面的最后修改日期;有的服务器提供的文档会在秒级以内发生变化,以一秒为粒度的修改日期可能不够用。为了解决这些问题,HTTP允许用户对实体标签ETag进行比较。当发布者对文档进行修改时,可以修改文档的实体标签来说明这个新的版本,缓存可以用If-None-Match条件首部来GET文档的新副本。

如果服务器回送一个ETag,客户端就必须使用实体标签验证器;如果服务器只回送一个Last-Modified值,客户端就可以使用If-Modified-Since验证。如果两者都提供,客户端就应该使用两种验证方案。当服务器同时收到两种方案的请求,只有当两个条件都满足时,才能返回304 Not Modified响应。

6. 控制缓存

6.1 响应首部

服务器可以通过HTTP定义的几种方式来指定在文档过期之前可以将其缓存多长时间。按照优先级递减的顺序,以下给出了这些控制缓存的首部。

  • Cache-Control:no-store:禁止缓存对响应进行复制。缓存会向客户端转发一条no-store响应,然后删除对象。
  • Cache-Control:no-cache:实际上可以存储在本地缓存区。只是在与原始服务器进行新鲜度再验证之前,缓存不能将其提供给客户端使用。
  • Cache-Control:must-revalidate:缓存是可以提供一些过期的对象的(保存在缓存上的副本有多个版本)。原始服务器可以用这个首部告诉缓存,在事先没有再验证的情况下,不能提供这个对象的过期副本。缓存仍然可以随意提供新鲜的副本。
  • Cache-Control:max-age:表示从服务器将文档传来之时起,可以认为此文档处于新鲜状态的秒数。若为0,每次访问都不缓存。
  • Expires:指定实际(绝对)的过期日期。但由于很多服务器的时钟不同步或不正确,最好还是使用max-age剩余秒数来表示过期时间。

如果响应中没有说明过期时间的首部,缓存可以通过一些算法来计算出一个试探性最大使用期。比如LM-Factor算法利用文档的最后修改日期来计算过期时间。

6.2 请求首部

Web浏览器也可以用Cache-Control请求首部来强化或放松对过期时间的限制,下面列举这些首部。

  • Cache-Control:max-stale=s:缓存可以随意提供过期的文件。如果指定了参数s,在这段时间内文档就不能过期。这条指令放松了缓存规则。
  • Cache-Control:min-fresh=s:至少在未来s秒内文档要保持新鲜。这条指令使缓存规则更加严格。
  • Cache-Control:no-cache:除非资源进行了再验证,否则这个客户端不会接受已缓存的资源。
  • Cache-Control:no-store:缓存应该尽快从存储器中删除文档的所有痕迹。
  • Cache-Control:only-if-cached:只有当缓存中有副本存在时,客户端才会获取一份副本。

6.3 控制Apache的HTTP首部

Apache Web服务器提供了几种设置HTTP缓存控制首部的机制,其中很多机制在默认情况下是没有启动的(需要手动启动或自行安装扩展模块)。

  • mod_headers:该模块可以对单独的首部进行配置,还可以将这些配置与Apache的常用表达式及过滤器结合在一起使用
  • mod_expires:可以自动生成带有正确过期日期的Expires首部。
  • mod_cern_meta:可以将一个包含HTTP首部的文件与特定的对象联系起来。

6.4 通过HTML的http-equiv控制HTML缓存

为了让开发者在无需与服务器配置文件进行交互的情况下,能够更容易地为所提供的HTML文档分配HTTP首部信息,可以使用一个meta标签来控制HTML缓存。例如下面的代码将HTML文档标记为非缓存的。

<head>
    <meta http-equiv="Cache-Control" content="no-cache">
</head>

但是,通过meta标签并不是控制文档缓存特性的好方法,因为这个可选特性会增加服务器的额外负载,很多服务器或代理也不支持此特性。由经过正确配置的服务器发送的HTTP首部来交流对文档的缓存控制请求是唯一可靠的方法。

注:本文略过了原书“7.11详细算法”缓存使用期和新鲜度计算算法的内容以及“7.12缓存和广告”的内容。

参考文献:人民邮电出版社《HTTP权威指南》第7章

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器