Hello, HTTP2!

HTTP2

2015年,http2的协议规范RFC 7540/7541发布。虽然目前还没有达到普及的程度,实际工作可能用的不多(自己猜测);但是都1202年了,如果你还不了解其设计思想和原理,可能就有些落伍啦。

首先我们截取RFC摘要解读一下:

This specification describes an optimized expression of the semantics of the Hypertext Transfer Protocol (HTTP), referred to as HTTP version 2 (HTTP/2). HTTP/2 enables a more efficient use of network resources and a reduced perception of latency by introducing header field compression and allowing multiple concurrent exchanges on the same connection. It also introduces unsolicited push of representations from servers to clients.
This specification is an alternative to, but does not obsolete, the HTTP/1.1 message syntax. HTTP’s existing semantics remain unchanged.

从摘要就能大致了解http2有哪些特点:

  1. 头部压缩(header field compression)
  2. 多路复用(multiple concurrent exchanges on the same connection)
  3. 服务端推送(unsolicited push from servers to clients)
  4. 不变的语义(existing semantics remain unchanged)

下面我们就简单讨论一下各个特性,并试着理解其原理。

由来

目前一个网页,页面里面几十个资源文件(html, css, js)是很常见的。http1.0 因为其“请求-应答”特点会导致严重的性能问题,每个请求连接的建立都需要建立一次很重的TCP连接,返回响应后连接关闭,虽然请求可以通过并发提升效率,如Chrome浏览器能同时并发6个请求连接,但一方面连接数也是有限的,另一方面因为http1的限制,连接数越大并不能提高效率,有时反而会起到副作用,类似于多线程一样。

针对连接问题,http1.1引入了keep-alive机制,从而实现了TCP连接的复用。具体而言,就是在http请求头加入Connection: Keep-Alive。目前http1.1都默认开启这个机制,使得当前请求处理完成后不会关闭连接,同时在http的响应头中也能看到该字段。

引入连接复用keep-alive之后,又有个问题:连接应该什么时候关闭?因为服务器资源是可贵的,请求一旦处理完成,长连接应该立即关闭回收。

http有个响应头Content-Length,这个字段可以告诉客户端http响应的Body有多少个字节,当客户端接收到这么多字节后,响应即成功接收,可以关闭连接。需要注意的是,互联网上传输的内容, 尤其是文本类内容,比如HTML, CSS, JS,都是经过Gzip压缩得到的。传输压缩在很多情况下是很有必要的,其不仅能优化传输数据量,减少网站流量,而且得益于机器强大的计算能力(即时压缩),整个压缩和解压都在内存的数据流中完成,提高了首字节时间(TTFB, Time To First Byte),改善了客户端打开网页的速度。因此得到的Content-Length实际为消息实体压缩后的长度。

但事实上,服务器返回的部分页面内容是动态生成的,例如Ajax, JSP, PHP等,如果需要得到Content-Length,还需要服务器在内存中渲染(SSR)然后再计算长度,比较耗时。

为此,http1.1引入了分块传输机制,即在响应头加上Transfer-Encoding: chunked,此时响应的Body中是以块为单位传输,并且块之间存在分隔符,所有块的结尾也存在特殊标记,这样方便客户端能判断响应数据是否全部接收完成。

看起来,http1.1已经帮我们解决了很多问题。诸如长连接分块传输等。为了追求更好的性能,前端层面以优化链路传输为目的,提出了很多减少网络请求的性能优化方案:

  1. 雪碧图
  2. 小图片Base64编码内联
  3. 文件的压缩与合并(JS,CSS,HTML)
  4. 域名分片,突破并发连接数

尽管做了很多类似的优化方式,也的确使得TCP连接数有所下降,但有得必有失。上述方案都或多或少存在一些副作用,这就需要我们权衡方案为传输实际带来的提升与为此所牺牲的其他性能指标。而且http1.1的长连接方案也并非完美,它的“请求-响应”模式依然是串行的,并发度远远不够。

http1.1又引入了Pipeline机制。顾名思义,就是如流水线一般地依次发送各个请求,而没有必要等到之前的响应回来再发送下一个请求,但是存在致命的队头阻塞(Head-of-Line Blocking)问题。

客户端发送请求1、2、3,虽然服务器能做到并发响应,但需要保证客户端的接收顺序为1、2、3,才便于将请求与响应进行配对。如果客户端长时间得不到请求1的响应,则请求2、3同样会被阻塞。
在这里插入图片描述
http2的多路复用机制就能很完美地解决队头阻塞的问题,而这也是http2最重要的技术特征之一。

建立会话

