HTTP中的缓存
扫描二维码
随时随地手机看文章
HTTP典型应用于能通过采用缓存技术而提高性能的分布式信息系统。HTTP/1.1协议包括的许多使缓存尽可能的工作的元素。因为这些元素与协议的其他方面有着千丝万缕的联系,而且他们相互作用、影响,因此有必要单独的来介绍基本的缓存设计。
如果缓存不能改善性能,他将一无用处。HTTP/1.1中缓存的目的是为了在很多情况下减少发送请求,同时在许多情况下可以不需要发送完整响应。前者减少了网络回路(译注:一个请求会返回一个响应,请求响应这个过程就是一个回路)的数量;我们利用一个“过期(expiration)”机制来为此目的(见13.2节)。后者减少了网络应用的带宽;我们用“验证(validation)”机制来为此目的。
对行为,可行性,和关闭的操作的要求放松了语义透明性的目的。HTTP/1.1协议允许服务器,缓存,和客户端能显示地降低透明性当在必要的时候。然而,因为不透明的操作能混淆非专业的用户,同时可能和某个服务器应用程序不兼容(例如订购商品),因此此协议透明性在下面情况下才能被放松要求:
-- 只有在一个显示的协议层的请求时,透明性才能被客户端或源服务器放松
-- 当出现一个对终端用户的显示的警告时,透明性才能被缓存或被客户端放松
因此,HTTP/1.1协议提供这些重要的元素:
1.提供完整语义透明的协议特征,当这些特征被通信的所有方需要时
2. 允许源服务器或用户代理显示的请求和控制不透明操作的协议特征
3. 允许缓存给这样的响应绑定警告信息的协议特征,这些响应不能保留请求的语义透明的近似
一个基本的原则是客户端必须能够发现语义透明性的潜在的放松。
注意:服务器,缓存,或者客户端的实现者可能会面对设计上的判断,而这些判断没有显示地在此规范里讨论。如果一个判断可能会影响语义透明性,那么实现者应该能维持语义透明性,除非一个仔细的完整的分析能说明打破透明性的好处。
13.1.1缓存正确性(Cache Correctness)
一个正确的缓存必须能用最新的响应来响应请求,此响应当在下面的条件满足时才适合此请求(见13.2.5,13.2.6,和13.12)
此缓存响应已被检测与假设通过源服务器重验证后源服务器返回的响应相等价。
此缓存响应是足够保鲜(fresh)的(见13.2节)。缺省的情况下,这意味着此响应必须满足客户端,源服务器,和缓存的最严格的保鲜要求(见14.9节);如果源服务器指定了保鲜寿命,这说明它是源服务器自己的保鲜要求。
由于客户端和源服务器的最严格的保鲜要求,如果一个缓存的响应不是足够保鲜的,那么在仔细考虑的情况下,缓存可能仍然返回此缓存的响应通过合适的Warning头域(见13.1.5和14.46节),除非此响应被阻止(例如:通过”no-store” cache-directive ,或者通过一个”no-cache”cache-request-directive;见14.9节)。
此缓存响应是一个304(没有被改变),305(代理重定向),或 错误(4xx或者5xx)响应消息。
如果缓存不能同源服务器通信,那么一个正确的缓存应该用它缓存的响应去响应请求,如果此缓存的响应能正确的服务于请求;如果不能服务器于此请求,那么缓存必须能返回一个错误或警告指示存在通信失败。
如果缓存从服务器端接收到一个响应(或者是一个完整的响应,或者一个304(没有被改变)的响应),此响应应该是正常情况下要发送到请求的客户端的,并且此响应并不是新鲜的,那么此缓存应该把此响应转发给请求客户端不需要添加一个新的Warning头域(但是没有移去已经存在的Warning头域)。缓存并不是简单的因为传输中响应变得陈旧而尝试去重验证响应;这可能会导致一个无限的循环。用户代理接收一个陈旧的没有Warning头域的响应应该提示用户一个警告信息。
13.1.2警告信息(Warnings)
无论何时,缓存返回一个响应,此响应既不是第一手的(first-hand)也不是足够保鲜(在13.1.1节的条件2),那么缓存必须利用一个Warning常用头域来警告产生的效果。Warning头域和当前定义的警告在14.46节里描述。这些警告允许客户端去采取合适的动作。
警告可能被用于其他的目的,和缓存相关的目的和其他的目的。警告和错误状态码的区别在于是否是真正的失败。
警告被赋予三位数字warn-codes。第一个数字指明:警告是否必须或不必须从缓存项里删除,在一个成功的重验证之后。
1xx 警告描述了响应的保鲜或重验证状态信息,并且这些信息应该在一个成功的重验证之后删除。1xx警告码可能是由缓存产生的,当缓存验证一个缓存项时。此警告码不能被客户端产生。
2xx 警告描述了实体主体或实体头域的某些方面的信息,这些信息不能被重验证修改,并且这些信息不能在一个成功重验证之后被删除。
见14.46节关于警告码的定义。
HTTP/1.0缓存将缓存所有响应中的警告,并且不会删除第一类警告。穿过HTTP/1.0缓存响应中的警告会携带一个额外的warning-date域,这是为了防止将来的HTTP/1.1接收端信任一个错误的缓存警告。
警告同样携带一个警告文本。此文本可能是任何合适的自然语义(可能基于客户端请求的Accept头域),同时包含一个可选择的关于何种字符集被使用的声明。
多个警告可能会绑定一个响应(或者被源服务器或者被一个缓存发送的),这包括多个警告可以共用一个警告码。例如,服务器可能会以英语和法语提供相同的警告。
当多个警告绑定一个响应时,有时候不可能把所有的警告都展示给客户。HTTP版本不能指定一个严格的优先值去决定警告的优先和顺序显示,但是可以探索一些方法。
13.1.3缓存控制机制 (Cache-control Mechanism)
HTTP/1.1基本的缓存机制(服务器指定过期时间和验证器)对缓存是隐含的指令。某些情况下,服务器或客户端可能需要给HTTP缓存提供显示的指令。我们利用Cache-Control头域为此目的。
Cache-Control头域允许客户端或服务器在请求或响应里传输多个指令。这些指令常常覆盖缺省的缓存算法。作为一个常用规则,如果头域值中存在一个明显的冲突,那么最具严格解释的头域值会被应用(也就是说,能保留语义透明性的值)。然而,一些情况下,cache-control指令被显示地指定用来削弱语义透明性的相似性(例如,”max-stale” 或者 “public”)。
Cache-control指令在14.9节被描述。
13.1.4显示的用户代理警告(Explicit User Agent Warnings)
很多用户代理允许用户覆盖基本缓存机制。例如,用户代理可能允许用户指定缓存实体(即使实体明显是陈旧的)从来不要被验证。或者,用户代理可能习惯于给任何请求加上“Cache-Control:max-stale=3600”。用户代理不能缺省的执行不透明行为,或者不能缺省的执行导致非正常的无效率的缓存行为,但是可能被显示地设置去这样做通过一个显示的用户动作。
如果用户已经覆盖基本的缓存机制,那么用户代理应该给用户指示何时显示不能满足服务器透明要求的信息(特别地,如果显示的实体被认为是陈旧的)。因为此协议通常允许用户代理去判定响应是否是陈旧或不是陈旧的,所以此指示只需要当实际发生时显示。此指示不必是对话框;它应该是一个图标(例如,一个正在腐烂的鱼)或者一些其他的指示器。
如果用户以一种方式已经覆盖了缓存机制,这种方式可能不正常地减少缓存效率,那么用户代理应该继续指示用户的状态(例如,通过一个图片显示)以便用户不能不注意地消费过度的资源或者忍受过度的延迟。
13.1.5规则和警告的例外情况
在某些情况下,缓存的操作者应该设置缓存返回陈旧的响应,即使此响应没有被客户端请求。这个判定本来不应该轻易决定,但是由于某些原因可能会这样做,特别是当缓存和源服务器连接不好时。无能何时当缓存会返回一个陈旧的响应时,缓存必须给此响应做个标记(利用Warning头域),因为这样能使客户端提示用户响应是陈旧的。
用户代理照样能采取步骤去获得第一手的或保鲜的响应。由于这个原因,如果客户端显示地请求第一手的或保鲜的响应,缓存就不能返回一个陈旧的响应,除非由于技术或策略方面的原因。
13.1.6由客户控制的行为(Client-controlled Behavior)
当源服务器是过期信息得主要来源时,有时候客户端可能需要控制缓存去判定是否返回一个缓存响应而不需要缓存通过服务器验证它。客户端通过利用一些Cache-Control头域来达到此目的。
客户端的请求可能会指定自己愿意接受一个没有验证的响应的最大的年龄(age);指定0值会强迫缓存重验证所有的响应。客户端照样会指定在响应过期前的最小保持时间。这些选项增加了对缓存行为的限制,同时这样做并不能进一步地放松缓存语义透明的近似。
客户端可能照样会指定它会接受陈旧响应直到陈旧数达到最大数量。这放松了对缓存的限制,同时这可能违反了源服务器指定对语义透明性的限制,但是这可能支持无连接的操作或者高获得性当连接不好时。
13.2 过期模型 (Expiration Model)
13.2.1 服务器指定模型(Server-Specified Expiratiion)
HTTP缓存会工作的很好,这是因为缓存能完全地避免客户端对源服务器的请求。避免对源服务器请求的主要机制是服务器将来会提供一个显示过期时间(explicit expiration time),此显示过期时间用来指示响应可能会满足后续的请求。也就是说,客户端请求响应时,缓存能返回一个保鲜(fresh)的响应而不需要从源服务器那里获得。
我们希望服务器给响应设置显示过期时间(explicit expiration time),服务器相信在过期时间之前实体不会改变。这能保持语义透明性(译注:语义透明性情况1.3节里关于“语义透明”的解释),只要服务器对过期时间仔细选择。
过期机制只能应用于能从缓存获得的响应,不能应用于客户端请求的第一手(first-hand,见1.3节术语)的响应。
如果源服务器希望强制一个语义透明缓存去验证每个请求,它会使显示过期时间(explicit expiration time)设为过去。这就是说响应总是陈旧的,所以此缓存应该验证此响应当缓存利用此响应去服务于后续的请求时。见14.9.4节,有更多强迫重验证的方法
如果源服务器希望强迫任何HTTP/1.1缓存(不管此缓存是怎样设置的)去验证每一个请求,源服务器应该在Cache-Control头域里指定“must-revalidate”缓存控制指令(见14.9节)。
源服务器利用Expires头域或在Cache-Control头域里通过max-age缓存控制指令,来指定显示过期时间(explicit expiration time)。
过期时间不能被用于强制客户代理去重新刷新它的显示或重载资源;过期的语义只能应用于缓存机制,并且这个机制值只需要检测资源的过期状态当请求那个资源的一个新请求发生时。见13.13节,关于缓存和历史机制的区别。
13.2.2 启发式过期
因为源服务器不能总是提供一个显示过期时间(explicit expiration time),HTTP缓存通常会设置一个启发式过期时间(heuristic expiration time),它采用一种算法,此算法利用其它的值(例如Last-Modified时间)去估计一个似乎合理的过期时间。HTTP/1.1规范没有提供特定的算法,但是的确是加强了对这些算法结果的最坏情况的限制。因为启发式过期时间可能会对语义透明性妥协,他们本应该被谨慎地使用,并且我们鼓励源服务器尽可能提供显示过期时间。
13.2.3 年龄(Age)计算
为了了解缓存项(译注:缓存项是用来响应请求的,它包含缓存的响应实体)是否是保鲜的(fresh),缓存需要知道其年龄是否已超过保鲜寿命(freshness lifetime)。我们在13.2.4节中讨论如何计算保鲜寿命,本节讨论如何计算响应或缓存项的年龄。
在此讨论中我们用“now”来表示主机进行计算时时钟的“当前值”。使用HTTP协议的主机,特别是运行于源服务器和缓存的主机,应该使用NTP[28] 或其他类似协议来将其时钟同步到一个全球性的精确时间标准上。
HTTP1.1协议要求源服务器尽可能在发送每条响应时都附加一个Date头域,要尽可能在每个响应里给出响应产生的时间(见14.18节)。我们利用术语“date_value”去表示Date头域的值,这是一种适合于运算操作的表示方法。
当从缓存里获取响应消息时,HTTP/1.1利用Age响应头域来传送响应消息的估计年龄。Age响应头域值是缓存对响应从产生或被重验证开始到现在的时间估计值。
实际上,年龄的值是响应从源服务器途径每一个缓存的逗留时间的总和,再加上响应在网络路径上传输的时间。
我们用“age_value”来表示Age头域的值,这是一种适于算术操作的表示方法。
一个响应的年龄(age)可以通过两种完全不同的途径来计算::
如果本地时钟与源服务器时钟同步的相当好,则用 now - date_value ,若结果为负,则取零。
如果途径响应路径(response path)的所有缓存均遵循HTTP1.1协议,则用age_value。
如果我们有两种独立的方法计算响应的年龄(译注:注意这里是响应的年龄),我们可以合并二者如下:
corrected_received_age = max(now – date_value,age_value)
并且只要我们或者有同步的时钟或者响应途径的所有缓存遵循HTTP/1.1,我们就能得到一个可信赖的结果。
由于网络附加延时,一些重要时隙会在服务器产生响应与下一个缓存或客户端接收之间流逝。如果不经修订,这一延迟会带来不正常的低年龄。
由于导致产生年龄值的请求(译注:就是存在Age头域的请求)是早于年龄值的产生,我们能校正网络附加延迟通过记录请求产生的时间。然后,当一个年龄值被接收后,它必须被解释成相对于请求产生的时间,而不是相对响应接收的时间。不管有多少延迟,此算法会导致稳定的结果。所以,我们计算:
corrected_initial_age = corrected_received_age + (now - request_time)
这里“request_time”是请求的发送时间。
缓存收到响应时,它计算响应年龄的算法如下:
/*
* age_value
* is the value of Age: header received by the cache with this response.
* date_value
* is the value of the origin server's Date: header
* request_time
* is the (local) time when the cache made the request
* that resulted in this cached response
* response_time
* is the (local) time when the cache received the response
* now
* is the current (local) time
*/
apparent_age = max(0, response_time - date_value); //缓存收到响应时响应的年龄
corrected_received_age = max(apparent_age, age_value);
response_delay = response_time - request_time;
corrected_initial_age = corrected_received_age + response_delay;
resident_time = now - response_time; //即收到响应到现在的时间间隔
current_age = corrected_initial_age + resident_time;
缓存项(cache entry)的current_age是缓存项从被源服务器最后验证开始到现在的时间间隔(以秒记)加上corrected_initial_age。当从缓存项里产生一条响应时,缓存必须在响应里包含一个Age头域,它的值应该等于缓存项的current_age值。
Age头域出现在响应里说明响应不是第一手的(first-hand)(译注:第一手的说明,响应是直接来自于源服务器到达接收端的,而不是来自于缓存里保存的副本)。然而相反的情况并不成立,因为响应里缺少Age头域并不能说明响应是第一手的(fisrt-hand),除非所有请求路径上的缓存都遵循HTTP/1.1协议(也就是说,以前HTTP版本缓存没有定义Age头域)。
13.2.4 过期计算(Expiration Calculations)
为了确定一条响应是保鲜的(fresh)还是陈旧的(stale),我们需要将其保鲜寿命(freshness lifetime)和年龄(age)进行比较。年龄的计算见13.2.3节,本节讲解怎样计算保鲜寿命,以及判定一个响应是否已经过期。在下面的讨论中,数值可以用任何适于算术操作的形式表示。
我们用术语“expires_value”来表明Expires头域的值。我们用术语“max_age_value”来表示Cache-Control头域里“max-age”控制指令的值(见14.9.3节)。
max-age指令优于Expires头域执行,所以如果max-age出现在响应里,那么定义如下:
freshness_lifetime = max_age_value
否则,若Expires头域出现在响应里,定义如下:
freshness_lifetime = expires_value - date_value
注意上述运算不受时钟误差影响,因为所有信息均来自源服务器。
如果Expires, Cache-Control:max-age, 或 Cache-Control:s-maxage (见 14.9.3) 均未在响应中出现,且响应没有包含对缓存的其他控制,那么缓存可以用启发式算法计算保鲜寿命(freshness lifetime)。缓存器必须对年龄大于24小时的响应附加113警告,如果此响应不带这种警告。
而且,如果响应有最后修改时间,启发式过期值应不大于从那个时间开始间隔的某个分数。典型设置为间隔的10% 。
计算响应是否过期非常简单:
response_is_fresh = (freshness_lifetime > current_age)
13.2.5澄清过期值(Disambiguation Expiration Values)
由于过期值很容易被设置,有可能两个缓存会包含同一资源的不同保鲜值。
如果客户端执行请求接收到一个非第一手的响应,此响应在此客户端拥有的缓存里仍然是保鲜的,并且缓存里的缓存项里的Date头域的值比此新响应的Date头域值要新,那么客户端应该忽略此响应。如果这样,它可能会重新以“Cache-Control:max-age=0”指令(见14.9节)请求去强制任何中间缓存通过源服务器对其进行检查。
如果缓存对有不同验证器(validator)的同一个表现形式(representation)有两个保鲜响应,那么缓存必须利用Date头域值最近的响应。这种情况可能发生由于缓存会从其他缓存得到响应,或者由于客户端请求对一个保鲜缓存项的重载或重验证(revalidation)。
13.2.6澄清多个响应(Disambiguating Multiple Response)
因为客户端可能收到经多个路径而来的响应,所以有些响应会经过一些缓存,而其他的响应会经过其他的缓存,客户端收到响应的顺序可能与源服务器发响应的顺序不同。我们希望客户端利用最新的响应,即使旧响应仍然是保鲜的。
实体标签(entity tag)和过期值都不能影响响应的顺序,因为可能会出现晚一点的响应可能会故意携带过早的过期时间。日期值的精度被规定只有一秒。
当客户端试着重新验证一个缓存项的时候(译注:这里的客户端应该包含一个本地缓存),而且接收到的响应的Date头域晚于已存在的缓存项,那么客户端应该重新进行无条件请求,并且包含
Cache-Control: max-age=0
去强制任何中间缓存通过源服务器来验证(validate)这些中间缓存的副本,或者
Cache-Control: no-cache
去强制任何中间缓存去从源服务器获得一个新的副本。
13.3 验证模型(Validation Model)
当缓存有一个旧缓存项并且缓存想利用此缓存项来作为客户端请求的响应时,缓存必须首先通过源服务器(或者可能通过一个有保鲜响应的中间缓存)对其进行检验看看此缓存项是否可用。我们称做“验证(validating)”此缓存项。因为我们不想对完整响应的传输付出太大代价,而且如果缓存项无效时也不会产生额外的回路(译注:回路的意思,如:从请求到响应就一条回路),HTTP1.1协议支持使用条件方法。.
协议支持条件方法的关键特征是围绕“缓存验证器(cache validator)”展开的。当源服务器产成一个完整响应时,它同时会附加一些验证器给响应,这些验证器和缓存项一起保存。当客户端(用户代理或缓存服务器)对对应有缓存项的资源执行条件请求时,客户端包含一个相关的验证器(validator)在请求里。
<br style="color:rgb(24,95,24);font-family:Georgia;font