Quic与HTTP3.0

1,592 阅读11分钟

聚焦于现行的基于TCP/IP协议的HTTP/2存在的一些问题,在问题的基础上引出基于UDP/IP协议的HTTP/3和新产生的QUIC协议,内容包括:

  1. TCP的可靠数据传输、流量控制原理
  2. TCP的拥塞控制原理
  3. TCP带来的一些问题
  4. HTTP版本的迭代、优化做的一些努力,以及TCP本身的一些无法解决的问题
  5. HTTP3和QUIC协议

一、TCP原理和存在的问题

传统架构上的应用开发,基本上都是在TCP (传输控制协议) 和UDP (用户数据协议) 之上二选一,这二者都基于IP协议开发,区别在于TCP提供面向字节流的可靠传输服务,而UDP则提供面向报文的尽力交付服务

但是相同之处在于二者都是基于IP协议实现的,在真正传输时,无论是TCP的数据块还是UDP报文都会被封装在一个IP分组的荷载中进行传输。

TCP将上层的数据看做是一串无序的字节流,会按照一定的大小为它切块,划分缓冲区。其中每一块都会使用一个序号来进行标注。而面向报文是指,UDP不会将上层的数据做进一步的划分,而是将它完整地转发出去。

1.1 TCP协议的优缺点

  • TCP特性: 根据应用区分(端口区分)、实现可靠数据传输,拥塞控制和流量控制。
  • 优点: 开发效率高、不需要额外考虑差错控制。
  • 缺点:
  1. TCP本身的特性制约了传输的效率
  2. TCP的管理是由操作系统实现的,更新迭代的成本较高

实际上,HTTP将TCP作为下层的协议,它在1.1/2.0等版本中做了大量的工作,试图提高HTTP的效率,但是受限于TCP固有的特性,难以取得突破。

1.2 TCP 可靠传输原理

TCP通过序号、确认、重传来保证可靠传输。

TCP端到端之间,采用缓冲区来暂存正在发送的数据,一旦数据发送出现问题(丢包导致的超时、冗余ACK等等),TCP发送端能够及时地从缓冲区中选择数据重发。

TCP为每一个TCP报文分配一个序号 seq = a,接收方接收到序号为a的报文,就响应一个ack = a + 1的ACK响应报文来确认,如果发生了丢包导致的计时器超时、收到冗余ACK,那么会触发重传机制,重发对应的数据包。

1.3 TCP存在的一些问题

1.3.1 TCP连接耗时严重

我们知道,基于TCP的HTTP/2链接,需要经过三次握手,这时候消耗掉一个RTT(如下图1),而HTTPs在TCP的基础上,又使用了TLS1.2进行数据加密(如下图4、5),需要消耗2个RTT,这样一来,在建立连接时,光是TCP + TLS过程就需要消耗掉3个RTT的时间。

这其中最大的问题就是,TCP的确认帧需要经过一个完整的RTT才可能被接收到,而TCP一个时刻发送的数据的数量会被拥塞窗口所限制,假设ssthresh设置为16,那么甚至需要5个RTT才能跑满基础的拥塞窗口,如果现实网络会在32时产生拥塞,那么根据拥塞避免算法,需要5 + 16个RTT才能最大化利用网络的带宽和吞吐量。

1.3.3 TCP序号的二义性

TCP协议采用序号来标记TCP发送的数据,当发送方发送seq = a的报文到接收端时,接收方会采用一个ack = a + 1的ACK报文来响应这个数据,当发送方接到这个ack = a + 1的应答报文时,就说明该帧被成功接收了。

如上图,TCP在Initial阶段发出的帧序号为a,而经过一段时间之后,没有收到ACK,此时触发超时重传,过了一会儿收到了一个ACK帧,但是ACK帧的内容是a + 1,此时无法确认该a + 1是对initial send还是retry帧的确认(图左是对Retry的确认,图右是对Initial Retry的确认,但是到达的比较晚,已经触发了超时重传)。导致最终的超时时间计算并不准,后续超时算法的迭代时间就会产生问题,比如 触发超时重传的时间可能会不准。

1.3.4 TCP内核态实现问题

TCP的处理依赖于操作系统,我们难以对它进行升级。虽然TCP头部中,提供一个Option字段,它提供了最长40字节的额外数据空间,我们可以将一些升级字段放置在Options中。

但是这个空间不仅仅不够用,而且一些系统的防火墙会将Options中的一些字段信息过滤掉,这是我们应用层难以控制的。

1.3.5 TCP队头堵塞的问题

由于TCP必须在一个请求的所有分片完成之后,才能将所有的数据提交给上层,所以TCP存在队头阻塞的问题。如下的缓冲区,如果3始终无法完成交付,那么会导致该TCP链接一直在处理3所述的应用层请求(通常是HTTP)。

二、HTTP协议所作的工作和主要问题

HTTP协议做了大量的工作,试图提高基于TCP/IP协议栈的网路效率,包括但不限于:

  1. HTTP/1.1 中的管道化,在一个TCP连接的基础上支持多个对象的请求和响应;支持缓存(头部的if-modify-since)。
  2. HTTP/2中的多路复用,引入流的概念,流就相当于请求/响应,HTTP2的内部采用一个流ID来标识一个请求,它采用分流技术,解决了HTTP本身的队头阻塞问题,但是TCP层面的队头阻塞问题仍然存在。

  1. HTTP/2采用二进制编码替代ASCII编码,节省编解码的时间;采用服务器推送技术,主动推送客户端可能需要的资源。

