深入理解 http 2.0 协议

246 阅读10分钟

简介

本文将从概述、原理、实战及检测等方面来详细介绍http2.0,希望能够加深你的理解。

什么是http2.0协议?

1. 对1.x协议语意的完全兼容

2.0协议是在1.x基础上的升级而不是重写,1.x协议的方法,状态及api在2.0协议里是一样的。

2. 性能的大幅提升

2.0协议重点是对终端用户的感知延迟、网络及服务器资源的使用等性能的优化。

http2.0优化内容

1. 二进制分帧(Binary Format)- http2.0的基石

http2.0 之所以能够突破 http1.X 标准的性能限制,改进传输性能,实现低延迟和高吞吐量,就是因为其新增了二进制分帧层,在二进制分帧层上,http2.0 会将所有传输信息分割为更小的消息和帧,并对它们采用二进制格式的编码将其封装,兼容上一代 http 标准,其中,http1.X 中的首部信息 header 封装到 Headers 帧中,而 request body 将被封装到 Data 帧中。

  1. 消息(message):一个完整的请求或者响应,比如请求、响应等,由一个或多个 Frame 组成。
  2. 帧(frame)包含部分:类型 Type, 长度 Length, 标记 Flags, 流标识 Stream 和 frame payload 有效载荷。

一个 http2 连接上可包含多个并发打开的流,这个并发流的数量能够由客户端设置。

流是连接中的一个虚拟信道,可以承载双向消息传输。每个流有唯一整数标识符。为了防止两端流ID冲突,客户端发起的流具有奇数 ID,服务器端发起的流具有偶数 ID。

流标识 Stream 是描述二进制 frame 的格式,使得每个 frame 能够基于 http2 发送,每个流是一个逻辑联系,一个独立的双向的 frame 存在于客户端和服务器端之间的http2连接中。

http2_1

2. 多路复用 (Multiplexing) / 连接共享

在 http1.1 中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量的限制,超过限制数目的请求会被阻塞。这也是为何一些站点会有多个静态资源 CDN 域名的原因之一。

而 http2.0 中的多路复用优化了这一性能。多路复用允许同时通过单一的 http/2 连接发起多重的请求-响应消息。有了新的分帧机制后,http/2 不再依赖多个 TCP 连接去实现多流并行了。每个数据流都拆分成很多互不依赖的帧,而这些帧可以交错(乱序发送),还可以分优先级,最后再在另一端把它们重新组合起来。

http 2.0 连接都是持久化的,而且客户端与服务器之间也只需要一个连接(每个域名一个连接)即可。http2 连接可以承载数十或数百个流的复用,多路复用意味着来自很多流的数据包能够混合在一起通过同样连接传输。当到达终点时,再根据不同帧首部的流标识符重新连接将不同的数据流进行组装。

http2_2

上图展示了一个连接上的多个传输数据流:客户端向服务端传输数据帧 stream5,同时服务端向客户端乱序发送 stream1 和 stream3。这次连接上有三个响应请求乱序并行交换。

http2_3

上图就是 http1.X 和 http2.0 在传输数据时的区别。

3. 头部压缩(Header Compression)

http1.x 的头带有大量信息,而且每次都要重复发送。http2 使用 encoder 来减少需要传输的 header 大小,通讯双方各自缓存一份头部字段表,既避免了重复 Header 的传输,又减小了需要传输的大小。

对于相同的数据,不再通过每次请求和响应发送,通信期间几乎不会改变通用键-值对(用户代理、可接受的媒体类型,等等)只需发送一次。

事实上,如果请求中不包含首部(例如对同一资源的轮询请求),那么,首部开销就是零字节,此时所有首部都自动使用之前请求发送的首部。

如果首部发生了变化,则只需将变化的部分加入到 Header 帧中,改变的部分会加入到头部字段表中,首部表在 http 2.0 的连接存续期内始终存在,由客户端和服务器共同渐进地更新。

需要注意的是,http 2.0 关注的是 Header 压缩,而我们常用的 gzip 等是报文内容(body)的压缩,二者不仅不冲突,且能够一起达到更好的压缩效果。

http/2使用的是专门为 Header 压缩而设计的 HPACK② 算法。

http2_4

从上图可以看到第一个 http1.X 不支持首部压缩,而 http2.0 的压缩算法效果最好,发送和接受的数据量都是最少的。

4. 压缩原理

用 Header 字段表里的索引代替实际的 Header。

http2 的 HPACK 算法使用一份索引表来定义常用的 http Header,把常用的 http Header 存放在表里,请求的时候便只需要发送在表里的索引位置即可。

例如 :method=GET 使用索引值 2 表示,:path=/index.html 使用索引值 5 表示,如下图:

http2_5

因为索引表的大小的是有限的,它仅保存了一些常用的 http Header,同时每次请求还可以在表的末尾动态追加新的 http Header 缓存,动态部分称之为 Dynamic Table。Static Table 和 Dynamic Table 在一起组合成了索引表:

以常用的 User-Agent 为例,它在静态表中的索引值是 58,它的值是不存在表中的,因为它的值是多变的。第一次请求的时候它的 key 用 58 表示,表示这是一个 User-Agent ,它的值部分会进行霍夫曼编码。

