上一节我们分析到 http1.1 存在的瓶颈,以及 pipline 会造成 “对首阻塞” 问题的局限性,本节我们来看下 http2 的协议实现以及相较于 http1.x 的优化。
前身 SPDY 协议
其实 SPDY 并不是新的一种协议,而是在 HTTP 之前做了一层会话层。
设计 SPDY 的目的在于降低网页的加载时间。通过优先级和多路复用,SPDY使得只需要建立一个TCP连接即可传送网页内容及图片等资源。SPDY中广泛应用了 TLS 加密,传输内容也均以 gzip 或DEFLATE格式压缩(与HTTP不同,HTTP的头部并不会被压缩)。另外,除了像HTTP的网页服务器被动的等待浏览器发起请求外,SPDY 的网页服务器还可以主动推送内容。[维基百科]
由 Google 开发,用来传送网页内容。基于传输控制协议(TCP)的应用层协议。SPDY 也就是 HTTP/2 的前身。Google最早是在 Chromium 中提出的 SPDY协议。被用于 Google Chrome 浏览器中来访问 Google 的 SSL 加密服务。SPDY 并不是首字母缩略字,而仅仅是 “speedy” 的缩写。SPDY 现为 Google的商标。HTTP/2 的关键功能主要来自SPDY技术,换言之,SPDY 的成果被采纳而最终演变为 HTTP/2。[维基百科]
HTTP/2 协议
HTTP/2 的目的是通过支持请求与响应的多路复用来较少延迟,通过压缩 HTTPS 首部字段将协议开销降低,同时增加请求优先级和服务器端推送的支持。特点包括:
- 传输的最小单元更改为更小的二进制分帧,编码方式为二进制编码
- 支持 tcp 多路复用来降低延迟
- 压缩头部 header 字段将协议开销降低
- 增加请求优先级的功能
- 增加服务端推送功能
二进制分帧层
HTTP 1.x在应用层以纯文本的形式进行通信,而HTTP 2.0将所有的传输信息分割为更小的消息和帧,并对它们采用二进制格式编码。这样,客户端和服务端都需要引入新的二进制编码和解码的机制。
如上图所示,HTTP/2 中的报文,在传输前都会被先构建成一个个的帧(Frame),每次 Socket 发送的最小单位是一个帧,每个帧都以二进制格式进行编码,HTTP 2.0并没有改变HTTP 1.x 的语义,只是在应用层使用二进制分帧方式传输,二进制分帧层只是逻辑上的分层,在 HTTP 和 TCP 层之间,类似于 Http Chunk,在客户端和服务端内部实现。
帧定义
HTTP2 通信的最小单位,包括帧首部、流标识符、优先值和帧净荷等。
其中,帧类型又可以分为:
- DATA:用于传输HTTP消息体;
- HEADERS:用于传输首部字段;
- SETTINGS:用于约定客户端和服务端的配置数据。比如设置初识的双向流量控制窗口大小;
- WINDOW_UPDATE:用于调整个别流或个别连接的流量
- PRIORITY: 用于指定或重新指定引用资源的优先级。
- RST_STREAM: 用于通知流的非正常终止。
- PUSH_ PROMISE: 服务端推送许可。
- PING: 用于计算往返时间,执行“ 活性” 检活。
- GOAWAY: 用于通知对端停止在当前连接中创建流。
标志位用于不同的帧类型定义特定的消息标志。比如 DATA 帧就可以使用End Stream: true 表示该条消息通信完毕。流标识位表示帧所属的流ID。优先值用于HEADERS帧,表示请求优先级。R表示保留位。
消息
消息并没有真正的结构定义,只是逻辑上的概念,一系列的帧组成了一个完整的消息,一系列的 DATA 帧、HEADERS 帧 都可以组成一个消息
流定义
和消息一样,流(Stream)也是一样,并没有真正意义的结构定义,只是逻辑上的概念。流定义为虚拟的信道,可以承载双向的数据传输,每个流有唯一整数标识符。为了防止两端流ID冲突,客户端发起的流具有奇数ID,服务器端发起的流具有偶数ID。
流-消息-帧关系:
- HTTP 2 通信都在一个 TCP 连接上完成, 这个连接可以承载任意数量的双向数据流 Stream
- 每个数据流以消息的形式发送
- 而消息由一或多个帧组成, 这些帧可以乱序发送, 然后根据每个帧首部的流标识符重新组装
二进制分帧层保留了HTTP的语义不受影响,包括首部、方法等,在应用层来看,和HTTP 1.x没有差别。同时,所有同主机的通信能够在一个TCP连接上完成。
二进制编码
在 HTTP/1 中,数据都是以文本编码的模式进行传输的。HTTP2 采用二进制编码的优势有哪些?
- 大多数情况下,二进制编码的占用会更低。
- 更适合用于消息、流的传输
缺点当然也比较明显:不如文本编码那种直观,易于调试,肉眼很难直接看出数据的内容
多路复用
这是 HTTP2 提高性能的核心功能,基于二进制分帧层,HTTP2 可以在共享同一个 TCP 连接的基础上,同时发送请求和响应。一个完整的 HTTP 消息被分为了多个独立的帧,交错发送出去,最后服务端根据流ID和首部将他们重新组合起来。
例如:客户端向服务度发起三个图片请求/image1.jpg,/image2.jpg,/image3.jpg。
HTTP 2 建立一条TCP连接后,并行传输着3个数据流,客户端向服务端乱序发送 stream1~3 的一系列的DATA帧,与此同时,服务端已经在返回stream 1的DATA帧。
上图中包含了一个连接上多个传输中的 Stream:Client 正在向服务端发送Stream5的 Data Frame,同时 Server 也在向 Client 交错发送 Stream1 和 Stream3 的 Frame,在这条TCP连接上有3个请求/响应的数据进行交互
多路复用的优势主要在于以下几点:
- 并行处理多个请求/响应,不会发生应用层面的阻塞
- 使用单个TCP连接,大幅减少资源占用
但是,这个并不是绝对的就说多路复用就比单连接一定快,一般浏览器会在首次加载时创建最多 6个 tcp 连接,如果在带宽足够,负载也不高时,同时下载的文件在 6 个以内时,单连接和多连接速度没什么区别。
请求优先级
流可以带有一个31bit的优先级:
- 0:表示最高优先级
- 231-1:表示最低优先级
客户端明确指定优先级,服务端可以根据这个优先级作为依据交互数据,比如客户端优先级设置为.css>.js>.jpg(具体可参见《高性能网站建设指南》), 服务端按优先级返回结果有利于高效利用底层连接,提高用户体验。 然而,也不能过分迷信请求优先级,仍然要注意以下问题:
- 服务端是否支持请求优先级
- 会否引起队首阻塞问题,比如高优先级的慢响应请求会阻塞其他资源的交互。
服务端推送
HTTP2 增加了服务端推送功能,服务端可以根据客户端的请求,提前返回多个响应,推送额外的资源给客户端
首部压缩
在 HTTP/1 中,报文中的 Header 是通过文本形式编码的,每个header name都会跟一个冒号,然后时一个可选的空格,每个header以CRLF结尾,最后还会保留一个空行作为header部分的结束标识。这些header每次传输会增加500-800字节的开销,如果算上HTTP cookie的化,甚至会增加上千个字节开销。为了减少此开销,HTTP/2使用HPACK压缩格式压缩请求和响应Header数据:
- 通过静态霍夫曼编码(Huffman code)对发送的Header进行编码,降低Header的大小
- 要求客户端和服务器都维护和更新以前看到的Header的索引列表,然后将该列表用作有效编码先前传输的值的参考
客户端和服务端都会建立一个Header索引表,里面包含已经发送或接收的header,当再次发送header数据时会先从这个header索引表中查找,如果找到就用特殊值替换这个重复header,这样降低了重复值的占用,减小了报文大小。
如上图所示,Request 1和Request 2的header只有一个:path不同,那么在发送Request 2时其他只有:path需要完整编码发送,其他header替换成对应重复的索引即可。
性能瓶颈
HTTP2 现在所有的压力集中在底层一个TCP连接之上,TCP很可能就是下一个性能瓶颈,比如 TCP 分组的也会存在队首阻塞问题,单个 TCP packet 丢失导致整个连接阻塞,无法逃避,此时所有消息都会受到影响。
参考资料
HTTP 2.0 原理详细分析
HTTP 的前世今生:一次性搞懂 HTTP、HTTPS、SPDY、HTTP2
HTTP/2 的一些关键性能提升点
SPDY