从浏览器如何下载资源来浅析HTTP2的新特性

一、浏览器是如何下载资源的

1、同步的JS阻塞下载

  HTML 在解析的过程中遇到同步的 script 会卡住 DOM 解析,这个知识点我相信大家应该都知道。

<!DOCTYPE html>
<html lang="en">
<body>
    <script>console.log(1)</script>
    <div>1</div>
</body>
</html>

  上述代码中,HTML 遇到 <script>console.log(1)</script> 代码就会停止 DOM 解析了。当然现代浏览器不是说就这样停住啥也不干了,会有个 preload scanner 去扫描底下的文件,然后根据文件类型去制定优先级(这个下面再说)。

  上面举得例子是内联代码,如果是一个 JS 文件的话,那么就得等这个 JS 文件下完以后执行代码才行。

  但是一般我们文件结构不会那么单一,CSS 文件也肯定是有的。因为 JS 不仅能动 DOM 也能改 style,所以如果遇到 script 之前还有别的 CSS 文件的话,浏览器还得等 CSS 文件下完以后再去执行 JS。

  所以说不只是 JS 会阻塞 DOM 解析,CSS 也会。当然我想现在应该很少再有人直接写同步的 script 了吧。

2、文件同时遇到,那会同时开始下载嘛?

  举个例子,在可以同时下载多个文件的情况下,浏览器在 CSS 文件中解析到了 img、font、background-image 资源,那么们会同时开始下载嘛?

  答案是不会!这里只有 img 会最先开始下载,其它两块内容都得等到 layout 以后才会开始下载。

  那么到底文件的优先级是如何的?

  CSS 和字体资源的优先级的最高的,JS 文件有高有低(这和类型有关),图片、svg 的优先级为低。这部分的优先级是指某个时间点上的优先级,而不是适应于整个周期。

3、网络协议才是核心

  浏览器要下载资源,TCP 协议肯定得用上,但是 TCP 协议天生就是一个慢的东西。先得握手,然后开始算法,就和我们用迅雷下载东西一样,带宽很大没用,速度都是得爬升上去的。如果再加个 HTTPS 的话,还得再多个 TLS 的握手。

  然后说回 HTTP1.1 协议。Chrome 只支持同时并发 6 个请求,所以在之前的协议中一堆请求都得 block 住等之前的下载完成。

  当然现在不同了,HTTP2 的概念基本大家都知道了,没见过猪跑但吃过猪肉,对于这块的知识点聊上几句没啥问题。什么多路复用,header 合并、二进制帧啥的。

  得利于 HTTP2 协议中的多路复用,我们不再被浏览器的六个并发所限制,所有同一个 domain 上的请求都能跑在复用的六个网络通道上,下载耗时瞬间减少了几个量级。

  但是大家有没有考虑过这个 HTTP2 到底是如何对待这些资源的?如何分配它们的优先级?依赖?带宽或者别的?

  比如说我现在需要下载 CSS、JS、图片和字体这些文件。举个例子在这些文件中我希望 CSS 文件能最先开始下载,并且能分配到的带宽多点;JS 文件呢可以等 CSS 文件下完再去下;其他文件呢优先级比 CSS 文件低点,但是也能够享受到带宽去进行下载。

  这个需求我们可以通过二进制帧去实现。HTTP2 协议共存在十个二进制帧,其中你可以通过 HEADERS 帧分配新的优先级,也可以通过 PRIORITY 帧更改优先级。

  对于流的优先级,我们可以通过两个方式来实现调整:

  • 当我们调整流的依赖时,就可以实现让 JS 文件等待 CSS 文件下载完毕以后再开始
  • 当我们调整流的 weight 时,就可以实现带宽的分配,让某些资源能更快地进行下载

  第一排的文件开始一起下载,weight 相加为 20,那么通过计算可得 CSS 文件享有带宽的一半,其他各占有 1 / 4,JS 文件得等 CSS 完成以后才开始。

  那么该如何设置这些东西呢?当然得服务端支持啦,一般都指 CDN 服务商了。

二、HTTP/2 特性

  HTTP/2 是 HTTP 协议自 1999 年 HTTP 1.1 发布后的首个更新,主要基于 SPDY 协议。由互联网工程任务组(IETF)的 Hypertext Transfer Protocol Bis(httpbis)工作小组进行开发。该组织于2014年12月将HTTP/2标准提议递交至IESG进行讨论,于2015年2月17日被批准。HTTP/2标准于2015年5月以RFC 7540正式发表。

  那 HTTP/2 到底有哪些具体变化呢?

