一、HTTP1.1的高延迟性能问题
- HTTP头部巨大且重复
- 对头阻塞问题
- 不支持服务器推送消息
- 并发连接有限
二、HTTP2的针对HTTP1.1的优化
兼容老版本
- HTTP没有在URI里引入新的协议名,仍然用http://表示明文协议,用https://表示加密协议, 于是只需要浏览器和服务器在背后自动升级协议,这样可以让用户意识不到协议的升级,很好的实现了协议的平滑升级。
- 只在应用层做了改变,还是基于 TCP 协议传输,应用层方面为了保持功能上的兼容,HTTP2把HTTP分解成了语义和语法两个部分,语义层不做改动,与 HTTP1.1 完全?致,比如请求方法、状态码、头字段等规则保留不变。 但是,HTTP/2 在语法层面做了很多改造,基本改变了 HTTP 报文的传输格式。
头部压缩
HTTP1.1存在的问题
- 存在很多固定字段,比如Cookie、Accept等,加起来占的字节高达几百到几千,有必要进行压缩。
- 大量的请求和报文里很多字段都是重复的,有必要避免重复性。
- 字段是ASCII编码的,虽然易于观察,但不适合传输,有必要改成二进制编码。
HTTP2没有使用常见的gzip,而是开发了HPACK算法
客户端和服务器两端都会建立和维护字典,用长度较小的索引号表示重复的字符串,再用Huffman 编码压缩数据,可达到 50%~90% 的高压缩率。
静态表编码
概述
HTTP2为高频出现在头部的字符串和字段建?了?张静态表,它是写入到 HTTP2框架里的,不会变化的,静态表里共有61组。
过程
例如,server头部字段的Index为 54,?进制为110110,再加上固定01,头部格式第1个字节就是 01110110,第二个字节的首个比特位表示Value是否经过 Huffman编码,剩余的 7 位表示Value的长度,10000110,首位为1,表示 Value字符串是经过Huffman编码的,经过Huffman编码的Value长度为6。
动态表编码
概述
静态表只包含了 61 种?频出现在头部的字符串,不在静态表范围内的头部字符串就要自行构建动态表,它的Index从62起步,会在编码解码的时候随时更新。
过程
例如,第?次发送时头部中的user-agent字段数据有上百个字节,经过 Huffman 编码发送出去后,客户端和服务器双方都会更新自己的动态表,添加?个新的Index号62。那么在下?次发送的时候,就不用重复发这个字段的数据了,只用发 1 个字节的 Index号就好了,因为双方都可以根据自己的动态表获取到字段的数据。
使用前提
使用动态表的前提是:必须同?个连接上,重复传输完全相同的 HTTP 头部。如果消息字段在 1 个连接上只发送了1次,或者重复传输时,字段总是略有变化,动态表就无法被充分利用了。
不足
动态表越大,占用的内存也就越大,如果占用了太多内存,是会影响服务器性能的,因此Web服务器都会提供类似 http2_max_requests 的配置,用于限制?个连接上能够传输的请求数量,避免动态表无限增大,请求数量到达上限后,就会关闭 HTTP/2 连接来释放内存。
二进制帧
HTTP/2 把响应报文划分成了两个帧(首部,消息负载)传输,并采用二进制来编码。 首部
- 前3个字节:代表数据帧的长度
- 第4个字节:表示帧的类型(共有10种,一般分为数据帧和控制帧)
- 第5个字节:标志位,用于携带简单的控制信息
- END_HEADERS 表示头数据结束标志
- END_STREAM 表示单方向数据发送结束,后续不会再有数据帧
- PRIORITY 表示流的优先级
- 最后4个字节:流标识符(StreamID),但最高位被保留不用,只有 31 位可以使用,它的作用是用来标识该 Fream 属于哪个Stream,接收方可以根据这个信息从乱序的帧里找到相同StreamID的帧,从而有序组装信息。
消息负载
- HPACK算法压缩过的HTTP头部
- HPACK算法压缩过的 HTTP包体
HTTP2二进制帧结构
并发传输
Steam、Message、Frame之间的关系
- 1个TCP连接包含?个或者多个Stream
- Stream里可以包含1个或多个Message,Message对应 HTTP1中的请求或响应,由HTTP 头部和包体构成
- Message里包含?条或者多个Frame,Frame 是 HTTP/2最小单位,以?进制压缩格式存放HTTP/1中的内容(头部和包体)
- HTTP消息可以由多个Frame构成,以及1个Frame可以由多个TCP报文构成
StreamID
- 不同 Stream的帧是可以乱序发送的(因此可以并发不同的 Stream ),因为每个帧的头部会携带Stream ID信息,所以接收端可以通过Stream ID有序组装成HTTP消息,而同一Stream内部的帧必须是严格有序的。
- 客户端建立的 Stream ID必须是奇数号,而服务器建立的Stream必须是偶数号。
- 同?个连接中的 Stream ID是不能复?的,只能顺序递增,所以当 Stream ID 耗尽时,需要发?个控制帧 GOAWAY ,用来关闭 TCP 连接。 在 Nginx 中,可以通过http2_max_concurrent_streams 配置来设置 Stream 的上限,默认是 128 个
Stream优先级
HTTP/2还可以对每个Stream 设置不同优先级,帧头中的标志位可以设置优先级。
服务器主动推送资源
客户端发起的请求,必须使?的是奇数号Stream,服务器主动的推送,使用的是偶数号 Stream。服务器在推送资源时,会通过PUSH_PROMISE帧传输 HTTP 头部,并通过帧中的Promised Stream ID字段告知客户端,接下来会在哪个偶数号Stream 中发送包体。 在Stream 1中通知客户端CSS 资源即将到来,然后在Stream2中发送 CSS 资源,注意 Stream1和2是可以并发的。
|