什么是缓存?
缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。当 web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。
缓存的作用
缓解服务器端压力,提升性能(获取资源的耗时更短了)。
缓存分类
私有缓存
私有缓存只能用于单独用户。
- 浏览器缓存
共享(共有)缓存
共享缓存可以被多个用户使用。
- 代理缓存
- 网关缓存
- CDN
- 反向代理缓存
缓存命中和缓存未命中
可以用已有的副本为某些到达缓存的请求提供服务,这被称为缓存命中(cache hit)。其他一些到达缓存的请求可能会由于没有副本可用,而被转发给原始服务器,这被称为缓存未命中(cache miss)。
再验证
原始服务器的内容可能会发生变化,缓存要不时对其进行检测,看看它们保存的副本是否仍是服务器上最新的副本。这些“新鲜度检测”被称为HTTP再验证。
缓存对缓存的副本进行再验证时,会向原始服务器发送一个小的再验证请求。如果内容没有变化,服务器会一个小的304 Not Modified进行响应。只要缓存知道副本仍然有效,就会再次将副本标识为暂时新鲜的,并将副本提供给客户端。这被称作再验证命中或缓慢命中。由于要与服务器进行核对,所以这种方式比缓存命中要慢,但是它没有从服务器获取对象数据,所以比缓存未命中要快一些。
最常用的再验证工具If-Modified-Since首部
- 再验证命中
如果服务器对象未被修改,服务器会向客户端发送一个小的HTTP 304 Not Modified响应。 - 再验证未命中。
如果服务器对象已与缓存副本不同,服务器向客户端发送一条普通的、带有完整内容的HTTP 200 OK响应。 - 对象被删除
如果服务器对象已经被删除了,服务器就会回送一个404 Not Found响应,缓存也会将其副本删除。
区分命中和未命中的情况
HTTP没有为用户提供一种手段来区分响应是缓存命中的,还是访问原始服务器得到的。在这两种情况下,响应码都是200 OK,说明响应有主题部分。
客户端有一种方法可以判断响应是否来自缓存,就是使用Date首部(将Date首部的值与当前时间进行对比)。客户端也可以通过Age首部来检测缓存的响应,通过这个首部可以分辨出这条响应的使用期。
缓存的处理步骤
对一条HTTP GET报文的基本缓存处理过程包括7个步骤:
- 接收——缓存从网络中读取抵达的请求报文。
- 解析——缓存对报文进行解析,提取出URL和各种首部。
- 查询——缓存查看是否有本地副本可用,如果没有,就获取一份副本并将其保存在本地
- 新鲜度检测——缓存查看已缓存的副本是否够新鲜,如果不是,就询问服务器是否有任何更新
- 创建响应——缓存会用新的首部和已缓存的主体来构建一条响应报文
- 发送——缓存通过网络将响应发回给客户端
- 日志——写日志记录
保持副本的新鲜
HTTP有一些简单的机制可以在不要求服务器记住有哪些缓存拥有其副本的情况下,保持已缓存数据与服务器数据之间充分一致。HTTP将这些简单的机制称为文档过期
和服务器再验证
。
文档过期
通过特殊的HTTP Cache-Control
首部和Expires
首部,HTTP让原始服务器向每个文档附加了一个“过期日期”。
在缓存文档过期之前,缓存可以以任意频率使用这些副本,而无需与服务器联系——当然,除非客户端请求中包含有阻止提供已缓存或未验证资源的首部。但一旦已缓存文档过期,缓存就必须与服务器进行核对,询问文档是否被修改过,如果被修改过,就要获取一份新鲜的副本。
过期日期和使用期
服务器用HTTP/1.0+的Expires
首部或HTTP/1.1的Cache-Control: max-age
响应首部来指定过期日期,同时还会带有响应主体。
Cache-Control: max-age
max-age值定义了文档的最大使用期——从第一次生成文档到文档不再新鲜、无法使用为止,最大的合法生存时间(以秒为单位)Expires
指定一个绝对日期。如果过期日已经过了,就说明文档不再新鲜了
服务器再验证
仅仅是缓存文档过期了并不意味着它和原始服务器上目前处于活跃状态的文档有实际的区别,这只意味着到了要进行核对的时间了。这种情况被称为“服务器再验证”,说明缓存需要询问原始服务器是否发生了变化。
- 如果发生了变化,缓存会获取一份新的文档副本,并将其存储在旧文档的位置上,然后将文档发送给客户端。
- 如果没有发生变化,缓存只需要获取新的首部,包括一个新的过期日期,并对缓存中的首部进行更新就好了。
用条件方法进行再验证
HTTP允许缓存向服务器发送一个“条件GET”,请求服务器只有在文档与缓存中现有副本不同时,才会回送对象主体。通过这种方式,将新鲜度检测和对象获取结合成了单个条件GET。向GET请求报文中添加一些特殊的条件首部,就可以发起条件GET。只有条件为真时,Web服务器才会返回对象。
HTTP定义了5个条件首部。对缓存再验证来说最有用的2个首部是If-Modified-Since
和If-None-Match
。
If-Modified-Since:<date>
如果从指定日期之后文档被修改了,就执行请求的方法。可以与Last-Modified
服务器响应首部配合使用If-Modified-Since: <cached last-modified date>
,只有在内容被修改后与已缓存版本有所不同的时候才去获取内容If-None-Match:<tags>
服务器可以为文档提供特殊的标签(ETag
),而不是将其与最近修改日期相匹配,这些标签就像序列号一样。如果已缓存标签与服务器文档中的标签有所不同,If-None-Match首部就会执行所请求的方法
强弱验证器
实体标签和最近修改日期都是缓存验证器
。
有时候,服务器希望在对文档进行一些非实质性或不重要的修改时,不要使所有已缓存副本都失效。HTTP/1.1支持弱验证器
,如果只是对内容进行了少量修改,就允许服务器声明那是“足够好”的等价体。
只要内容发生了变化,强验证器
就会变化。弱验证器
允许对一些内容进行修改,但内容的主要含义发生变化时,通常它还是会变化的。
什么时候应该使用实体标签和最近修改日期
- 如果服务器回送了实体标签,HTTP/1.1客户端就必须使用实体标签验证器
- 如果服务器只回送了一个Last Modified值,客户端就可以使用If-Modified-Since验证
- 如果实体标签和最后修改日期都提供了,客户端就应该使用这两种再验证方案,这样HTTP/1.0和HTTP/1.1缓存就都可以正确响应了
- 如果HTTP/1.1缓存或服务器收到的请求既带有If-Modified-Since,又带有实体标签条件首部,那么只有这两个条件同时满足时,才能返回304 Not Modified响应
控制缓存的能力
no-store与no-cache响应首部
Pragma: no-cache
Cache-Control: no-store
Cache-Contorl: no-cache
- 标识为
no-store
的响应会禁止缓存对响应进行复制(即不允许将响应主体对象存到缓存中区)。 - 标识为
no-cache
的响应实际上是可以存储在本地缓存区中的。只是在与原始服务器进行新鲜度再验证之前,缓存不能将其提供给客户端使用。
HTTP/1.1中提供Pragma: no-cache
首部是为了兼容于HTTP/1.0+。除了与只理解Pragma: no-cache
的HTTP/1.0应用程序进行交互时,HTTP/1.1应用程序都应该使用Cache-Contorl: no-cache
。
max-age响应首部
Cache-Contorl: max-age
首部表示的是从服务器将文档传来之时起,可以认为此文档处于新鲜状态的秒数。还有一个s-maxage
首部,其行为与max-age
类似,但仅适用于共享缓存:
Cache-Control: max-age=3600
Cache-Control: s-maxage=3600
服务器可以请求缓存不要缓存文档,或者将最大使用期设置为零,从而在每次访问的时候都进行刷新:
Cache-Control: max-age=0
Cache-Control: s-maxage=0
Expires响应首部
不推荐使用Expires首部,它指定的是实际的过期日期而不是秒数。
expires: Wed, 09 Feb 2022 05:41:19 GMT
有些服务器还会回送一个expires: 0
响应首部,试图将文档置于永远过期的状态,但这种语法是非法的,可能给某些软件带来问题。
must-revalidate响应首部
可以配置缓存,使其提供一些陈旧(过期)的对象,以提高性能。如果原始服务器希望缓存严格遵守过期信息,可以在原始响应中附加一个Cache-Control: must-revalidate
首部。
Cache-Control: must-revalidate
max-stale响应首部
Cache-Control: max-stale
Cache-Control: max-stale=<s>
缓存可以随意提供过期的文件。如果指定了<s>
参数,在这段时间内,文档就不能过期。这条指令放松了缓存的规则。
min-fresh响应首部
Cache-Control: min-fresh=<s>
至少在未来的<s>
秒内文档要保持新鲜。
only-if-cached响应首部
Cache-Control: only-if-cached
只有当缓存中有副本存在时,客户端才会获取一份副本。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。