IETF标准并不要求http2必须基于TLS/SSL协议。所以http2协议有两种版本:

  1. h2: 基于TLS的http2,即加密的http2
  2. h2c:基于TCP的http2,即明文的http2

h2c协议基于TCP,在作会话建立的时候,同websocket类似,请求头会加入字段Connection: Upgrade, HTTP2-Settings以及Upgrade: h2c进行协议升级,其中HTTP2-Settings为http2的设置帧,类型是0x4,用于传递影响两端通信的配置参数。之后服务端返回101状态码,并写入Connection: Upgrade,最后还需要客户端返回一个24字节ASCII编码的Preface(Magic帧),其内容是固定的:PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n,紧随其后为一个Settings Frame

h2协议基于TLS,也是目前浏览器支持的HTTP2协议。不同于h2c的协议升级,h2协议首先建立TLS握手,通过客户端发送Client Hello,服务端返回Server Hello来协商密钥和支持协议(ALPN Extension)。ALPN Extension中的ALPN Protocol中表明支持协议:ALPN Next Protocol: h2。同h2c协议一样,最后客户端也会发送一个Magic Frame,并紧跟Setting Frame,完成h2协议会话建立。

多路复用

不同于http1.x 中基于ASCII码的文本传输格式,http2中采用了二进制分帧(Frame)来描述各种数据,使得其具有更小的传输体积和负载,解析也更加高效;此外,http2采用消息(Message)对应于http1中的请求/响应,一个Message中包含一条或多条Frame;多路复用的关键在于(Stream)的提出,一个双向通信数据Stream中包含了一个或者多个Message,而多个Stream就组成了http2的连接(Connection)。

在这里插入图片描述
从本质上说,http2 舍弃了 http1.x 的Request概念,采用了Frame作为最小粒度的传输单位。http2首先将请求和响应的明文字符报文转换为二进制,然后分成多个Frame发送,在同一个TCP连接中,Frame的发送可以是乱序的,但属于同一个Stream下的Frame则有着相同的Stream ID,并且有序;利用Stream ID作为标签在接收端进行重组,从而保证在一一配对的前提下,实现了高并发。

多路复用技术在通信与网络中十分常见。多路复用这个词语看起来很高大上,但并不难理解。为了有效地利用通信线路,使得在一个信道上同时传输多路信号的技术就叫多路复用技术。多路复用技术根据参考量的不同分为频分多路复用、时分多路复用等。而计算机网络I/O中的多路复用技术属于时间片轮转的时分多路复用技术,利用一个线程来检查多个文件描述符Socket的状态,如果有一个文件描述符准备就绪,则返回,否则阻塞直到超时,这样可以达到使用单线程监控多个高并发连接,减少了相应的上下文切换和内存开销,大名鼎鼎的NginxRedis都是很好地利用了IO多路复用技术,从而具备强悍的性能。
在这里插入图片描述
有了多路复用的支持,http2 就可以对每个域名只维持一个 TCP 连接(One Connection Per Origin)并发多个Stream,来以任意顺序传输任意数量的资源。这样就既减轻了服务器的连接压力,开发者也不用去考虑域名分片这种事情,突破了浏览器对每个域名最多 6 个并发连接数的限制。设想,当 http2 实现100个并发连接时,只需要经历1次TCP握手、1次TCP慢启动以及1次TLS握手,http1.x 则会将上述过程重复100倍。

为了更好地利用连接,加大吞吐量,http2 还添加了一些控制帧来管理虚拟的Stream,实现了优先级和依赖控制,这些特性也和 TCP 协议非常相似。

HPACK算法

因为http的无状态特性,使得http的头部很重。而http2采用了HPACK算法对其进行了瘦身。

不同于gzipzlib等压缩算法,HPACK需要客户端与服务端各自维护一份字典,压缩和解压缩就是查表和更新表的操作。

静态/动态字典

http2 将原来的起始行里面的请求方法、URI、状态码等都统一转换成了伪头字段(pseudo-header fields)。例如:method:status:scheme分别表示请求方法、状态码和协议。

