本文整理自《HTTP权威指南》、《图解HTTP 》。
写在前面
自己想了解HTTP缓存时,看了许多相关文章,发现大多从强缓存和协商缓存分类介绍,仔细研究却发现文章描述总有些自我矛盾。
于是想从参考文献学习,然而查阅《HTTP权威指南》、《图解HTTP》以及一些文档后,无一提到“强缓存”和“协商缓存”,更加困惑。希望看到这篇文章且了解这两个定义出处的同学不吝赐教。
所以决定抛弃许多热门文章描述的“强缓存”、“协商缓存”,从《HTTP权威指南》、《图解HTTP》学习,毕竟HTTP缓存就是那么个事,叙述清楚就好。
于是有了这篇文章。
缓存的基础知识
缓存是什么
缓存是指代理服务器或客户端本地磁盘内保存的资源副本。
缓存保存在哪
- 代理服务器中
- 客户端浏览器中
缓存可以保存在缓存服务器中,也可以存在客户端浏览器中。
缓存服务器是代理服务器的一种,并归类在缓存代理类型中。本篇文章下面将详细描述缓存服务器的缓存。
为什么需要缓存
利用缓存可减少对原始服务器的访问,因此就节省了通信流量和通信时间。
缓存有以下优点:
- 减少了冗余的数据传输
- 缓解了网络瓶颈问题
- 降低了对原始服务器的要求
- 降低了距离时延
缓存命中和未命中
由于缓存无法保存世界上每份文档的副本(巨大并且无法及时更新),就出现了缓存的命中与未命中情况。
- 缓存命中(cache hit):到达缓存的请求可以由已有的副本提供服务。
- 缓存未命中(cache miss):到达缓存的请求由于没有副本可用,而被转发给原始服务器。
新鲜度
就算缓存保存了副本,可能不是所有的已缓存副本都与服务器上的文档一致。毕竟,这些文档会随着时间发生变化。如果缓存提供的总是老的数据,就会变得毫无用处。
HTTP有一些简单的机制保持已缓存数据与服务器数据之间充分一致。HTTP将这些简单的机制称为文档过期(document expiration)和服务器再验证(server revalidation)。
再验证
原始服务器的内容可能会发生变化,缓存对其进行检测,看它们保存的副本是否仍是服务器上最新的副本。这些“新鲜度检测”成为HTTP再验证。
为了有效的进行再验证,HTTP定义了一些特殊的请求,不用从服务器上获取整个对象,就可以快速检测出内容是否是最新的。后面会解释HTTP的新鲜度检测规则。
缓存的过程
HTTP缓存流程图如下,显示缓存处理器如何处理请求。

接收每一个缓存请求,缓存处理器对请求解析后,首先要查看是否命中,即是否有本地副本可用:
-
如果没有,就从服务器获取一份副本(并将其保存在本地)。
-
如果有,就要查看已缓存副本是否足够新鲜:
- 如果新鲜,缓存用新首部和已缓存的主题构建一条响应报文,提供给客户端
- 如果不新鲜,就与服务器进行再验证,询问服务器是否有任何更新:
- 再验证命中:即服务器对象未被修改,服务器向客户端发送一个小的HTTP 304 Not Modified 响应(包含一个新的过期时间)
- 再验证未命中:即服务器对象与已缓存副本不同,服务器向客户端发送一条普通的、带有完整内容的HTTP 200 OK响应(新首部+新主体)。
- 对象呗删除:如果服务器对象已经被删除了,服务器就回送一个404 Not Found响应,缓存也会将其副本删除。
新鲜度检测规则
通过HTTP Cache-Control
和 Expires
首部,HTTP让原始服务器向每个文档附加了一个“过期日期”,这些首部说明了在多长时间内可以将这些内容视为新鲜的。
在缓存文档过期之前,缓存可以以任意频率使用这些副本,而无需与服务器联系----当然,除非客户端请求中包含阻止提供已缓存或未验证资源的首部。但一旦已缓存文档国企,缓存就必须与服务器进行核对,询问文档是否被修改过,如果被修改过,就哟啊获取一份新鲜(带有新的过期日期)的副本。
过期日期和使用期
Expires
首部
Expires
是HTTP/1.0+,指定一个绝对的过期日期。如果过期日期已经过了,就说明文档不再新鲜了。
例如:

Cache-Control: max-age
Cache-Control: max-age
是HTTP/1.1,是相对时间,来指定过期日期,定义了文档的最大使用期,以秒为单位。例如:

Cache-Control
的优先级大于Expires
。
服务器再验证
仅仅是已缓存文档过期了并不意味着它和原始服务器上的文档有实际的区别;只意味着要到了进行核对的时间了。这种情况被称为“服务器再验证”。
对缓存再验证来说最有用的2个首部是If-Modified-Since
和If-None-Match
。
If-Modified-Since
:Date再验证
If-Modified-Since
通常被称为IMS请求,只有与自某个日期之后资源发生了变化的时候,IMS请求才会指示服务器执行请求。
与Last-Modified
服务器响应首部配合使用,此首部使用绝对时间表示最后修改日期。都是绝对时间。
If-None-Match
:实体标签(ETag
)再验证
有些情况下仅使用最后修改日期进行验证是不够的,HTTP允许用户对实体标签(ETag
)的“版本标识符”进行比较。
ETag
是附加到文档上的任意标签(引用字符串)。当发布者对文档进行修改时,可以修改ETag
来说明这个新的版本。

使用优先级
ETag
优先级大于Last-Modified
:
-
只要服务器回送了一个实体标签,HTTP/1.1 客户端就必须使用实体标签验证器。
-
只有当服务器只回送了一个
Last-Modified
值,客户端可以使用If-Modified-Since
。 -
如果服务器两个都提供了,客户端要使用两种验证方案,并且只有两个都满足时才能返回304。
控制缓存的能力
服务器可以通过HTTP定义的几种方式来指定文档过期之前可以将其缓存多长时间。按照优先级递减,服务器可以:
- 附加一个
Cache-Control: no-store
首部到响应中去; - 附加一个
Cache-Control: no-cache
首部到响应中去; - 附加一个
Cache-Control: must-revalidate
首部到响应中去; - 附加一个
Cache-Control: max-age
首部到响应中去;
Cache-Control: no-store
首部
禁止缓存对响应进行复制。缓存应该尽快从存储器中删除文档的所有痕迹。
Cache-Control: no-cache
首部
除非资源进行了再验证,否则这个客户端不会接受已缓存的资源。
Cache-Control: must-revalidate
首部
可以配置缓存,使其提供一些陈旧的对象,以提供性能。
Cache-Control: max-age
首部
如果将最大使用期设置为0,则服务器可以请求缓存不要缓存文档,从而每次访问的时候都进行刷新。
还有一个s-maxage
首部与max-age
相似,但仅适用于公有缓存。