HTTP缓存机制[译文]

本文翻译自: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching ,主要用于我的记录和共享,如有疏漏错误,请不吝指正,谢谢!html

 

经过重用已获取的资源,可大幅提升web站点和应用的性能。因为web缓存减小了延迟和网络流量,所以缩短了展现一个资源所需的时间。经过使用HTTP缓存机制,web站点可实现更快更灵活的响应。webpack

不一样类型的缓存

缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。当发起一个请求时,web缓存会判断是否已有此请求的一个副本(以前已经请求过一次而且被缓存了),如有,则缓存会拦截此请求,并直接返回缓存中的请求结果副本,从而防止从新到源服务器下载资源。缓存的目的:减轻服务器压力(服务器不用每次为全部客户端提供服务了),提升访问效率(由于缓存离客户端最近,可直接提供资源副本,也可节省不少传输时间)。对于网站来说,缓存是建设高性能网站的最重要的组件,但在另外方面,缓存必须进行合理的配置才能达到最佳效果,由于并不是全部资源都是永久不变更的,因此咱们须要保证某个资源的缓存仅在它未变更时有效。web

全部不一样类型的缓存,大体能够归为两类:私有缓存 和 共享缓存。共享缓存中存储的资源副本是供全部用户使用的(好比不一样浏览器,不一样机器),而私有缓存是仅提供给单个用户的专有缓存(不一样用户保留不一样私有缓存副本)。本文仅讨论浏览器缓存和代理缓存,但就目前来说,还有不少其余类型的缓存,好比:网关缓存、CDN、反向代理缓存、负载均衡(负载均衡是部署在服务器端的,为多个web服务器提供更可靠、更高性能以及更易进行规模化扩展的方案)。算法

What a cache provide, advantages/disadvantages of shared/private caches.

浏览器(私有)缓存

私有缓存是单个用户的专有缓存,通常来说,在你的浏览器设置中就能够看到“缓存”的选项。浏览器缓存保留了用户经过HTTP下载的全部文档资源,前进/后退、保存、查看源代码等操做均可以使用到此缓存,而不用再从新访问服务器。一样的,有了缓存,咱们还能够实现脱机浏览文档和资源。浏览器

代理(共享)缓存

共享缓存中存放的访问结果是提供给多个用户使用的。好比:ISP或你的公司可能会组建一个本地网络的代理,该代理(服务器)会缓存不一样用户访问外网时请求的公共资源,这些公共资源被缓存后,下次其余用户也访问同一资源时,就会重用此已被缓存的资源(就不用再向源站获取了),从而减小了网络浏览和延迟。缓存

缓存操做的目标

HTTP缓存虽然是可选的,但通常是全部人都须要的。HTTP缓存一般只缓存GET请求(其余请求通常不缓存),缓存的主键由请求方法和目标URI(一般只用到URI,由于通常仅缓存GET请求)组成。一般的缓存条目有:服务器

  • 成功的查询请求的结果数据:状态码为200的GET响应(结果中可能包含资源数据如:HTML文档、图片或文件等)
  • 永久性跳转:状态码为301(Moved Permanently)的响应
  • 返回出错,文档不存在:状态码为404(Not Found)的响应
  • 不完整的结果数据:状态码为206(Partial Content)的响应(经过Range头发起的请求所返回的结果,Range用于只获取文档某一部分)
  • 其余非GET请求的结果(若是这些结果比较适合做为缓存的话)

缓存的条目也可能缓存多个,其中根据内容协商方式,每个对应的二级键(header头中的字段)不同。详见 Vary 头。
网络

缓存控制

Cache-control头部

HTTP/1.1中,Cache-Control头用于指定缓存机制中的不一样指令,它是可用在请求报文及响应报文中的通用头部。经过该头部提供的不一样指令,你能够定义一个本身的缓存策略。app

禁止缓存 方式

以下头部定义,在该方式下,缓存不会保存任何的客户端请求和服务器响应。每次客户端的请求都会发送到源服务器,而且每次源服务器返回的数据都会所有下载到客户端。负载均衡

Cache-Control: no-store Cache-Control: no-cache, no-store, must-revalidate

强制确认缓存 方式

以下头部定义,此方式下,每次有请求发出时,缓存会将此请求发到服务器(译者注:该请求应该会带有与本地缓存相关的验证字段),服务器端会验证请求中所描述的缓存是否过时,若未过时(译者注:实际就是返回304),则缓存才使用本地缓存副本。