http2为常用的头字段定义了一个只读的静态表[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-00xRWgLK-1623552604064)(file:///C:/Users/86136/Desktop/blog/http2_6.webp)]
通过简单地查表就可以知道字段名和值,例如1表示authority头部,即 http1.x 中的host2表示GET方法,7表示https协议,8表示200状态码。仅当表里value值为空或者不存在相应的字段key时,利用动态表添加到静态表后面,编码解码的时候随时更新。例如,当请求中cookie字段很大,首先经过哈夫曼编码压缩并发送,同时客户端与服务器两边都更新自己的动态表,添加了一个新的索引号62,那么下一次只用一个字节发送编号就好,而省略了重复的、冗长的cookie字段,大大节省了流量。下图是一个简单静态表的头部格式:
img
Index字典索引,与固定头部01(静态表)组成第一个字节。H位表示是否对value字段进行Huffman编码,剩余7位表示其长度,最后则是编码后的二进制字符串。

可以想到,动态表的优势将会随着同一http2连接发送报文的增多而变大,但是动态表越大意味着越大的内存占用。一般情况下,Web 服务器都会提供类似 http2_max_requests 的配置,用于限制一个连接上能够传输的请求数量,避免动态表无限增大,一旦请求数量到达上限后,就会关闭 http2 连接来释放内存。

哈夫曼编码

哈夫曼编码是一种可变长编码的方式,其思想很简单:依据字符出现概率来构造异字头的平均长度最短的码字。其底层依赖于一种哈夫曼树的数据结构,它是一种特殊的二叉树,这种树的所有叶子结点都带有权值,而我们的目标就是从中构造出带权路径长度最短的二叉树。例如下面两棵具有相同叶子节点的树,其中叶子节点数值标识某个字符的出现频数:
在这里插入图片描述

WPL(Weighted Path Length)=  1 × 2 + 3 × 2 + 5 × 2 + 7 × 2 = 32

在这里插入图片描述

WPL = 1 × 3 + 3 × 3 + 5 × 2 + 7 × 1 = 29

实际上,WPL = 29的这棵树就是一颗Huffman树。这里虽然仅列出2种树的形态。实际上你也可以列出所有(卡特兰树,Catalan number)具有4个给定叶子节点二叉树的所有形态,计算它们的WPL进行对比、体会。Huffman树一旦建立好了,将每个节点的左分支都编码为0,右分支路径编码为1,即可得到该树所对应的Huffman编码序列。例如上面这课Huffman树,对出现7次的字符编码为1,而对出现3次的字符编码为001

服务端推送

http2还在一定程度上改变了传统的“请求-应答”工作模式,服务器不再是完全被动地响应请求,也可以新建“流”主动向客户端发送消息。比如,在浏览器刚请求 HTML 的时候就提前把可能会用到的 JS、CSS 文件发给客户端,减少等待的延迟,这被称为“服务器推送”(Server Push,也叫 Cache Push)。
在这里插入图片描述
上图Stream 2Stream 4下的promise表示服务端有意向客户端推送的PUSH_PROMISE帧,PUSH_PROMISE帧中只包含了推送资源的头部,如果客户端接受了PUSH_PROMISE,则服务端在之后发送响应的DATA帧以推送资源,当客户端存在资源缓存时,资源推送也就没有必要,客户端可以选择拒绝PUSH_PROMISE

当然如果不需要服务端推送,可以在Settings帧中设置服务端流的值为0,禁用此功能。

语义

http2在设计过程中就很好地考虑了与http1.x的兼容,让浏览器或者服务器去自动升级或降级协议,免去了选择的麻烦,让用户在上网的时候都意识不到协议的切换,实现平滑过渡。因此不管是URL Scheme报文结构还是端口都与http1.x一模一样,实际上http2可以看作是http1.1与TCP之间的一个转换层,转换层就是SPDY,也即是早期的http2。

协议栈

通过一个形象的协议栈来总结HTTP的演进变化:
img
可见,目前网络链路传输正处于新旧交替的时代,许多既有的设备、程序、知识,都会在未来几年的时间里出现重大更新。我也不敢预测未来基于通信、网络协议的快速普及发展会带来什么爆炸性的创新,只能选择为此做好准备,并拭目以待。

不足

相对于http1.x,http2中基于二进制分帧的多路复用技术的确带来了性能上的优势。但也有一个例外,设想如果当前网络环境十分糟糕或者在传输大文件的时候,http2的所有连接仅建立在一个TCP连接上,每个TCP分组都会带着一个唯一的序号被发出,而所有分组必须按顺序传送到接收端。如果中途有一个分组没能到达接收端,那么后续分组必须存放在接收端内核的TCP缓冲区上,等待丢失分组重发并到达接收端,因此TCP丢包可能导致整个连接阻塞,此时性能可能并不及http1.x,毕竟http1.x能同时开启多个并发连接。也就是说,http2并不能彻底解决队头阻塞问题,它从HTTP协议层面解决了请求的并发发送与接收,但TCP协议先进先出的特性使得其传输的二进制分帧依然是串行的。Google提出了彻底解决队头阻塞的方法,那就是换掉TCP协议。因此,基于UDP的QUIC协议被提出,因为UDP是面向数据报的协议,数据报之间就不会有阻塞约束。

参考:

High Performance Browser Networking

透视HTTP协议

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Key Board

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值