Web 缓存是能够自动保存常见文档副本的HTTP设备。当Web请求抵达缓存时,若是本地有“已缓存的”副本,就能够从本地存储设备而不是原始服务器中提取这个文档。
算法
使用缓存有下列优势:
浏览器
有不少客户端访问一个流行的原始服务器页面时,服务器会屡次传输同一份文档,每次传送给一个客户端。一些相同的字节会在网络中一遍遍地传输。这些冗余的数据传输会耗尽昂贵的网络带宽,下降传输速度,加剧 Web 服务器的负载。有了缓存,就能够保留第一条服务器响应的副本,后继请求就能够由缓存的副原本应对了,这样能够减小那些流入/流出原始服务器的、被浪费掉了的重复流量。
缓存
带宽瓶颈:缓存还能够缓解网络的瓶颈问题。不少网络为本地网络客户端提供的带宽比为远程服务器提供的带宽要宽。客户端会以路径上最慢的网速访问服务器。若是客户端从一个快速局域网的缓存中获得了一份副本,那么缓存就能够提升性能——尤为是要传输比较大的文件时。
安全
瞬间拥塞:缓存在破坏瞬间拥塞(Flash Crowds)时显得很是重要。突发事件使不少人几乎同时去访问一个Web文档时,就会出现瞬间拥塞。由此形成的过多流量峰值可能会使网络和Web服务器产生灾难性的崩溃。
距离时延:即便带宽不是问题,距离也可能成为问题。每台网络路由器都会增长因特网流量的时延。即便客户端和服务器之间没有太多的路由器,光速自身也会形成显著的时延。
服务器
缓存没法保存世界上每份文档的副本。能够用已有的副本为某些到达缓存的请求提供服务,这被称为缓存命中(cache hit)。其余一些到达缓存的请求可能会因为没有副本可用,而被转发给原始服务器,这被称为缓存未命中(cache miss)。
网络
原始服务器的内容可能会发生变化,缓存要不时对其进行检测,看看它们保存的副本是否还是服务器上最新的副本。这些"新鲜度检测"被称为HTTP再验证(revalidation)。为了有效地进行再验证,HTTP定义了一些特殊的请求,不用从服务器上获取整个对象,就能够快速检测出内容是不是最新的。缓存能够在任意时刻,以任意的频率对副本进行再验证。但因为缓存中一般会包含数百万的文档,并且网络带宽是很珍贵的,因此大部分缓存只有在客户端发起请求,而且副本旧得足以须要检测的时候,才会对副本进行再验证。缓存对缓存的副本进行再验证时,会向原始服务器发送一个小的再验证请求。若是内容没有变化,服务器会以一个小的"304 Not Modified"进行响应。只要缓存知道副本仍然有效,就会再次将副本标识为暂时新鲜的,并将副本提供给客户端这被称做再验证命中(revalidate hit)或缓慢命中(slow hit)。这种方式确实要与原始服务器进行核对,因此会比单纯的缓存命中要慢,但它没有从服务器中获取对象数据,因此要比缓存未命中快一些。HTTP为咱们提供了几个用来对已缓存对象进行再验证的工具,但最经常使用的是"If-Modified-Since"首部。将这个首部添加到GET请求中去,就能够告诉服务器,只有在缓存了对象的副本以后,又对其进行了修改的状况下,才发送此对象。
数据结构
这里列出了在3种状况下(服务器内容未被修改,服务器内容已被修改,或者服务器上的对象被删除了)服务器收到"GET If-Modified-Since"请求时会发生的状况:
工具
由缓存提供服务的请求所占的比例被称为缓存命中率(cache hit rate,或称为缓存命中比例),有时也被称为文档命中率(document hit rate)。命中率在0到1之间,但一般是用百分数来描述的。缓存的管理者但愿缓存命中率接近100%。而实际获得的命中率则与缓存的大小、缓存用户兴趣点的类似性、缓存数据的变化或个性化频率,以及如何配置缓存有关,命中率很难预测。缓存的好处是,即便是中等规模的缓存,其所包含的常见文档也足以显著地提升性能、减小流量了。缓存会努力确保将有用的内容保存在缓存中。
性能
因为文档并不全是同一尺寸的,因此文档命中率并不能说明一切。有些大型对象被访问的次数可能较少,但因为尺寸的缘由,对整个数据流量的贡献却更大。所以,有些人更愿意使用字节命中率(byte hit rate)做为度量值(尤为那些按流量字节付费的人!)。字节命中率表示的是缓存提供的字节在传输的全部字节中所占的比例。经过这种度量方式,能够得知节省流量的程度。100%的字节命中率说明每一个字节都来自缓存,没有流量流到因特网上去。文档命中率和字节命中率对缓存性能的评估都是颇有用的。文档命中率说明阻止了多少通往外部网络的Web事务。事务有一个一般都很大的固定时间成分(好比,创建一条到服务器的TCP链接),提升文档命中率对下降总体延迟(时延)颇有好处。字节命中率说明阻止了多少字节传向因特网。提升字节命中率对节省带宽颇有利。
设计
不幸的是,HTTP没有为用户提供一种手段来区分响应是缓存命中的,仍是访问原始服务器获得的。在这两种状况下,响应码都是"200 OK",说明响应有主体部分。有些商业代理缓存会在Via首部附加一些额外信息,以描述缓存中发生的状况。客户端有一种方法能够判断响应是否来自缓存,就是使用Date首部。将响应中Date首部的值与当前时间进行比较,若是响应中的日期值比较早,客户端一般就能够认为这是一条缓存的响应。客户端也能够经过Age首部来检测缓存的响应,经过这个首部能够分辨出这条响应的使用期。
缓存能够是单个用户专用的,也能够是数千名用户共享的。专用缓存被称为私有缓存(private cache)。私有缓存是我的的缓存,包含了单个用户最经常使用的页面。共享的缓存被称为公有缓存(public cache)。公有缓存中包含了某个用户团体的经常使用页面。
私有缓存不须要很大的动力或存储空间,这样就能够将其作得很小,很便宜。Web浏览器中有内建的私有缓存——大多数浏览器都会将经常使用文档缓存在你我的电脑的磁盘和内存中,而且容许用户去配置缓存的大小和各类设置。
公有缓存是特殊的共享代理服务器,被称为缓存代理服务器(caching proxy server),或者更常见地被称为代理缓存(proxy cache)。代理缓存会从本地缓存中提供文档,或者表明用户与服务器进行联系。公有缓存会接受来自多个用户的访问,因此经过它能够更好地减小冗余流量。
在实际中,实现层次化(hierarchy)的缓存是颇有意义的,在这种结构中,在较小缓存中未命中的请求会被导向较大的父缓存(parent cache),由它来为剩下的那些"提炼过的"流量提供服务。
有些网络结构会构建复杂的网状缓存(cache mesh),而不是简单的缓存层次结构。网状缓存中的代理缓存之间会以更加复杂的方式进行对话,作出动态的缓存通讯决策,决定与哪一个父缓存进行对话,或者决定完全绕开缓存,直接链接原始服务器。这种代理缓存会决定选择何种路由对内容进行访问、管理和传送,所以可将其称为内容路由器(content router)。缓存之间这些更为复杂的关系容许不一样的组织互为对等(peer)实体,将它们的缓存链接起来以实现双赢。提供可选的对等支持的缓存被称为兄弟缓存(sibling cache)。HTTP并不支持兄弟缓存,因此人们经过一些协议对HTTP进行了扩展,好比因特网缓存协议(Internet Cache Protocol,ICP)和超文本缓存协议(HyperText Caching Protocol,HTCP)。
网状缓存中为内容路由设计的缓存(除了其余任务以外)要完成下列全部功能:
现代的商业化代理缓存至关地复杂。这些缓存构建得很是高效,能够支持HTTP和其余一些技术的各类高级特性。但除了一些微妙的细节以外,Web缓存的基本工做原理大多很简单。对一条"HTTP GET"报文的基本缓存处理过程包括7个步骤:
在第一步中,缓存检测到一条网络链接上的活动,读取输入数据。高性能的缓存会同时从多条输入链接上读取数据,在整条报文抵达以前开始对事务进行处理。
接下来,缓存将请求报文解析为片段,将首部的各个部分放入易于操做的数据结构中。这样,缓存软件就更容易处理首部字段并修改它们了。
在第三步中,缓存获取了URL,查找本地副本。本地副本可能存储在内存、本地磁盘,甚至附近的另外一台计算机中。专业级的缓存会使用快速算法来肯定本地缓存中是否有某个对象。若是本地没有这个文档,它能够根据情形和配置,到原始服务器或父代理中去取,或者返回一条错误信息。已缓存对象中包含了服务器响应主体和原始服务器响应首部,这样就会在缓存命中时返回正确的服务器首部。已缓存对象中还包含了一些元数据(metadata),用来记录对象在缓存中停留了多长时间,以及它被用过多少次等。
HTTP经过缓存将服务器文档的副本保留一段时间。在这段时间里,都认为文档是"新鲜的",缓存能够在不联系服务器的状况下,直接提供该文档。但一旦已缓存副本停留的时间太长,超过了文档的新鲜度限值(freshness limit),就认为对象"过期"了,在提供该文档以前,缓存要再次与服务器进行确认,以查看文档是否发生了变化。客户端发送给缓存的全部请求首部自身均可以强制缓存进行再验证,或者彻底避免验证,这使得事情变得更加复杂了。HTTP有一组很是复杂的新鲜度检测规则,缓存产品支持的大量配置选项,以及与非HTTP新鲜度标准进行互通的须要则使问题变得更加严重了。本章其他的大部分篇幅都用于解释新鲜度的计算问题。
咱们但愿缓存的响应看起来就像来自原始服务器的同样,缓存将已缓存的服务器响应首部做为响应首部的起点。而后缓存对这些基础首部进行了修改和扩充。缓存负责对这些首部进行改造,以便与客户端的要求相匹配。好比,服务器返回的多是一条"HTTP/1.0"响应(甚至是"HTTP/0.9"响应),而客户端期待的是一条"HTTP/1.1"响应,在这种状况下,缓存必须对首部进行相应的转换。缓存还会向其中 插入新鲜度信息(Cache-Control、Age以及Expires首部),并且一般会包含一个Via首部来讲明请求是由一个代理缓存提供的。注意,缓存不该该调整Date首部。Date首部表示的是原始服务器最初产生这个对象的日期。
一旦响应首部准备好了,缓存就将响应回送给客户端。和全部代理服务器同样,代理缓存要管理与客户端之间的链接。高性能的缓存会尽力高效地发送数据,一般能够避免在本地缓存和网络I/O缓冲区之间进行文档内容的复制。
大多数缓存都会保存日志文件以及与缓存的使用有关的一些统计数据。每一个缓存事务结束以后,缓存都会更新缓存命中和未命中数目的统计数据(以及其余相关的度量值),并将条目插入一个用来显示请求类型、URL和所发生事件的日志文件。
可能不是全部的已缓存副本都与服务器上的文档一致。毕竟,这些文档会随着时间发生变化。报告可能每月都会变化。在线报纸天天都会发生变化。财经数据可能每过几秒钟就会发生变化。若是缓存提供的老是老的数据,就会变得毫无用处。已缓存数据要与服务器数据保持一致。HTTP有一些简单的机制能够在不要求服务器记住有哪些缓存拥有其文档副本的状况下,保持已缓存数据与服务器数据之间充分一致。HTTP将这些简单的机制称为文档过时(document expiration)和服务器再验证(server revalidation)。
经过特殊的HTTP Cache-Control首部和Expires首部,HTTP让原始服务器向每一个文档附加了一个"过时日期"。这些首部说明了在多长时间内能够将这些内容视为新鲜的。在缓存文档过时以前,缓存能够以任意频率使用这些副本,而无需与服务器联系——固然,除非客户端请求中包含有阻止提供已缓存或未验证资源的首部。但一旦已缓存文档过时,缓存就必须与服务器进行核对,询问文档是否被修改过,若是被修改过,就要获取一份新鲜(带有新的过时日期)的副本。
服务器用"HTTP/1.0+"的Expires首部或"HTTP/1.1"的"Cache-Control: max-age"响应首部来指定过时日期,同时还会带有响应主体。Expires首部和"Cache-Control: max-age"首部所作的事情本质上是同样的,但因为Cache-Control首部使用的是相对时间而不是绝对日期,因此咱们更倾向于使用比较新的Cache-Control首部。绝对日期依赖于计算机时钟的正确设置。
过时响应首部
首部 | 描述 |
---|---|
Cache-Control: max-age=484200 | max-age值定义了文档的最大使用期——从第一次生成文档到文档再也不新鲜、没法使用为止,最大的合法生存时间(以秒为单位) |
Expires: Fri, 05 Jul 2002, 05:00:00 GMT | 指定一个绝对的过时日期。若是过时日期已通过了,就说明文档再也不新鲜了 |
仅仅是已缓存文档过时了并不意味着它和原始服务器上目前处于活跃状态的文档有实际的区别;这只是意味着到了要进行核对的时间了。这种状况被称为"服务器再验证",说明缓存须要询问原始服务器文档是否发生了变化。缓存并不必定要为每条请求验证文档的有效性——只有在文档过时时它才须要与服务器进行再验证。这样不会提供陈旧的内容,还能够节省服务器的流量,并拥有更好的用户响应时间。
HTTP协议要求行为正确的缓存返回下列内容之一:
HTTP的条件方法能够高效地实现再验证。HTTP容许缓存向原始服务器发送一个"条件GET",请求服务器只有在文档与缓存中现有的副本不一样时,才回送对象主体。经过这种方式,将新鲜度检测和对象获取结合成了单个条件GET。向GET请求报文中添加一些特殊的条件首部,就能够发起条件GET。只有条件为真时,Web服务器才会返回对象。HTTP定义了5个条件请求首部。对缓存再验证来讲最有用的2个首部是If-Modified-Since和If-None-Match。全部的条件首部都之前缀"If-"开头。
缓存再验证中使用的条件请求首部:
首部 | 描述 |
---|---|
If-Modified-Since:<date> | 若是从指定日期以后文档被修改过了,就执行请求的方法。能够与Last-Modified服务器响应首部配合使用,只有在内容被修改后与已缓存版本有所不一样的时候才去获取内容 |
If-None-Match:<tags> | 服务器能够为文档提供特殊的标签,而不是将其与最近修改日期相匹配,这些标签就像序列号同样。若是已缓存标签与服务器文档中的标签有所不一样,If-None-Match首部就会执行所请求的方法 |
最多见的缓存再验证首部是If-Modified-Since。If-Modified-Since再验证请求一般被称为IMS请求。只有自某个日期以后资源发生了变化的时候,IMS请求才会指示服务器执行请求:
If-Modified-Since首部能够与Last-Modified服务器响应首部配合工做。原始服务器会将最后的修改日期附加到所提供的文档上去。当缓存要对已缓存文档进行再验证时,就会包含一个If-Modified-Since首部,其中携带有最后修改已缓存副本的日期。若是在此期间内容被修改了,最后的修改日期就会有所不一样,原始服务器就会回送新的文档。不然,服务器会注意到缓存的最后修改日期与服务器文档当前的最后修改日期相符,会返回一个"304 Not Modified"响应。注意,有些Web服务器并无将If-Modified-Since做为真正的日期来进行比对。相反,它们在IMS日期和最后修改日期之间进行了字符串匹配。这样获得的语义就是"若是最后的修改不是在这个肯定的日期进行的",而不是"若是在这个日期以后没有被修改过"。将最后修改日期做为某种序列号使用时,这种替代语义可以很好地识别出缓存是否过时,但这会妨碍客户端将If-Modified-Since首部用于真正基于时间的一些目的。
有些状况下仅使用最后修改日期进行再验证是不够的。
为了解决这些问题,HTTP容许用户对被称为实体标签(ETag)的"版本标识符"进行比较。实体标签是附加到文档上的任意标签(引用字符串)。它们可能包含了文档的序列号或版本名,或者是文档内容的校验和及其余指纹信息。当发布者对文档进行修改时,能够修改文档的实体标签来讲明这个新的版本。这样,若是实体标签被修改了,缓存就能够用If-None-Match条件首部来GET文档的新副本了。
缓存能够用实体标签来判断,与服务器相比,已缓存版本是否是最新的(与使用最近修改日期的方式很像)。从这个角度来看,实体标签和最近修改日期都是缓存验证器(cache validator)。有时,服务器但愿在对文档进行一些非实质性或不重要的修改时,不要使全部的已缓存副本都失效。"HTTP/1.1"支持"弱验证器",若是只对内容进行了少许修改,就容许服务器声明那是"足够好"的等价体。只要内容发生了变化,强验证器就会变化。弱验证器容许对一些内容进行修改,但内容的主要含义发生变化时,一般它仍是会变化的。有些操做不能用弱验证器来实现(好比有条件地获取部份内容),因此,服务器会用前缀"W/"来标识弱验证器。无论相关的实体值以何种方式发生了变化,强实体标签都要发生变化。而相关实体在语义上发生了比较重要的变化时,弱实体标签也应该发生变化。注意,原始服务器必定不能为两个不一样的实体重用一个特定的强实体标签值,或者为两个语义不一样的实体重用一个特定的弱实体标签值。缓存条目可能会留存任意长的时间,与其过时时间无关,有人可能但愿当缓存验证条目时,绝对不会再次使用在过去某一时刻得到的验证器,这种愿望可能不太现实。
若是服务器回送了一个实体标签,"HTTP/1.1"客户端就必须使用实体标签验证器。若是服务器只回送了一个Last-Modified值,客户端就可使用If-Modified-Since验证。若是实体标签和最后修改日期都提供了,客户端就应该使用这两种再验证方案,这样"HTTP/1.0"和"HTTP/1.1"缓存就均可以正确响应了。除非"HTTP/1.1"原始服务器没法生成实体标签验证器,不然就应该发送一个出去,若是使用弱实体标签有优点的话,发送的可能就是个弱实体标签,而不是强实体标签。并且,最好同时发送一个最近修改值。若是"HTTP/1.1"缓存或服务器收到的请求既带有If-Modified-Since,又带有实体标签条件首部,那么只有这两个条件都知足时,才能返回"304 Not Modified"响应。
服务器能够经过 HTTP 定义的几种方式来指定在文档过时以前能够将其缓存多长时间。按照优先级递减的顺序,服务器能够:
"HTTP/1.1"提供了几种限制对象缓存,或限制提供已缓存对象的方式,以维持对象的新鲜度。no-store首部和no-cache首部能够防止缓存提供未经证明的已缓存对象:
"Cache-Control: max-age"首部表示的是从服务器将文档传来之时起,能够认为此文档处于新鲜状态的秒数(Cache-Control: max-age=3600)。还有一个s-maxage首部(注意maxage的中间没有连字符),其行为与max-age相似,但仅适用于共享(公有)缓存(Cache-Control: s-maxage=3600)。服务器能够请求缓存不要缓存文档,或者将最大使用期设置为零,从而在每次访问的时候都进行刷新(Cache-Control: max-age=0)。
不推荐使用Expires首部,它指定的是实际的过时日期而不是秒数。HTTP设计者后来认为,因为不少服务器的时钟都不一样步,或者不正确,因此最好仍是用剩余秒数,而不是绝对时间来表示过时时间。能够经过计算过时值和日期值之间的秒数差来计算相似的新鲜生存期(Expires: Fri, 05 Jul 2002, 05:00:00 GMT)。有些服务器还会回送一个"Expires:0"响应首部,试图将文档置于永远过时的状态,但这种语法是非法的,可能给某些软件带来问题。应该试着支持这种结构的输入,但不该该产生这种结构的输出。
能够配置缓存,使其提供一些陈旧(过时)的对象,以提升性能。若是原始服务器但愿缓存严格遵照过时信息,能够在原始响应中附加一个"Cache-Control: must-revalidate"首部。"Cache-Control: must-revalidate"响应首部告诉缓存,在事先没有跟原始服务器进行再验证的状况下,不能提供这个对象的陈旧副本。缓存仍然能够随意提供新鲜的副本。若是在缓存进行must-revalidate新鲜度检查时,原始服务器不可用,缓存就必须返回一条"504 Gateway Timeout"错误。
若是响应中没有"Cache-Control: max-age"首部,也没有Expires首部,缓存能够计算出一个试探性最大使用期。可使用任意算法,但若是获得的最大使用期大于24小时,就应该向响应首部添加一个Heuristic Expiration Warning(试探性过时警告,警告13)首部。不多有浏览器会为用户提供这种警告信息。LM-Factor算法是一种很经常使用的试探性过时算法,若是文档中包含了最后修改日期,就可使用这种算法。LM-Factor算法将最后修改日期做为依据,来估计文档有多么易变。实际的LM-Factor算法会计算缓存与服务器对话的时间跟服务器声明文档最后被修改的时间之间的差值,取这个间隔时间的一部分,将其做为缓存中的新鲜度持续时间。一般人们会为试探性新鲜周期设置上限,这样它们就不会变得太大了。尽管比较保守的站点会将这个值设置为一天,但一般站点会将其设置为一周。若是最后修改日期也没有的话,缓存就没什么信息可利用了。缓存一般会为没有任何新鲜周期线索的文档分配一个默认的新鲜周期(一般是一个小时或一天)。有时,比较保守的缓存会将这种试探性新鲜生存期设置为0,强制缓存在每次将其提供给客户端以前,都去验证一下这些数据仍然是新鲜的。与试探性新鲜计算有关的最后一点是——它们可能比你想象的要常见得多。不少原始服务器仍然不会产生Expires和max-age首部。选择缓存过时的默认时间时要特别当心!
LM-Factor算法的逻辑:
Web 浏览器都有刷新(Refresh)或 重载(Reload)按钮,能够强制对浏览器或代理缓存中可能过时的内容进行刷新。刷新按钮会发布一个附加了Cache-Control请求首部的GET请求,这个请求会强制进行再验证,或者无条件地从服务器获取文档。刷新的确切行为取决于特定的浏览器、文档以及拦截缓存的配置。客户端能够用Cache-Control请求首部来强化或放松对过时时间的限制。有些应用程序对文档的新鲜度要求很高(好比人工刷新按钮),对这些应用程序来讲,客户端能够用Cache-Control首部使过时时间更严格。另外一方面,做为提升性能、可靠性或开支的一种折衷方式,客户端可能会放松新鲜度要求。
Cache-Control请求指令:
指令 | 目的 |
---|---|
Cache-Control: max-stale = (s) | 缓存能够随意提供过时的文件。若是指定了参数(s),在这段时间内,文档就不能过时。这条指令放松了缓存的规则 |
Cache-Control: min-fresh=(s) | 至少在将来(s)秒内文档要保持新鲜。这就使缓存规则更加严格了 |
Cache-Control: max-age = (s) | 缓存没法返回缓存时间长于(s)秒的文档。这条指令会使缓存规则更加严格,除非同时还发送max-stale指令,在这种状况下,使用期可能会超过其过时时间 |
Cache-Control: no-cache | 除非资源进行了再验证,不然这个客户端不会接受已缓存的资源 |
Cache-Control: no-store | 缓存应该尽快从存储器中删除文档的全部痕迹,由于其中可能会包含敏感信息 |
Cache-Control: only-if-cached | 只有当缓存中有副本存在时,客户端才会获取一份副本 |
注意:文档过时系统并非一个完美的系统。若是发布者不当心分配了一个好久以后的过时日期,在文档过时以前,她要对文档作的任何修改都不必定能显示在全部缓存中。所以,不少发布者都不会使用很长的过时日期。并且,不少发布者甚至都不使用过时日期,这样缓存就很难肯定文档会在多长时间内保持新鲜了。