HTTP/1.1协议的性能问题
最大性能问题就是 HTTP/1.1 的高延迟
主要原因如下几个:
延迟难以下降,
并发连接有限,
队头阻塞问题,;
HTTP 头部巨大且重复,
不支持服务器推送消息,
一些关键的地方是没办法优化的,比如请求-响应模型、头部巨大且重复、并发连接耗时、服务器不能主动推送等,要改变这些必须重新设计 HTTP 协议,于是 HTTP/2 就出来了
兼容HTTP/1.1
HTTP/2 是怎么做的呢?
第一点,HTTP/2 没有在 URI 里引入新的协议名,仍然用「http://」表示明文协议,用「https://」表示加密协议
第二点,只在应用层做了改变,还是基于 TCP 协议传输,应用层方面为了保持功能上的兼容,HTTP/2 把 HTTP 分解成了「语义」和「语法」两个部分,「语义」层不做改动,与 HTTP/1.1 完全一致
HTTP/2 在「语法」层面做了很多改造,基本改变了 HTTP 报文的传输格式
头部压缩
HTTP 协议的报文是由「Header + Body」构成的
HTTP/1.1 协议可以使用头字段 「Content-Encoding」指定 Body 的压缩方式,但报文中的另外一部分 Header,是没有针对它的优化手段。
HTTP/1.1 报文中 Header 部分存在的问题
含很多固定的字段比如 Cookie、User Agent、Accept 等,有必要压缩
大量的请求和响应的报文里有很多字段值都是重复的,必须要避免重复性
字段是 ASCII 编码的,虽然易于人类观察,但效率低,所以有必要改成二进制编码
HTTP/2HPACK 算法压缩头部
HPACK 算法主要包含三个组成部分:
静态字典;
动态字典;
Huffman 编码(压缩算法);
客户端和服务器两端都会建立和维护「字典」,用长度较小的索引号表示重复的字符串,再用 Huffman 编码压缩数据,可达到 50%~90% 的高压缩率
静态表编码
HTTP/2 为高频出现在头部的字符串和字段建立了一张静态表,它是写入到 HTTP/2 框架里的,不会变化的,静态表里共有
61
组表中的
Index
表示索引(Key),Header Value
表示索引对应的 Value,Header Name
表示字段的名字HTTP/2 头部由于基于二进制编码,就不需要冒号空格和末尾的\r\n作为分隔符,于是改用表示字符串长度(Value Length)来分割 Index 和 Value。
动态表编码
静态表只包含了 61 种高频出现在头部的字符串,不在静态表范围内的头部字符串就要自行构建动态表,它的 Index 从
62
起步,会在编码解码的时候随时更新。那么在下一次发送的时候,就不用重复发这个字段的数据了,只用发 1 个字节的 Index 号就好了,因为双方都可以根据自己的动态表获取到字段的数据。
使得动态表生效有一个前提:必须同一个连接上,重复传输完全相同的 HTTP 头部。
二进制帧
HTTP/2 厉害的地方在于将 HTTP/1 的文本格式改成二进制格式传输数据,极大提高了 HTTP 传输效率,而且二进制数据使用位运算能高效解析
HTTP/2 把响应报文划分成了两类帧(Frame),图中的 HEADERS(首部)和 DATA(消息负载) 是帧的类型,也就是说一条 HTTP 响应,划分成了两类帧来传输,并且采用二进制来编码。
二进制帧的结构
帧头(Frame Header)很小,只有 9 个字节,帧开头的前 3 个字节表示帧数据(Frame Playload)的长度。
帧长度后面的一个字节是表示帧的类型,HTTP/2 总共定义了 10 种类型的帧,一般分为数据帧和控制帧两类,
帧类型后面的一个字节是标志位,可以保存 8 个标志位,用于携带简单的控制信息
END_HEADERS 表示头数据结束标志,相当于 HTTP/1 里头后的空行(“\r\n”);
END_Stream 表示单方向数据发送结束,后续不会再有数据帧。
PRIORITY 表示流的优先级;
帧头的最后 4 个字节是流标识符(Stream ID),但最高位被保留不用,只有 31 位可以使用,因此流标识符的最大值是 2^31,大约是 21 亿,它的作用是用来标识该 Frame 属于哪个 Stream,接收方可以根据这个信息从乱序的帧里找到相同 Stream ID 的帧,从而有序组装信息。
帧数据了,它存放的是通过 HPACK 算法压缩过的 HTTP 头部和包体
并发传输
HTTP/1.1 的实现是基于请求-响应模型的
同一个连接中,HTTP 完成一个事务(请求与响应),才能处理下一个事务,也就是说在发出请求等待响应的过程中,是没办法做其他事情的,如果响应迟迟不来,那么后续的请求是无法发送的,也造成了队头阻塞的问题。
HTTP/2 通过 Stream 这个设计,多个 Stream 复用一条 TCP 连接,达到并发的效果,解决了 HTTP/1.1 队头阻塞的问题,提高了 HTTP 传输的吞吐量。
1 个 TCP 连接包含一个或者多个 Stream,Stream 是 HTTP/2 并发的关键技术;
Stream 里可以包含 1 个或多个 Message,Message 对应 HTTP/1 中的请求或响应,由 HTTP 头部和包体构成;
Message 里包含一条或者多个 Frame,Frame 是 HTTP/2 最小单位,以二进制压缩格式存放 HTTP/1 中的内容(头部和包体);
多个 Stream 跑在一条 TCP 连接,同一个 HTTP 请求与响应是跑在同一个 Stream 中,HTTP 消息可以由多个 Frame 构成, 一个 Frame 可以由多个 TCP 报文构成。
在 HTTP/2 连接上,不同 Stream 的帧是可以乱序发送的(因此可以并发不同的 Stream ),同一 Stream 内部的帧必须是严格有序的。
因为当 HTTP/2 实现 100 个并发 Stream 时,只需要建立一次 TCP 连接,而 HTTP/1.1 需要建立 100 个 TCP 连接,每个 TCP 连接都要经过 TCP 握手、慢启动以及 TLS 握手过程,这些都是很耗时的。
HTTP/2 还可以对每个 Stream 设置不同优先级,帧头中的「标志位」可以设置优先级
服务器主动推送资源
HTTP/1.1 不支持服务器主动推送资源给客户端,都是由客户端向服务器发起请求后,才能获取到服务器响应的资源。
那 HTTP/2 的推送是怎么实现的?
客户端发起的请求,必须使用的是奇数号 Stream,服务器主动的推送,使用的是偶数号 Stream。
服务器在推送资源时,会通过
PUSH_PROMISE
帧传输 HTTP 头部,并通过帧中的Promised Stream ID
字段告知客户端,接下来会在哪个偶数号 Stream 中发送包体。在 Stream 1 中通知客户端 CSS 资源即将到来,然后在 Stream 2 中发送 CSS 资源,注意 Stream 1 和 2 是可以并发的
总结
HTTP/2优点
第一点,对于常见的 HTTP 头部通过静态表和 Huffman 编码的方式,将体积压缩了近一半,而且针对后续的请求头部,还可以建立动态表,将体积压缩近 90%
第二点,HTTP/2 实现了 Stream 并发,多个 Stream 只需复用 1 个 TCP 连接,节约了 TCP 和 TLS 握手时间,以及减少了 TCP 慢启动阶段对流量的影响
第三点,服务器支持主动推送资源,大大提升了消息的传输性能
HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。
有没有什么解决方案呢?
放弃 TCP 协议,转而使用 UDP 协议作为传输层协议
【知识点随笔分析 | 第六篇】HTTP/1.1,HTTP/2和HTTP/3的区别
2024-03-28 10:12:03 60 阅读