Cache-Control: no-cache

私有和公共缓存

"public" 指令表示该响应能够被任何中间人(译者注:好比中间代理、CDN等)缓存。若指定了"public",则一些一般不被中间人缓存的页面(译者注:由于默认是private)(好比 带有HTTP验证信息(账号密码)的页面 或 某些特定影响状态码的页面),将会被其缓存。

而 "private" 则表示该响应是专用于某单个用户的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中。

Cache-Control: private Cache-Control: public

过时机制

过时机制中,最重要的指令是 "max-age=<seconds>",表示资源可以被缓存(保持新鲜)的最大时间。与 Expires指令不一样,该指令的值是相对于请求的那个时间以后的秒数。对于那些不会变更的文档资源,你能够直接将其设置为永久缓存,好比像图片、CSS文件、JS文件这些静态资源。

更多信息,参见下方的 Freshness 。

Cache-Control: max-age=31536000

验证确认

当使用了 "must-revalidate" 指令,那就意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,而且,已过时的缓存将不被使用。更多信息,参见下方的 Validation

Cache-Control: must-revalidate

Pragma 头部

Pragma 是HTTP/1.0规范中的头部,它已经不是可靠的用于过时控制的头部了,尽管它的行为和 Cache-Control: no-cache 一致(未设置Cache-Control头部的状况下)。Pragma 现仅用于兼容 HTTP/1.0 客户端。

新鲜度

理论上来说,当一个资源被缓存存储后,该资源应该能够被永久存储在缓存中。因为缓存只有有限的空间用于存储资源副本,因此缓存会按期地将一些副本删除,这个过程叫作 缓存驱逐。另外一方面,当服务器上面的资源进行了更新,那么缓存中的对应资源也应该被更新,因为HTTP是C/S模式的协议,服务器更新一个资源时,不可能直接通知客户端及其缓存,因此双方必须为该资源约定一个过时时间,在该过时时间以前,该资源(缓存副本)就是 新鲜的,当过了过时时间后,该资源(缓存副本)则变为 陈旧的。驱逐算法用于将陈旧的资源(缓存副本)替换为新鲜的,注意,一个陈旧的资源(缓存副本)是不会直接被清除或忽略的,当客户端发起一个请求时,缓存检索到已有一个对应的陈旧资源(缓存副本),则缓存会先将此请求附加一个If-None-Match头,而后发给目标服务器,以此来检查该资源副本是不是依然仍是算新鲜的,若服务器返回了 304 (Not Modified)(该响应不会有带有实体信息),则表示此资源副本是新鲜的,这样一来,能够节省一些带宽。(译者注:若服务器经过 If-None-Match 或 If-Modified-Since判断后发现已过时,那么会带有该资源的实体内容返回)

下面是一个代理共享缓存的过程示例:

Show how a proxy cache acts when a doc is not cache, in the cache and fresh, in the cache and stale.

新鲜度的生命周期是经过若干头部值来计算的,若是设置了 "Cache-control: max-age=N" 头部,那么新鲜度的生命期则等于 N。常常状况下,可能未设置此头部,则会检查 Expires 头部是否存在,若 Expires 头部存在,则新鲜度生命期 等于 该头部的值 减去 Date 头部的值。若两种头部都未设置,则会查找 Last-Modified 头部,若存在,则新鲜度生命期 等于 Date 头部值 减去 Last-modified 头部值 再除以 10。

expirationTime = responseTime + freshnessLifetime - currentAge

上式中,responseTime 表示浏览器接收到此响应的那个时间点。

资源版本化

缓存使用越频繁(译者注:跟命中率有关了),那么网站的响应速度和效率就越高,为此,在最佳实践中,咱们推荐尽量地将过时时间设置得长一些,但这会致使咱们很难去更新那些不常变更的资源。好比咱们常常会遇到这样的需求:不少页面都引用了一些JS和CSS文件,当这些文件的内容变更时,咱们但愿能尽快地让其在缓存中更新。

