我只是想弄懂缓存而已~

797 阅读16分钟

关于HTTP缓存,一直都想好好了解一下,之前也在掘金上搜过一些文章,也看过《HTTP权威指南》缓存那一章,可无奈的是,总是无法形成一个系统的联结,只是知道有这么些HTTP定义的字段跟缓存相关,但是具体这些字段之间的关系啊,怎么用的啊,看过都不是很理解。┓(;´_`)┏

【关于缓存的疑问】:比如,这些字段是携带在什么地方的?是浏览器发给服务器的,还是服务器告诉浏览器的?为什么有那么多字段,都要用么?最近下定决心要把这个坎给突破,跟着下面的学习路径,就能把缓存知识的任督二脉打通啦~

(1)面试精选之http缓存:明白这些字段出现的原因,层层改进;

(2)缓存详解:了解缓存的分类;

(3)你应该知道的前端—缓存 +《HTTP权威指南》第7章 缓存:权威知识点

(4)前端工程上如何利用和解决浏览器缓存的?☞ 比如,在项目中遇到GET请求的缓存问题,如何解决呢?—— 添加时间戳

(5)阅读其它好文章加深印象(更多参见文末参考链接)

所有关于缓存资源的问题,都仅仅针对 GET 请求。而对于 POST , DELETE , PUT 这类行为性操作通常不做任何缓存。因此才有 条件GET请求

图片来源:https://github.com/amandakelake/blog/issues/43
)

本文相当于对 《HTTP权威指南》 缓存一章内容的笔记,自己梳理一遍,记得更牢嘛~😜

OK,首先整体预览一下HTTP中主要的字段,其中标红的为跟缓存相关的首部:

HTTP中主要的字段(缓存相关标红)

一、定义

代理的基本原理:在客户端和 Web 服务器之间充当中间人的方式。

Web缓存或代理缓存是一种特殊的HTTP代理服务器,可以将经过代理传送的常用文档复制保存起来。——《HTTP权威指南》

二、作用

缓存服务器就是充当一个中间人的角色,在中转消息的过程中, 缓存服务器还会顺便将页面数据保存下来, 随着缓存数据的积累, 用户访问的数据命中缓存的几率也会提高。

(1)减少了冗余的数据传输:通过缓存副本来应对客户端的响应,可以减少那些流入/流出原始服务器的、被浪费掉的重复流量,从而节省网络费用。

(2)缓解了网络瓶颈:很多网络为本地客户端提供的带宽比为远程服务器提供的带宽要宽,如果客户端从一个快速局域网的缓存中得到了一份副本,那么利用缓存就能够更快地加载页面,从而提高性能——尤其是要传输比较大的文件时。

(3)破坏瞬间拥塞,降低了对原始服务器的要求:分担流量,使服务器可以更快地响应,避免过载的情况出现。

(4)降低了距离时延:每台网络路由器都会增加因特网流量的时延,即使客户端和服务器之间没有太多的路由器,信号传输自身也会造成显著的时延,因此从较远的地方加载页面会更慢一些。

说了这么多,其实就是在3个方面产生了有利的影响:客户端服务器通信链路。客户端下载资源更快了,服务器压力更小了,网络通信流量小更顺畅了~

三、缓存是否命中

缓存命中:用已有的副本为某些到达缓存的请求提供服务。 缓存未命中:一些到达的请求可能由于没有副本可用,而被转发给原始服务器。

新鲜度检测(HTTP再验证):因为原始服务器的内容可能会发生变化,缓存要时不时对其进行检测,看看它们保存的副本是否仍是服务器上最新的副本。验证方式是通过HTTP定义的一些特殊请求来进行,不用从服务器上获取整个对象。

由于网络带宽是珍贵的,所以大部分缓存只有在客户端发起请求,并且副本旧的足以需要检测的时候,才会被副本进行再验证。

再验证方式之一:如果缓存服务器中有缓存时,则当缓存服务器在向Web服务器发起请求的时候,会添加一个 If-Modified-Since 头部字段,以询问Web服务器用户请求的数据是否已经发生变化。(也是最常用的)

服务器收到 GET If-Modified-Since 时会发生的3种情况:

  • 再验证命中(缓慢命中):如果服务器对象未被修改,服务器会向客户端发送一个小的 HTTP 304 Not Modified 响应,并不会发送整个内容。
  • 再验证未命中:如果服务服务器对象已被修改,服务器向客户端发送一条普通的、带有完整内容的 HTTP 200 OK 响应。
  • 对象被删除:如果服务器对象已经被删除了,服务器就回送一个 404 Not Found 响应,缓存也会将其副本删除。

好处:Web 服务器只要查询一下数据的最后更新时间就好了, 比返回页面数据的负担要小一些。 而且返回的响应消息也比较短, 能相应地减少负担。

缓存性能的评估

  • 文档命中率:说明阻止了多少通往外部网络的 Web 事务,提高文档命中率对降低整体时延很有好处。
  • 字节命中率:说明阻止了多少字节传向因特网,提高字节命中率对节省带宽很有利。

客户端判断响应是否来自缓存:使用Date首部或Age首部。将响应中Date首部的值与当前时间进行比较,如果响应中的日期值比较早,客户端通常就可以认为这是一条缓存的响应。

四、缓存的拓扑结构

缓存根据单个用户专用或是数千名用户共享,可以分为私有缓存公有缓存

私有缓存:不需要很大的动力或存储空间,如Web浏览器中内建的私有缓存——大多数浏览器都会将常用文档缓存在你个人电脑的磁盘(from disk)和内存(from memory)中,并且允许用户去配置缓存的大小和各种设置。

下图显示了百度首页资源的加载,在截图的过程中发现,from memory cache 每个加载的时间都为0,而 from disk cache 则需要读磁盘的时间。而浏览器判断资源是从磁盘还是内存加载,看到有文章说跟浏览器的内存使用率以及文档大小有关。

百度首页加载

公有缓存:特殊的共享代理服务器,被称为 缓存代理服务器代理缓存。代理缓存会从本地缓存中提供文档,或者代表用户与服务器进行联系。公有缓存会接受来自多个用户的访问,所以通过它可以更好地减少冗余流量。

在实际中,会实现层次化的缓存,基本思想是:在靠近客户端的地方使用小型廉价缓存,而更高层次中,则逐步采用更大、功能更强的缓存来装载多用户共享的文档。如客户端浏览器自带缓存,从技术上来讲,就是一个三级的缓存层次结构。

有些网络结构会构建复杂的网状缓存,而不是简单的缓存层次结构。

五、缓存的处理步骤

Web缓存的基本工作原理:

(1)接收——缓存从网络中读取抵达的请求报文。

(2)解析——缓存对报文进行解析,提取出URL和各种首部。

(3)查询——缓存查看是否有本地副本可用,如果没用,就获取一份副本(并将其保存在本地)。

(4)新鲜度检测——缓存查看已缓存副本是否足够新鲜,如果不是,就询问服务器是否有任何新鲜。

(5)创建响应——缓存会用新的首部和已缓存的主体来构建一条响应报文。

(6)发送——缓存通过网络将响应发回给客户端。

(7)日志——缓存可选地创建一个日志文件条目来描述这个事务。

缓存在响应的过程中,可能会对首部进行相应的转换,或插入新鲜度信息,但是不应该调整 Date 首部,该首部表示的是原始服务器最初产生这个对象的日期。

六、保持副本的新鲜

HTTP 有一些简单的机制可以在不要求服务器记住有哪些缓存拥有其文档副本的情况下,保持已缓存数据与服务器数据之间充分一致。这些机制包括文档过期(document expiration)服务器再验证(server revalidation)

1、文档过期

文档过期日期就跟商品中的过期日期一样,说明了多长时间内这些内容是新鲜的。在文档过期之前,缓存可以以任意频率使用这些副本,而无需与服务器联系——除非客户端请求中包含有阻止提供已缓存或未验证资源的首部。

过期响应首部:

首部 HTTP版本 描述
Cache-Control:max-age HTTP/1.1 max-age 值定义了文档的最大使用期——从第一次生成文档到文档不再新鲜、无法使用为止,最大的合法生存时间(以秒为单位)

Cache-Control: max-age=484200
Expires HTTP/1.0+ 指定一个绝对的过期日期。如果日期已经过了,就说明文档不再新鲜。

Expires: Fri, 05 Mar 2019, 05:00:00 GMT

Expires 首部和 Cache-Control:max-age 首部所做的事情本质上是一样的,只不过后者使用的是相对日期,而不是绝对日期,所以我们更倾向于使用比较新的 Cache-Control 首部。

2、服务器再验证

文档过期了,并不代表服务器上的资源有改变,只是需要跟服务器核对一下。

  • 如果再验证显示内容 发生了变化,缓存会获取一份新的文档副本,并将其存储在旧文档的位置上,然后将文档发送给客户端。
  • 如果再验证显示内容 没有发生变化,缓存只需要获取新的首部,包括一个新的过期日期,并对缓存中的首部进行更新就行了。

这是一个很棒的系统。缓存并不一定要为每条请求验证文档的有效性——只有在文档过期时它才需要与服务器进行再验证。这样不会提供陈旧的内容,还可以节省服务器的流量,并拥有更好的响应时间。

HTTP 允许缓存向原始服务器发送一个“条件GET”进行再验证,这样,就将 新鲜度检测对象获取 结合成了单个条件GET。向GET请求报文中添加一些特殊的条件首部,就可以发起条件GET。只有条件为真时,Web服务器才会返回对象。

缓存再验证中使用的两个条件首部:

(1)If-Modified-Since:<cached last-modified date> :如果从指定日期文档之后文档被修改过了,就执行请求的方法。与Last-Modified 服务器响应首部配合使用,只有在内容被修改后与已缓存版本有所不同的时候才u去获取内容。 (2)If-None-Match:<tags>:服务器可以为文档提供特殊的标签,而不是将其与最近修改日期相匹配,这些标签就像序列号一样。如果已缓存标签与服务器文档中的标签有所不同,If-None-Match 首部就会执行所请求的方法。

If-Modified-Since 是最常见的缓存再验证首部,但是有些情况下仅使用最后修改日期进行再验证是不够的:

  • 有些文档可能会被周期性地重写(比如,从一个后台进程中写入),但实际包含的数据常常是一样的。尽管内容没有变化,但修改日期会发生变化。
  • 有些文档可能被修改了,但所做修改并不重要,不需要让世界范围内的缓存都重装数据(比如对拼写或注释的修改)。
  • 有些服务器无法准确地判断其页面的最后修改日期。
  • 有些服务器提供的文档会在亚秒间隙发生变化,对这些服务器来说,以一秒为粒度的修改日期可能就不够用了。

为了解决这些问题,HTTP允许用户对被称为实体标签(ETag)的“版本标识符”进行比较,这样,如果实体标签被修改了,缓存就可以用 If-None-Match 条件首部来GET文档的新副本了。

可以在 If-None-Match 首部包含几个实体标签,告诉服务器,缓存中已经存在这些实体标签的副本对象:

If-None-Match: "v2.6"
If-None-Match: "v2.4", "v2.5", "v2.6"

七、控制缓存

服务器可以通过HTTP定义的几种方式来指定在文档过期之前可以将其缓存多长时间。按照优先级递减的顺序,服务器可以:

  • 附加一个 Cache-Control: no-store 首部到响应中去;
  • 附加一个 Cache-Control: no-cache 首部到响应中去;
  • 附加一个 Cache-Control: must-revalidate 首部到响应中去;
  • 附加一个 Expires 日期首部到响应中去;
  • 不附加过期信息,让缓存确定自己的过期日期。

☞ 【标识为 no-store 的响应】:会禁止缓存对响应的复制。

☞ 【标识为 no-cache 的响应】:实际上是可以存储在本地缓存区中的,只是在于原始服务器进行新鲜度再验证之前,缓存不能将其提供给客户端使用。类似于叫 do-not-serve-from-cache-without-revalidation,哈哈。

☞ 【Pragma: no-cache 首部】:除了与只理解该首部字段的 HTTP/1.0 应用程序进行交互时,HTTP 1.1 应用程序都应该使用 Cache-Control: no-cache

☞ 【max-age 响应首部】:表示从服务器将文档传来之时起,可以认为此文档处于新鲜状态的描述。还有一个 s-maxage 首部,行为与 max-age 类似,但仅适用于共享(公有)缓存。

☞ 【Expires 响应首部】:不推荐使用,它指定的是实际的过期日期而不是秒数,由于很多服务器的时钟都不同步,或者不准确,所以最好还是用剩余秒数,而不是绝对时间来表示过期时间。

☞ 【must-revalidate 响应首部】:告诉缓存,在事先没有跟原始服务器进行再验证的情况下,不能提供这个对象的陈旧副本。如果在进行 must-revalidate 新鲜度检查时,原始服务器不可用,缓存就必须返回一条 504 Gateway Timeout 错误。

除了这些常见的,还有试探性过期客户端的新鲜度限制,有兴趣的可以自行了解下~

Cache-Control最佳策略

【注意事项】:文档过期系统并不是一个完美的系统。如果发布者不小心分配了一个很久之后的过期日期,在文档过期之前,她要对文档做的任何修改都不一定能显示在所有缓存中。因此,很多发布者都不会使用很长的过期日期。而且,很多发布者甚至都不使用过期日期,这样缓存就很难确定文档会在多长时间内保存新鲜了。

所以,如果使用了缓存,但是想更新或废弃缓存的响应,该怎么办?如下图,可以自己定义“缓存层次结构”,这样不但可以控制每个响应的缓存时间,还可以控制访问者看到新版本的速度。前端发布版本就是这样做的。

定义每个资源的缓存策略

☄ 知识点补充

启发式缓存:如果什么缓存策略都没设置,浏览器会采用一个启发式的算法,通常会根据响应头中的2个时间字段 Date 减去 Last-Modified 值的 10% 作为缓存时间。

图片来源:https://juejin.cn/post/6844903599537930253

出现304的情况:该状态码表示发送附带条件的请求时(如GET方法的请求报文中包含If-Match,If-Modified-Since,If-Non-Match,If-Range,If-Unmodified-Since中任一首部),服务器端允许请求访问资源, 但未满足条件的情况。

304状态码返回时,不包含任何响应的主体部分。所以,304虽然被划分在 3XX 类别中,但是和重定向是没有关系滴。

顺便回顾下HTTP各个状态码含义。

HTTP状态码含义

【☕️鸡汤】:真的,学习的效率不在于看了多少东西,很多东西反反复复讲的都是类似的内容,之前看的好些文章,印象中就是些“强缓存”、“协商缓存”、哪两个字段配对使用,但是感觉这些其实都只是缓存这个知识点的结果,我们在学习的时候,想要理解结果,还得从源头着手,知道缓存是什么,为什么要有缓存,缓存的拓扑结构等等。我原先一直不理解,可能我真的网络方面的知识确实不过关,还是得多亏《网络是怎样连接的》这本书,让我对整个网络有了一个深刻的印象,才能对后面的知识有更好的吸收。(在此劝诫在校的宝宝们,一定要好好学习鸭ヾ(o・ω・)ノ)

➹ 参考