1、二进制分帧

  与HTTP/1.x采用ASCII码明文传输的方式不同,HTTP/2在服务器和客户端之间采用传输效率更高的二进制的形式。

  值得一提的是,HTTP/2中报文二进制帧被分为头部帧(Header Frame)和数据帧(Data Frame),这主要是因为HTTP/2为头部字段单独提供了一种压缩方式,可以使网络中传输的头部数据降低到最小。

  帧:HTTP/2 数据通信的最小单位消息:指 HTTP/2 中逻辑上的 HTTP 消息。例如请求和响应等,消息由一个或多个帧组成。

  流:存在于连接中的一个虚拟通道。流可以承载双向消息,每个流都有一个唯一的整数ID。

  HTTP/2 采用二进制格式传输数据,而非 HTTP 1.x 的文本格式,二进制协议解析起来更高效。 HTTP / 1 的请求和响应报文,都是由起始行,首部和实体正文(可选)组成,各部分之间以文本换行符分隔。HTTP/2 将请求和响应数据分割为更小的帧,并且它们采用二进制编码。

  HTTP/2 中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装。

2、多路复用

  在HTTP/2中,所有数据的传输可以分为连接(Connection)、流(Stream)和帧(Frame)三个层次。

  与HTTP/1.1中采用的并行连接不同,HTTP/2在客户端和服务器的整个会话都会复用一条连接。

  在这个连接中一组完整地请求、响应被称为流。

  流中的每段数据(头部、数据段)被称为帧。

  帧是HTTP/2中通信的最小单位,其中包含了其属于那条流的“身份”信息。

  在HTTP/1.x中,通常采用维持多个连接的方式使请求和响应并行发送,因为计算和存储资源的限制,无法通过无限开启新连接(通常上限为六个)的方式彻底解决头阻塞问题。

  在HTTP/2中,所有的数据通信均在一个连接中完成。每个数据帧在发送端准备完成后即可发送,无需等待前一个请求的响应。

  数据接收端则在接到这些乱序发送的数据帧后,再根据每个帧携带的“身份”,对这些数据进行重新组装,以获得完整地请求或响应数据。

  HTTP/2中采用的多路复用技术彻底解决了头阻塞问题;重复利用了每一组请求、响应之间的连接的网络资源,在降低资源开销的同时,降低了数据传输延时。

  多路复用代替原来的序列和阻塞机制,所有请求都是通过一个 TCP连接并发完成。

  HTTP 1.x 中,如果想并发多个请求,必须使用多个 TCP 链接,且浏览器为了控制资源,还会对单个域名有 6-8个的TCP链接请求限制,如下图,红色圈出来的请求就因域名链接数已超过限制,而被挂起等待了一段时间:

  在 HTTP/2 中,有了二进制分帧之后,HTTP2 不再依赖 TCP 链接去实现多流并行了,在 HTTP/2中:

(1)同域名下所有通信都在单个连接上完成。

(2)单个连接可以承载任意数量的双向数据流。

(3)数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装。

  这一特性,使性能有了极大提升:

(1)同个域名只需要占用一个 TCP 连接,消除了因多个 TCP 连接而带来的延时和内存消耗。

(2)单个连接上可以并行交错的请求和响应,之间互不干扰。

(3)在HTTP/2中,每个请求都可以带一个 31bit 的优先值,0表示最高优先级, 数值越大优先级越低。有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。

3、服务器推送

  服务端可以在发送页面HTML时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。例如服务端可以主动把JS和CSS文件推送给客户端,而不需要客户端解析HTML时再发送这些请求。

  服务端可以主动推送,客户端也有权利选择是否接收。如果服务端推送的资源已经被浏览器缓存过,浏览器可以通过发送RST_STREAM帧来拒收。

  主动推送也遵守同源策略,服务器不会随便推送第三方资源给客户端。

  可以看这篇了解:了解更多 Server Push 特性