web开发者们研究出一个方案,Steve Sounders称它为 revving[1]。其原理是,将那些常常更新的文件的文件名经过一种特别的方式来命名,即文件名中加入版本号,这样一来,每一次文件内容改变,文件名也被一块儿改变,就至关于新建了另外一个不一样的资源,那咱们就能够将该资源设置为永久不过时了(一般设置为1年以上)。但为了引用这个改动后的资源,因此连接到此资源的连接地址都须要改变(译者注:其文件名改变后,相对应的URI也改变,因此连接到此资源的地址也应该改变),这也是该方案的缺点:带来了额外的复杂性,一般web开发者会使用一些工具来自动应对此缺点(译者注:如webpack),当不常变更的资源改变时,这些资源的文件名URI也随之改变,而引用这些资源的另外的常常变更的资源,也将随之改变(引用地址改变),当客户端请求常变更的资源时,它里面引用的不常变更的资源,因为加入了版本号,因此其新的版本也被下载。

这种方案有一个好处:能够解决2个资源过时时间不一致致使不一同被更新的问题。这在当网站的CSS或JS资源拥有共同的依赖时显得尤其重要,好比他们引用了同一个HTML元素,致使他们互相依赖。(译者注:举例,好比当前缓存了两个JS文件A,B,A里面是比较早期的功能,A先过时,B是晚于A开发的功能,且依赖于A,B后过时,假如这期间服务器端A和B都作了改变,加了新的功能,那么当A过时时拿到了最新版的A,而B还未过时,则使用的是旧版的B,这样页面运行时,可能致使严重错误)。

添加在资源名称上面的版本号并不是必须是像1.1.3这样的经常使用版本定义方式,甚至能够仅仅是一个连续递增的数字,只要版本号不冲突,它能够是任意的,好比hash值或日期时间。

缓存的验证确认

当用户点击刷新(从新加载)按钮时,就会触发一个再次验证确认,当用户正常访问某网站/资源时,浏览器会检查该请求对应的缓存内容,若以前缓存的响应内容中,包含了 "Cache-control: must-revalidate" 头部,那么也会触发一个再次验证确认。另一个引起再次验证确认的因素是:用户能够在浏览器的 Advanced->Cache 设置选项面板中设置 强制每次加载页面时都进行验证确认。

当一个缓存副本已经到了过时时间,那么就会先到服务器验证确认此缓存的新鲜度,或者直接从服务器获取该资源最新的内容。验证确认操做仅仅在服务器的响应信息中提供了 强验证器弱验证器 才会发生。

ETag头部

响应报文中的 ETag 头是一个对 用户代理透明 的值,被用来做为强验证器,意思就是说,像浏览器这样的用户代理程序并不知道其值表明的含义。若在服务器的响应报文中含有 ETag 头部,则后续客户端发起同一请求时,会附加一个 If-None-Match 头部(其值为以前Etag的值)用于让服务器验证该请求对应的缓存是否新鲜。

响应报文中的 Last-Modified 头用来做为弱验证器,之因此做为“弱”,是由于它最多只能精确到秒,若服务器的响应报文中存在 Last-Modified 头部,那么客户端发起一个 If-Modified-Since 请求来验证缓存是否新鲜。

当发起了一个验证请求,服务器能够忽略此验证请求并返回一个正常的 200 OK 响应报文,或者也能够返回 304 Not Modified(带有一个空的实体)来告知浏览器:你可使用当前缓存副本,后者(304)的响应报文中也能够附加一些头部,用来更新当前缓存副本中缓存的头部信息。

可变响应 - Vary头部

响应报文中的 Vary 头部用于 决定如何匹配后续请求的头部,从而决定是否采用某缓存副本,而不是从服务器得到一个新的副本。

当发起一个请求时,缓存命中了一个副本(以前缓存的响应报文信息),而这个副本中含有 Vary 头部,那么缓存须要检查 Vary 头部中所列出的头部字段,若在当前请求中的这些头部字段值 与 缓存副本中响应的头部字段值匹配,那么可使用此缓存副本,不然须要不能使用此副本(从新请求服务器)。

The Vary header leads cache to use more HTTP headers as key for the cache.

有了这个头部,就能够动态地提供内容了,举个例子,当咱们使用 Vary: User-Agent 头部时,缓存服务器就须要去查看新发起的请求的 User-Agent 头部是否匹配,而后再决定是否采用缓存。若是你须要为手机端用户展现不一样的内容,此举能够防止将电脑端的缓存错误地提供给了手机端的用户,而且还能够帮助Google这些搜索引擎发现并抓取手机端版本的页面,以及告诉搜索引擎这不是 Cloaking 做弊。

Vary: User-Agent

因为 User-Agent 头部在移动端和电脑端中的值都是不一样的,因此缓存就不会错误地将移动端内容提供给电脑端用户了,反之亦然。

参考资料

相关文章
相关标签/搜索