缓存
浏览器缓存保存着用户通过HTTP
请求获取的所有资源,下一次请求时可以直接获取资源,避免向服务器发出多余的请求。
通俗的说,就是在你访问过一次某个网站之后,这个站点的文字、图片等所有资源都被下载到本地了,下次再访问该网站时判断是否满足缓存条件,如果满足就不用再花费时间去等待资源的获取了。
分类
浏览器缓存分为:
- 强制缓存
- 协商缓存
当命中强制缓存时,就直接返回存放在本地的资源;如果没有命中,就会发请求到服务器看是否命中协商缓存。
流程
强制缓存
客户端第一次向服务器发送请求,不存在缓存的结果和标示,直接向服务器发送请求。
服务器返回数据,并将缓存规则放入到响应头中,一起返回给客户端。缓存规则存放在Expire
(HTTP1.0)和Cache-Control
(HTTP1.1)中。
Expire
expires: Tue, 13 Apr 2021 08:27:47 GMT
expires
表示的是缓存过期的时间,这是个绝对时间。GMT
表示的是格林威治时间,和北京时间相差8小时。
客户端首次发送请求到服务端,服务端返回的响应数据里会包含expires
字段,当再次发送请求时,会先查看expires
这个字段的时间是否过期,如果没有过期就直接在本地缓存里取数据,如果过期了,就发送请求到服务器,获取最新数据再更新缓存里的数据。
缺陷:由于expires是绝对时间,如果客户端自己改了本地时间,这个缓存就失效了。。。
Cache-Control
为了解决上面的问题,HTTP1.1
中的响应头里新增了字段Cache-Control
,Cache-Control
字段可以设置值为max-age
,该字段表示资源缓存的最大有效时间,这是一个相对时间。
Cache-Control:max-age=200
这里代表最大缓存时间是200s
。这里的计时开始是在第一次被请求时服务器记录的Request_time
(请求时间)。
当客户端再发送请求时,浏览器会判断是否在这个范围内,如果在,就直接取缓存里的数据,如果不再才会再发送请求到服务器。
Cache-Control
字段还包含以下几个值可以选择:
- no-cache:需要进行协商缓存,发送请求到服务器确认是否使用缓存。
- no-store:禁止使用缓存,每一次都要重新请求数据。
- public:默认设置。可以进行代理器缓存。
- private:不能被多用户共享。不可以进行代理器缓存。
一般会同时设置expires
和Cache-Control
,如果同时设置了,Cache-Control
的优先级会更高。
协商缓存
当强制缓存没有命中,浏览器会发请求到服务器,服务器根据请求头中的部分信息来判断是否命中缓存。如果命中,则返回304
,告诉浏览器资源未更新,可使用本地的缓存。
流程
如果未命中强制缓存,浏览器发请求到服务器,服务器在返回数据同时,会在响应数据头部返回Last-Modified
字段和Etag
字段。当再次发送请求到服务器时,请求头会自动带上If-Modified-Since
字段,这个字段值就是Last-Modified
返回值。服务器拿到请求后会比对这个值,如果没有变化就返回304
状态码,取本地缓存中数据,如果有变化就返回200
,并把最新数据和最新的Last-Modified
的值返回,并把数据放入到缓存中。
但是Last-Modified
有缺陷:
- 因为时间是
s
级的,如果在短时间修改,Last-Modified
并不会变化。 - 如果在一个周期内多次改变,最终又变回原来的值,我们应该视为内容未变,但是
Last-Modified
就显示变化了。
为了解决这个问题,Etag
就出现了。Etag
是内容的hash
值,第一次请求,服务器会返回Etag
在头部,再次请求,请求头带上If-no-match
,它的值就是Etag
。服务器会对值进行对比,如果没有变化,就直接返回304
。
Last-Modified
和Etag
是可以同时设置的,服务器会优先校验Etag
,如果Etag
相等就会继续比对Last-Modified
,最后才会决定是否返回304
。
缓存位置
缓存的内容放到内存或磁盘中,对应的标示是from disk cache(磁盘缓存)
和from memory cache(内存缓存)
以及资源大小数值。
| 状态 | 类型 | 说明
| --- | --- |
| 200 | form memory cache | 不请求网络资源,资源在内存当中,一般脚本、字体、图片会存在内存当中
| 200 | form disk ceche | 不请求网络资源,在磁盘当中,一般非脚本会存在内存当中,如css等
| 200 | 资源大小数值 | 从服务器下载最新资源
| 304 | 报文大小 | 请求服务端发现资源没有更新,使用本地资源
关闭标签页,再打开时,缓存是在disk ceche
中获取的。
F5
刷新时,缓存是再memory cache
获取的。
内容来源:
HTTP版本区别
HTTP1.0
1. 连接
HTTP1.0
是串行链接,每一次通信后都要断开TCP
连接,当下一次请求时要重新创建一个TCP
连接。
2. 缓存
只有Expires
字段,只有强制缓存。
HTTP1.1
1. 连接
HTTP1.1
默认为持久连接。既TCP
只需要连接一次,后面请求都会复用这个连接。这样就减少了每次连接带来的不必要的通信时间的浪费。
只有在服务端和客户端头部的Connection
设置为Keep-Alive
时才开启了持久连接,如果是close
就是关闭状态。
但是这里的通信实质上还是串联的,再次请求必须等到上次响应返回后才能发起,如果上次的请求还没返回响应内容,下次请求就只能等着(就是常说的HTTP线头阻塞)。
如何解决呢:
A. 并发连接
对于一个域名允许分配多个长连接,那么相当于增加了任务队列,不至于一个队伍的任务阻塞其它所有任务。在RFC2616规定过客户端最多并发 2 个连接,不过事实上在现在的浏览器标准中,这个上限要多很多,Chrome 中是 6 个。
但其实,即使是提高了并发连接,还是不能满足人们对性能的需求。
B.域名分片
一个域名不是可以并发 6 个长连接吗?那我就多分几个域名。
比如content1.sanyuan.com
、content2.sanyuan.com
。
这样一个sanyuan.com
域名下可以分出非常多的二级域名,而它们都指向同样的一台服务器,能够并发的长连接数更多了,事实上也更好地解决了队头阻塞的问题。
2.缓存
增加了Cache-Control
,Last-Modified
,If-Modified-Since
,Etag
,If-no-match
字段,扩展了缓存的功能。
3.Host
请求消息和响应消息都必须包含Host
头部,以区分同一个物理主机中的不同虚拟主机的域名,可以达到负载均衡目的。
4.Range
允许范围请求,即在请求头中加入Range
头部。
HTTP2.0
1.连接
多路复用。就是在一个TCP
连接中可以存在多条流,也就是可以发送多个请求,服务端则可以通过帧中的标识知道该帧属于哪个流(即请求),通过重新排序还原请求。这样便从HTTP
协议本身解决了线头阻塞问题。
首先,HTTP/2 认为明文传输对机器而言太麻烦了,不方便计算机的解析,因为对于文本而言会有多义性的字符,比如回车换行到底是内容还是分隔符,在内部需要用到状态机去识别,效率比较低。于是 HTTP/2 干脆把报文全部换成二进制格式,全部传输01串,方便了机器的解析。
原来Headers + Body的报文格式如今被拆分成了一个个二进制的帧,用Headers帧存放头部字段,Data帧存放请求体数据。分帧之后,服务器看到的不再是一个个完整的 HTTP 请求报文,而是一堆乱序的二进制帧。这些二进制帧不存在先后关系,因此也就不会排队等待,也就没有了 HTTP 的队头阻塞问题。 通信双方都可以给对方发送二进制帧,这种二进制帧的双向传输的序列,也叫做流(Stream)。HTTP/2 用流来在一个 TCP 连接上来进行多个数据帧的通信,这就是多路复用的概念。
可能你会有一个疑问,既然是乱序首发,那最后如何来处理这些乱序的数据帧呢?
首先要声明的是,所谓的乱序,指的是不同 ID 的 Stream 是乱序的,但同一个 Stream ID 的帧一定是按顺序传输的。二进制帧到达后对方会将 Stream ID 相同的二进制帧组装成完整的请求报文和响应报文。当然,在二进制帧当中还有其他的一些字段,实现了优先级和流量控制等功能,我们放到下一节再来介绍。
2.头部压缩
HTTP/2 针对头部字段,也采用了对应的压缩算法——HPACK,对请求头进行压缩。
3.服务器推送
在 HTTP/2 当中,服务器已经不再是完全被动地接收请求,响应请求,它也能新建 stream 来给客户端发送消息,当 TCP 连接建立之后,比如浏览器请求一个 HTML 文件,服务器就可以在返回 HTML 的基础上,将 HTML 中引用到的其他资源文件一起返回给客户端,减少客户端的等待。
HTTP3.0
HTTP/2.0 使用了多路复用,一般来说同一域名下只需要使用一个 TCP 连接。但当这个连接中出现了丢包的情况,那就会导致整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了。反而对于 HTTP/1.0 来说,可以开启多个 TCP 连接,出现丢包反倒只会影响其中一个连接,剩余的 TCP 连接还可以正常传输数据。
出现包阻塞的原因是因为底层TCP协议导致的问题,但是修改TCP协议是不现实的问题,就像typeof null === 'object'
一样,修改这个问题会导致出现更多的问题。既然不能修改你,那就另起一个协议取代你。Google 基于 UDP 协议推出了一个的 QUIC 协议,并且使用在了 HTTP/3 上。
QUIC 基于 UDP,但是UDP本身存在不稳定性等诸多问题,所以QUIC在UDP的基础上新增了很多功能,比如多路复用、0-RTT、使用 TLS1.3 加密、流量控制、有序交付、重传等等功能。优点诸多,参考这里:
- 避免包阻塞: 多个流的数据包在TCP连接上传输时,若一个流中的数据包传输出现问题,TCP需要等待该包重传后,才能继续传输其它流的数据包。但在基于UDP的QUIC协议中,不同的流之间的数据传输真正实现了相互独立互不干扰,某个流的数据包在出问题需要重传时,并不会对其他流的数据包传输产生影响。
- 快速重启会话: 普通基于tcp的连接,是基于两端的ip和端口和协议来建立的。在网络切换场景,例如手机端切换了无线网,使用4G网络,会改变本身的ip,这就导致tcp连接必须重新创建。而QUIC协议使用特有的UUID来标记每一次连接,在网络环境发生变化的时候,只要UUID不变,就能不需要握手,继续传输数据。
内容来源: