文章目录
一、QUIC 如何解决TCP的队头阻塞问题?
1.1 TCP 为何会有队头阻塞问题
前篇博文:HTTP/2 是如何解决HTTP/1.1 性能瓶颈的?中介绍了HTTP/2 相比HTTP/1.1 设计出的一些优秀的改进方案,大幅提高了HTTP 的网络利用效率。HTTP/2 在应用协议层通过多路复用同一个TCP连接解决了队头阻塞问题,但这是以下层协议比如TCP 协议不出现任何数据包阻塞为前提的。TCP 在实际运行中,特别是遇到网络环境不好时,数据包超时确认或丢失是常有的事,假如某个数据包丢失需要重传时会发生什么呢?
在博文:网络传输管理之TCP协议中介绍了TCP 的工作原理与实现过程,TCP 采用正面确认和超时重传机制来保证数据包的可靠交付。比如主机A 向 主机B 发送数据包,主机B 收到该数据包后会向主机A 返回确认应答报文,表示自己确实收到了该数据包,主机A 收到确认应答报文后才确定上一个数据包已经发送成功,开始发送下一个数据包。如果超过一定时间(根据每次测量的往返时间RTT估算出的动态阈值)未收到确认应答,则主机A 判断上一个数据包丢失了,重新发送上一个数据包,这就相当于阻塞了下一个数据包的发送。
逐个发送数据包,等待确认应答到来后再发送下一个数据包,效率太低了,TCP 采用滑动窗口机制来提高数据传输效率。窗口大小就是指无需等待确认应答而可以继续发送数据的最大值,这个机制实现了使用大量的缓冲区,通过对多个数据包同时进行确认应答的功能。当可发送数据的窗口消耗殆尽时,就需要等待收到连续的确认应答后,当前窗口才会向前滑动,为发送下一批数据包腾出窗口。假设某个数据包超时未收到确认应答,当前窗口就会阻塞在原地,重新发送该数据包,在收到该重发数据包的确认应答前,就不会有新增的可发送数据包了。也就是说,因为某个数据包丢失,当前窗口阻塞在原地,同样阻塞了后续所有数据包的发送。
TCP 因为超时确认或丢包引起的滑动窗口阻塞问题,是不是有点像HTTP/1.1 管道化机制中出现的队头阻塞问题?HTTP/2 在应用协议层通过多路复用解决了队头阻塞问题,但TCP 在传输层依然存在队头阻塞问题,这是TCP 协议的一个主要性能瓶颈。该怎么解决TCP 的队头阻塞问题呢?
1.2 QUIC 如何解决队头阻塞问题
TCP 队头阻塞的主要原因是数据包超时确认或丢失阻塞了当前窗口向右滑动,我们最容易想到的解决队头阻塞的方案是不让超时确认或丢失的数据包将当前窗口阻塞在原地。QUIC (Quick UDP Internet Connections)也正是采用上述方案来解决TCP 队头阻塞问题的。
TCP 为了保证可靠性,使用了基于字节序号的 Sequence Number 及 Ack 来确认消息的有序到达。QUIC 同样是一个可靠的协议,它使用 Packet Number 代替了 TCP 的 Sequence Number,并且每个 Packet Number 都严格递增,也就是说就算 Packet N 丢失了,重传的 Packet N 的 Packet Number 已经不是 N,而是一个比 N 大的值,比如Packet N+M。
QUIC 使用的Packet Number 单调递增的设计,可以让数据包不再像TCP 那样必须有序确认,QUIC 支持乱序确认,当数据包Packet N 丢失后,只要有新的已接收数据包确认,当前窗口就会继续向右滑动。待发送端获知数据包Packet N 丢失后,会将需要重传的数据包放到待发送队列,重新编号比如数据包Packet N+M 后重新发送给接收端,对重传数据包的处理跟发送新的数据包类似,这样就不会因为丢包重传将当前窗口阻塞在原地,从而解决了队头阻塞问题。那么,既然重传数据包的Packet N+M 与丢失数据包的Packet N 编号并不一致,我们怎么确定这两个数据包的内容一样呢?
还记得前篇博文:HTTP/2 是如何解决HTTP/1.1 性能瓶颈的?使用Stream ID 来标识当前数据流属于哪个资源请求,这同时也是数据包多路复用传输到接收端后能正常组装的依据。重传的数据包Packet N+M 和丢失的数据包Packet N 单靠Stream ID 的比对一致仍然不能判断两个数据包内容一致,还需要再新增一个字段Stream Offset,标识当前数据包在当前Stream ID 中的字节偏移量。
有了Stream Offset 字段信息,属于同一个Stream ID 的数据包也可以乱序传输了(HTTP/2 中仅靠Stream ID 标识,要求同属于一个Stream ID 的数据帧必须有序传输),通过两个数据包的Stream ID 与 Stream Offset 都一致,就说明这两个数据包的内容一致。
上图中数据包Packet N 丢失了,后面重传该数据包的编号为Packet N+2,丢失的数据包和重传的数据包Stream ID 与 Offset 都一致,说明这两个数据包的内容一致。这些数据包传输到接收端后,接收端能根据Stream ID 与 Offset 字段信息正确组装成完整的资源。
QUIC 通过单向递增的Packet Number,配合Stream ID 与 Offset 字段信息,可以支持非连续确认应答Ack而不影响数据包的正确组装,摆脱了TCP 必须按顺序确认应答Ack 的限制ÿ