但是无论怎么修改,只要基于TCP协议,那么就会有如下的问题:

  1. TCP协议本身实现可靠传输的工作效率低下(基于确认)
  2. 建立链接成本高:基础三次握手需要消耗1个RTT,而后续的TLS加密协议需要消耗2个RTT,即广泛使用的HTTPs建立连接需要3个RTT才能完成建立数据传输链路;传输还需要经过慢开始等等阶段才能到达满速。
  3. 存在队头堵塞问题
  4. 内核态,协议细节无法做到热插拔的设计
  5. TCP四元组带来的IP地址变动链接恢复问题(重新建立连接,重新握手等等)

三、另辟蹊径 —— 拆分TCP

既然TCP的协议本身所带来的瓶颈无法被打破,那么是否能够将HTTP/2从TCP协议整体复刻到UDP之上,自己实现差错控制和可靠性传输呢?

TCP主要的功能可以拆分成两个层次:对来自网络层数据的传输控制

  • 控制 指的是,对整个链路的流量控制、可靠传输。
  • 传输 指的是将TCP报文从IP分组中提取出来,并转交给上层;

控制显然在传输之上,并且相比较控制来说,传输是更加稳定且不容易被修改的。而控制算法是多种多样的,不同的业务场景、环境下,所需要的控制算法不一定是相同的。

如果剥离控制和传输,那么显然,采用更加简短、高效的UDP作为传输层协议是更好的选择。而可靠传输?自然是交给更高的层次去做了,这样有个明显的好处。更高的层次通常是基于用户态的,系统只需要聚焦于数据的底层传输,而不介入可靠传输,可以低成本地采用不同的可靠传输方式和算法。我们似乎可以新建一个独立的层来做传输的控制,既不侵入业务,又能够实现灵活的控制。

3.1 HTTP/3

HTTP3的实现思路是:HTTP2 Slimmed + Quic协议

将HTTP/2的多路复用/二进制编码/头部压缩等工作下沉,TCP的拥塞控制/可靠传输上浮,实现一个相对独立的层次,该层次介于传输层和应用层之间。(应用层针对数据的服务下沉,而传输层针对数据的服务上浮,形成了一个特有的HTTP3/QUIC/UDP/IP协议栈)

实际上,之前Google已经有一些服务采用了这种架构方案,将服务从TCP搬迁到UDP之上,在2021年5月,IETF推出相关的标准:RFC9000,它就是Quic协议。

它所处的位置在HTTP/2之下,UDP之上,它仍然要平级依赖于TLS提供的加密安全服务。

3.2 Quic协议的特点

  • Quic仍然采用流的概念来管理请求,但是它不会存在TCP头端阻塞的问题,因为本身不存在请求队列这一结构,也不会存在有序的流执行的顺序,自然也不会阻塞

  • Quic支持快速握手,如果搭配TLS1.3协议,在之前建立过连接的情况下,只需要0个RTT即可开始发送数据。(HTTP/2需要3个RTT)
  • 高效的差错恢复/无状态重试,以及可插拔的拥塞控制机制
  • 可支持连接迁移技术
  • 基于用户态实现,不需要更新操作系统即可更新Quic相关的内容

3.3 Quic的一些实现细节

HTTP/2采用 IP地址+端口的 套接字来标记一个终端。但无论是IP地址还是端口号,可能都是会发生改变的,IP地址可能会因为我们的手机接入AP或者是WiFi就发生改变,或者是NAT路由的私有/公有地址映射发生改变。而端口可能因为端口被占用而发生改变。因此,用这种方式来标记一个处于稳定情况下的连接会话是可行的,但是一旦用户的网络环境频繁发生变化,那么将会无法维护一个稳定的TCP连接。

  • 连接迁移

而Quic抛弃了原有的传输层四元组(两个套接字),采用了一个Connection ID的形式来标记端。只要对方的IP地址不变(比如服务器),使用Connection ID,能够无缝地实现发送端的地址更改,在不需要建立连接的情况下,快速实现连接迁移。(因为不需要重新握手)

  • 快速握手

本义指的是QUIC不需要额外建立连接,初次握手需要1个RTT进行握手,而后续情况下,可以将连接的一些细节保存,直接复用上一次连接的数据实现0-RTT快速握手。

  • 高效地恢复丢失

TCP的SACK选项能够告诉发送方已经接收到的连续 Segment 的范围,方便发送方进行选择性重传。TCP最多只能提供30个字节的空间给sAck,而每一个sack Block的长度为8,Sack Option头部为2个字节,这样一来,30 - 2 = 28,28 / 8 = 3,最多只能提供3个完整的Block给sAck字段使用。

而ACK帧可以同时提供256个Ack Block,在丢包率比较高的网络下,更多的 Sack Block 可以提升网络的恢复速度,减少重传量。

  • 解决TCP序号二义性问题

无论是正常还是重传的数据,其序号本身一定是单调递增的,这样一来即使是不同的序号也有可能数据内容是相同的(一个是正常帧,一个是超时重传帧),这样一来统计的超时时间是准确的。

  • 安全性

QUIC与TLS1.3配合,实现身份认证,密钥共享。而TLS1.3通过QUIC传递数据包。支持1-RTT完成TLS握手,在建立握手之后,最优情况下,支持0-RTT恢复握手,在建立连接时节省了往返时间。

参考来源
  1. TCP 拥塞控制算法 - 知乎 (zhihu.com)
  2. 中科大郑烇全套《高级计算机网络》_哔哩哔哩_bilibili
  3. 这一篇,带你掌握HTTPS
延伸阅读
  1. HTTP3.0和QUIC协议那些事_wolfGuiDao的博客-CSDN博客_quic协议交互
  2. 【帧和流的一些细节】Http系列(二) Http2中的多路复用 - 小锅炖豆腐 - 博客园
  3. HTTP/2 协议 - 如果的事 - 博客园