服务端收到请求后,会将这个 User-Agent 添加到 Dynamic Table 缓存起来,分配一个新的索引值。客户端下一次请求时,假设上次请求User-Agent 的在表中的索引位置是 62, 此时只需要发送 62 ,便可以代表:User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36。

http2_6

最终,相同的 Header 只需要发送索引值,新的 Header 会重新加入 Dynamic Table。

5. 请求优先级(Request Priorities)

把 http 消息分为很多独立帧之后,就可以通过优化这些帧的交错和传输顺序进一步优化性能。每个流都可以带有一个 31 比特的优先值,0 表示最高优先级;2的31次方-1 表示最低优先级。

服务器可以根据流的优先级,控制资源分配(CPU、内存、带宽),而在响应数据准备好之后,优先将最高优先级的帧发送给客户端。高优先级的流都应该优先发送,但又不会绝对地准守,可能又会引入首队阻塞的问题:高优先级的请求慢导致阻塞其他资源交付。

分配处理资源和客户端与服务器间的带宽,不同优先级的混合也是必须的。客户端会指定哪个流是最重要的,有一些依赖参数,这样一个流可以依赖另外一个流。优先级别可以在运行时动态改变,当用户滚动页面时,可以告诉浏览器哪个图像是最重要的,你也可以在一组流中进行优先筛选,能够突然抓住重点流。

  • 优先级最高:主要的html
  • 优先级高:CSS文件
  • 优先级中:js文件
  • 优先级低:图片

6. 服务端推送(Server Push)

服务器可以对一个客户端请求发送多个响应,服务器向客户端推送资源无需客户端明确地请求。并且,服务端推送能把客户端所需要的资源伴随着 index.html 一起发送到客户端,省去了客户端重复请求的步骤。

正因为没有发起请求,建立连接等操作,所以静态资源通过服务端推送的方式可以极大地提升速度。Server Push 让 http1.x 时代使用内嵌资源的优化手段变得没有意义;如果一个请求是由你的主页发起的,服务器很可能会响应主页内容、logo 以及样式表,因为它知道客户端会用到这些东西,这相当于在一个 HTML 文档内集合了所有的资源。

不过与之相比,服务器推送还有一个很大的优势:可以缓存!也让在遵循同源的情况下,不同页面之间可以共享缓存资源成为可能。

http2_7

注意两点:

  1. 推送遵循同源策略;
  2. 这种服务端的推送是基于客户端的请求响应来确定的。

当服务端需要主动推送某个资源时,便会发送一个 Frame Type 为 PUSH_PROMISE 的 Frame,里面带了 PUSH 需要新建的 Stream ID。意思是告诉客户端:接下来我要用这个 ID 向你发送东西,客户端准备好接着。客户端解析 Frame 时,发现它是一个 PUSH_PROMISE 类型,便会准备接收服务端要推送的流。

http2.0 性能瓶颈

启用http2.0后会给性能带来很大的提升,但同时也会带来新的性能瓶颈。因为现在所有的压力集中在底层一个TCP连接之上,TCP很可能就是下一个性能瓶颈,比如TCP分组的队首阻塞问题,单个TCP packet丢失导致整个连接阻塞,无法逃避,此时所有消息都会受到影响。未来,服务器端针对http 2.0下的TCP配置优化至关重要。

如何升级为 http2.0 协议

1. 首先 Nginx 服务器升级 http2.0 协议需要满足如下条件:

  1. Nginx 版本高于1.9.5
  2. 安装模块 --with-http_ssl_module、--with-http_v2_module

--with-http_ssl_module 模块是因为 http2.0 协议是一种 https 协议。

2. 检查一下 Nginx 配置

nginx -V

http2_8

注:这个是已经添加了上述对应模块。没有这两个模块的需要手动编译安装,下面就来看看如何安装。

3. 找到 Nginx 文件目录

http2_9

4. 编译安装 Nginx 模块

--with-http_stub_status_module  --with-http_ssl_module  --with-http_v2_module

http2_10

然后执行如下命令,进行编译安装。

make

make install

http2_11

http2_12

5. 更改 Nginx 配置

安装结束后将 Nginx.config 文件中 443 端口添加 http2

http2_13

6. 启动 Nginx

最后一步,重启 nginx restart(注意不要直接 nginx -s reload )。这时候你的站点就升级为了 http2.0 协议了。

7. 检查

升级完成后,怎么确定自己的站点是 http2.0 协议呢?一般有如下几种方法:

  1. chrome devtool 打开 chrome 调试工具,在 network 勾选 protocol 项,h2 代表的是 http2.0 协议

  2. 网站 SSL 服务器检测的网站,对网站进行安全评级,并将检测结果自动生成一个详细的评价报告

  3. 插件 http.2 and SPDY indicator 这是一款检测 http2.0 和 SPDY 协议(Google 开发的基于 TCP的会话层协议)的插件。

总结

总的来说,http2.0 带来了很多实质上的提升,其中主要包括:

  1. 多路复用
  2. Header 压缩缓存
  3. 服务器推送

但上述中也提到 http2.0 也有缺点,例如:多路复用在同一个 http 建立的 TCP 通道上,位于高优先级的消息帧,如果出现了阻塞,就会导致后续的所有消息帧都会受到影响,导致阻塞其他资源交付。这点就可以看看 http3 所做的优化有哪些了。