缓存的基本原理
所谓 客户端缓存 是将某一次的响应结果保存在客户端(比如浏览器)中,而后续的请求仅需要从缓存中读取即可
- 极大的降低了服务器的处理压力,大大提升了网站的性能
- 减少了冗余的数据传输
- 加快了客户端加载网页的速度
-
客户端缓存的原理如下:
-
这里就涉及到一个缓存策略的问题,这些问题包括:
- 哪些资源需要加入到缓存,哪些不需要?
- 缓存的时间是多久呢?
- 如果服务器的资源有改动,客户端如何更新缓存呢?
- 如果缓存过期了,可是服务器上的资源并没有发生变动,又该如何处理呢?
- …
-
缓存的资源去哪里了?
memory cache disk cache 相同点 只能存储一些派生类资源文件 只能存储一些派生类资源文件 不同点 退出进程时数据会被清除 退出进程时数据不会被清除 存储资源 一般脚本、字体、图片会存在内存当中 一般非脚本会存在内存当中,如 css 等
缓存的分类
强缓存
:强缓存指的是在缓存时间内不会向服务器发起请求,只有过期之后才会向服务器发起请求,在浏览器中,强缓存分为 Expires (http1.0 规范)、cache-control (http1.1 规范) 两种;
协商缓存
:协商缓存都会向服务器发送请求,判断缓存数据是否过期,过期的话会返回新的内容,没有过期则使用本地的缓存数据,对于协商缓存主要利用两个字段:Last-Modify、Etag;
来自服务器的缓存指令
-
当客户端发出一个
GET /index.js
请求到服务器,服务器在 响应头 中加入了以下内容,通过响应头传递给客户端了:# 把这个资源缓存起来,缓存时间是3600秒(1小时) Cache-Control: max-age=3600 # 资源的编号是 W/"121-171ca289ebf",根据内容生成 ETag ETag: W/"121-171ca289ebf" # 响应这个资源的服务器时间(格林威治时间) Date: Thu, 30 Apr 2020 12:39:56 GMT # 上一次修改时间(格林威治时间) Last-Modified: Thu, 30 Apr 2020 08:16:31 GMT
-
如果客户端是其他应用程序,根本不会缓存任何东西,但若客户端是一个浏览器会做如下记录:
- 浏览器把这次请求得到的 响应体 缓存到本地文件中
- 浏览器标记这次请求的 请求方法 和 请求路径
- 浏览器标记这次 缓存的时间 是 3600 秒
- 浏览器记录 服务器的响应时间 是格林威治时间 2020-04-30 12:39:56
- 浏览器记录 服务器给予的资源编号 是 W/“121-171ca289ebf”
- 浏览器记录 资源的上一次修改时间 是格林威治时间 2020-04-30 08:16:31
-
这一次的记录非常重要,它为以后浏览器要不要去请求服务器提供了各种依据:
来自客户端的缓存指令
-
当客户端准备再次请求
GET /index.js
时,会到缓存中去寻找是否有缓存的资源,寻找的过程如下:- 缓存中是否有匹配的请求方法和路径?
- 如果有,该缓存资源是否还有效呢?
-
验证是否有匹配的缓存:只需要验证当前的请求方法
GET
和当前的请求路径/index.js
是否有对应的缓存存在即可,如果没有,就直接请求服务器,就和第一次请求服务器时一样;
-
验证缓存是否有效:就是把
max-age + Date
得到一个过期时间,看看这个过期时间是否大于当前时间,如果是,则表示缓存还没有过期,仍然有效,如果不是,则表示缓存失效;
缓存有效
当浏览器发现缓存有效时,完全不会请求服务器,直接使用缓存即可得到结果;
此时,如果断开网络,会发现资源仍然可用,这种情况会极大的降低服务器压力,但当服务器更改了资源后,浏览器是不知道的,只要缓存有效,它就会直接使用缓存;
缓存无效
-
当浏览器发现缓存已经过期,它 并不会简单的把缓存删除,而是抱着一丝希望,想问问服务器,我这个缓存还能继续使用吗?
-
于是,浏览器向服务器发出了一个 带缓存的请求,加入了以下的请求头:
# 这个资源的上一次修改时间是格林威治时间`2020-04-30 08:16:31`,请问这个资源在这个时间之后有发生变动吗? If-Modified-Since: Thu, 30 Apr 2020 08:16:31 GMT # 这个资源的上一次编号是 W/"121-171ca289ebf",请问这个资源的编号发生变动了吗? If-None-Match: W/"121-171ca289ebf"
-
之所以要发两个信息,是为了兼容不同的服务器,因为有些服务器只认
If-Modified-Since
,有些服务器只认If-None-Match
,有些服务器两个都认;- 目前的很多服务器,只要发现
If-None-Match
存在,就不会去看If-Modified-Since
If-Modified-Since
是http1.0
版本的规范,If-None-Match
是http1.1
的规范
- 目前的很多服务器,只要发现
-
此时,问题又抛给了服务器,服务器可能会产生两个情况:
- 缓存已经失效:服务器再次给予一个正常的响应(响应码
200
带响应体),响应头带上新的缓存指令,这就回到了上面——来自服务器的缓存指令,客户端就会重新缓存新的内容; - 缓存仍然有效:它可以通过一种极其简单的方式告诉客户端:(
304 Not Modified
、无响应体),响应头带上新的缓存指令,见上面——来自服务器的缓存指令,就相当于告诉客户端:「你的缓存资源仍然可用,我给你一个新的缓存时间,你那边更新一下就可以了」
- 缓存已经失效:服务器再次给予一个正常的响应(响应码
细节
Cache-Control
在上述的讲解中,
Cache-Control
是服务器向客户端响应的一个消息头,它提供了一个max-age
用于指定缓存时间,实际上Cache-Control
还可以设置下面一个或多个值:
public
:指示服务器资源是公开的。比如有一个页面资源,所有人看到的都是一样的。这个值对于浏览器而言没有什么意义,但可能在某些场景可能有用。本着「我告知,你随意」的原则,http 协议中很多时候都是客户端或服务器告诉另一端详细的信息,至于另一端用不用,完全看它自己;private
:指示服务器资源是私有的。比如有一个页面资源,每个用户看到的都不一样。这个值对于浏览器而言没有什么意义,但可能在某些场景可能有用。本着「我告知,你随意」的原则,http 协议中很多时候都是客户端或服务器告诉另一端详细的信息,至于另一端用不用,完全看它自己;no-cache
:告知客户端,你可以缓存这个资源,但是不要 直接 使用它。当你缓存之后,后续的每一次请求都需要附带缓存指令,让服务器告诉你这个资源有没有过期;见:「来自客户端的缓存指令 - 缓存无效」no-store
:告知客户端,不要对这个资源做任何的缓存,之后的每一次请求都按照正常的普通请求进行。若设置了这个值,浏览器将不会对该资源做出任何的缓存处理;max-age
:不再赘述;比如,
Cache-Control: public, max-age=3600
表示这是一个公开资源,请缓存 1 个小时;
Expire
-
在
http1.0
版本中通过Expire
响应头来指定过期时间点的,是一个绝对的时间 (当前时间+缓存时间),例如:Expires: Thu, 30 Apr 2020 23:38:38 GMT
-
在响应消息头中,设置这个字段之后,就可以告诉浏览器,在未过期之前不需要再次请求,但是,这个字段设置时有两个缺点:
- 由于是绝对时间,用户可能会将客户端本地的时间进行修改,而导致浏览器判断缓存失效,重新请求该资源;此外,即使不考虑自行修改的因素,时差或者误差等因素也可能造成客户端与服务端的时间不一致,致使缓存失效;
- 写法太复杂了;表示时间的字符串多个空格,少个字母,都会导致变为非法属性从而设置失效;
-
到了
http1.1
版本通过Cache-Control
的max-age
来记录了;
Last-Modified、If-Modified-Since
-
服务器通过 Last-Modified 字段告知客户端,资源最后一次被修改的时间,例如:
Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
-
浏览器将这个值和内容一起记录在缓存数据库中;
-
下一次请求相同资源时时,浏览器从自己的缓存中找出 不确定是否过期的 缓存;因此在请求头中将上次的 Last-Modified 的值写入到请求头的 If-Modified-Since 字段;
-
服务器会将 If-Modified-Since 的值与 Last-Modified 字段进行对比;如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据;
-
但是他还是有一定缺陷的:
- 如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒;
- 如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用;
-
因此在 HTTP/1.1 出现了 ETag 和 If-None-Match
Etag、If-None-Match
-
为了解决上述问题,出现了一组新的字段 Etag 和 If-None-Match;
- Etag 存储的是文件的特殊标识(一般都是一个 Hash 值),服务器存储着文件的 Etag 字段;
- 之后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新时间改变成了 Etag 字段和它所表示的文件 hash ,把 If-Modified-Since 变成了 If-None-Match;
-
浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的 Etag 值放到请求头里的 If-None-Match 里,服务器只需要比较客户端传来的 If-None-Match 跟自己服务器上该资源的 ETag 是否一致,就能很好地判断资源相对客户端而言是否被修改过了;
-
如果服务器发现 ETag 匹配不上,那么直接以常规 GET 200 回包形式将新的资源(当然也包括了新的 ETag)发给客户端;如果 ETag 是一致的,则直接返回 304 告诉客户端直接使用本地缓存即可;
-
两者之间的简单对比:
- 首先在精确度上 Etag 要优于 Last-Modified:Last-Modified 的时间单位是秒,如果某个文件在 1 秒内改变了多次,那么 Last-Modified 其实并没有体现出来修改,但是 Etag 是一个 Hash 值,每次都会改变从而确保了精度;
- 第二在性能上 Etag 要逊于 Last-Modified ,毕竟 Last-Modified 只需要记录时间,而 Etag 需要服务器通过算法来计算出一个 Hash 值;
- 第三在优先级上服务器校验优先考虑 Etag ,也就是说 Etag 的优先级高于 Last-Modified;
记录缓存有效期
-
浏览器会按照服务器响应头的要求,自动记录缓存到本地文件,并设置各种相关信息,在这些信息中,有效期 尤为关键,它决定了这个缓存可以使用多久,浏览器会根据服务器不同的响应情况,设置不同的有效期;
-
具体的有效期设置,按照下面的流程进行:
-
例如,当
max-age = 0
时,缓存立即过期,虽然立即过期,但缓存仍然被记录下来,后续的请求通过缓存指令发送到服务器,来确认资源是否被更改;因此,Cache-Control: max-age=0
类似于Cache-Control: no-cache
Pragma
-
这是
http1.0
版本的消息头,当该消息头出现在请求中时,是向服务器表达:不要考虑任何缓存,给我一个正常的结果; -
在
http1.1
版本中,可以在 请求头 中加入Cache-Control: no-cache
实现同样的含义; -
在 Chrome 浏览器中调试时,如果勾选了 Disable cache ,则发送的请求中会附带该信息
Vary
-
有的时候,是否有缓存,不仅仅是判断请求方法和请求路径是否匹配,可能还要判断头部信息是否匹配,此时就可以使用
Vary
字段来指定要区分的消息头,比如,当使用GET /personal.html
请求服务器时,请求头中cookie
的值不一样,得到的页面也不一样; -
如果还按照之前的做法,仅仅匹配请求方法和请求路径,如果
cookie
变动,可能得到的仍然是之前的页面,正确的做法如下:
使用版本号或 hash
如果使用过 vue 或其他基于 webpack 搭建的工程,会发现打包的结果中很多文件名类似于这样 app.68297cd8.css,文件的中间部分使用了 hash 值,这样做的好处是,可以让客户端大胆的、长时间的缓存该文件,减轻服务器的压力,当文件改动后,它的文件 hash 值也会随之而变,这样一来,客户端要请求新的文件时,就会发现路径从 /app.68297cd8.css 变成了 app.446fccb8.css,由于之前的缓存路径无法匹配到,因此就会发送新的请求来获取新资源了;
而在古老的年代,没有构建工具出现时,使用的办法是在资源路径后面加入版本号来获取新版本的文件,比如:
- 页面中引入了一个 css 资源 app.css,它可能的引入方式是:<link href=“/app.css?v=1.0.0”>,这样一来,缓存的路径是 /app.css?v=1.0.0;
- 当服务器的版本发生变化时,可以给予新的版本号,让 html 中的路径发生变动 <link href=“/app.css?v=1.0.1”>,由于新的路径无法命中缓存,于是浏览器就会发送新的普通请求来获取这个资源;
总结
-
服务器无法知道客户端到底有没有像浏览器那样缓存文件,它只管根据请求的情况来决定如何响应,很多后端语言搭建的服务器都会自带自己的默认缓存规则,当然也支持不同程度的修改;
-
浏览器在发出请求时会判断要不要使用缓存
-
当收到服务器响应时,会自动根据缓存指令进行处理
面试题
为什么用多个域名存储网站资源更有效?
主要原因是浏览器对同一个域下的 TCP 连接数是有限制的,这样就导致某个网页如果外部资源多了,比如图片很多的网页,在解析页面时,由于 TCP 连接数受限,就无法同时发起多个下载连接,无法充分利用带宽资源;
因此,可以把静态资源放到多个域名下,这样就绕开了连接数的限制,做到了并发下载;
计算机网络🛜 文件下载
上一篇