4、头部压缩

  HTTP 1.1请求的大小变得越来越大,有时甚至会大于TCP窗口的初始大小,因为它们需要等待带着ACK的响应回来以后才能继续被发送。

  HTTP/2对消息头采用HPACK(专为http/2头部设计的压缩格式)进行压缩传输,能够节省消息头占用的网络的流量

  而HTTP/1.x每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。

  HTTP每一次通信都会携带一组头部,用于描述这次通信的的资源、浏览器属性、cookie等,例如

  HTTP/1.x会在请求和响应中中重复地携带不常改变的、冗长的头部数据,给网络带来不必要地负担,为了减少这块的资源消耗并提升性能, HTTP/2对这些首部采取了压缩策略

  HTTP/2除了将ASCII明文转化为二进制帧以提高网络传输的有效性外,还提出了一种专门针对报文头部数据进行压缩的方案——Hpack。

  其主要思想是,通过重用当前连接中之前发送请求中的头部,仅发送新加或更改的头部信息,以降低报文中头部字段大小。

  头部压缩的过程如下图所示:

(1)HTTP/2在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;

  连接会维持一个查找表,该查找表中给出了62个常用字段及其默认值(来自对多个常用网站中请求的统计)。

(2)首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新。

(3)每个新的首部键-值对要么被追加到当前表的末尾,要么替换表中之前的值。在第一次请求时,会首先查看查找表中对应字段的值是否为本次请求携带的值,如果不是则更改这个查找表,同时增加查找表中没有的字段。在查找表中将所有头部添加完成后才根据查找表对头部进行压缩并发送请求。

  在接下来的每次请求中(当前连接),报文中的压缩数据仅包含查找表中被修改的字段。鉴于一个连接中很多请求中大部分头部字段携带的值都是不常变化的,此举可以大大降低头部传输所需的网络资源。

  例如:下图中的两个请求, 请求一发送了所有的头部字段,第二个请求则只需要发送差异数据,这样可以减少冗余数据,降低开销。

5、流的优先级

  在HTTP/2中,请求和响应是可以乱序传输的,因此我们需要一个机制可以确保哪些被其他响应数据所依赖的或者关键资源被优先传输,以使网页的呈现和使用具有最好的体验。

  HTTP/2中在流的层面,采用了“优先级树”的形式确保响应数据能够按照依赖关系和优先级顺序来传输。

  “优先级树”可以表示成如下图所示的样子。其中子节点所表示的流响应依赖于父节点流,因此父节点流应该被优先传输。在兄弟节点中,被分配权重较大的点应该被分配更多的网络资源,被优先传输。 如下图中(3)所示的“优先级树”中,A,B依赖于C,C依赖于D,因此D应该被优先传输,D传输完后才应该传输C,同理C传输完成后才应该传输A,B,A,B的数据在传输过程中所占用的网络资源应遵循3:1的关系。

三、升级HTTP2

  根据不同的业务类型和复杂程度,可以选择不同的升级方式,这里给出三种升级HTTP/2的方式。

1、将静态资源存放到支持HTTP/2的CDN中

  通常情况下,一个网站中绝大多数的请求是针对静态资源发起的,因此将静态资源部署到支持HTTP/2的CDN上是一种最方便快捷的升级方法。在这种情况下访问静态资源的请求通过HTTP/2完成,而API访问则通过HTTP/1.x完成。这种方法虽然比较便捷,但由于对服务端HTTP/2的支持失去控制,往往难以充分利用所有已经实现的新特性(如Server Push、优先级控制等)。

2、升级反向代理服务器

  反向代理服务器位于客户端和真正的服务器之间,用于对高并发访问的服务器进行负载均衡。在这种情况下头阻塞主要发生在客户端和代理服务器之间的通信,因此在代理服务器中支持HTTP/2可以消除大部分头阻塞带来的性能问题。而代理服务器和真正的服务器之间仍然采用HTTP/1.x进行通信。

3、服务器完全升级

  当然,如果要充分利用HTTP/2带来的性能提升,就需要对所有的服务器启用支持,以保证客户端和服务器之间的所有通信均采用HTTP/2执行。

  值得注意的是,现在HTTP/2中的很多特性仍处于实验阶段,不同的服务器、包可能对不同特性的支持略有差别,所以在升级之前需要充分阅读文档,确保你需要的特性已经被支持。

posted @ 2021-04-21 16:50  古兰精  阅读(517)  评论(0编辑  收藏  举报