QUIC协议


简介

QUIC(Quick UDP Internet Connection)是谷歌制定的一种互联网传输层协议,它基于UDP传输层协议,同时兼具TCPTLSHTTP/2等协议的可靠性与安全性,可以有效减少连接与传输延迟,更好地应对当前传输层与应用层的挑战

QUIC协议是一系列协议的集合,主要包括:

  • 传输协议(Transport)
  • 丢包检测与拥塞控制(Recovery)
  • 安全传输协议(TLS)
  • HTTP3协议
  • HTTP头部压缩协议(QPACK)
  • 负载均衡协议(Load Balance)

QUIC是基于UDP协议,实现了类似TCP的可靠传输,并在此基础上,结合HTTP3/QPACK,更好地服务互联网上海量的HTTP Request/Response需求

QUIC/HTTP3的特点:

  • 有序传输:用stream的概念,确保数据有序。不同的stream或者packet,不保证有序到达。
  • 报文压缩,提高荷载比率:比如QUIC引入了variable-length integer encoding。又比如引入QPACK进行头部压缩
  • 可靠传输:支持丢包检测和重传
  • 安全传输:TLS 1.3安全协议

分层的协议

QUIC是在UDP的基础上,构建类似TCP的可靠传输协议。HTTP3则在QUIC基础上完成HTTP事务

  • UDP层: 在UDP层传输的是UDP报文,此处关注的是UDP报文荷载内容是什么,以及如何高效发送UDP报文
  • Connection层: Connection通过CID来确认唯一连接,Connectionpacket进行可靠传输和安全传输
  • Stream层: Stream在相应的Connection中,通过StreamID进行唯一流确认,StreamStream Frame进行传输管理
  • HTTP3层HTTP3建立在QUIC Stream的基础上,相对于HTTP1.1HTTP2.0HTTP3提供更有效率的HTTP事务传输。HTTP3中通过QPACK协议进行头部压缩

UDP层

UDP荷载大小

荷载大小受限于3个对象:QUIC协议规定 路径MTU 终端接受能力

QUIC不能运行在不支持1200字节的单个UDP传输网络路径上 QUIC有规定initial包大小不得小于1200,如果数据本身不足1200(比如initial ack),那么需要用padding方式至少填充到1200字节

QUIC不希望出现IP层分片现象本要求意味着UDP交给IP层的数据不会大于1个MTU,假设MTU为1500,ipv4场景下,UDP的荷载上限为1472字节(1500-20-8),ipv6下,UDP荷载上限为1452(1500-40-8)。
QUIC建议使用PMTUD以及DPLPMTUD进行MTU探测。在实战中,我们建议设置IPv6的MTU1280,大于这个值,某些网络会存在丢包现象

终端能接受 transport paraments的max_udp_payload_size(0x03)的是终端接受单个UDP包大小的能力,发送端应当遵从这一约定

UDP荷载内容

UDP荷载内容即为QUIC协议中的Packet。协议规定,如果不超过荷载大小的限制,那么多个Packet可以组成一个UDP报文发出去。在QUIC实现中,如果每个UDP报文只包含一个QUIC Packet,会更容易出现乱序问题

高效发UDP包

TCP不同,QUIC需要在应用层就完成UDP数据组装,且每个UDP报文不大于1个mtu,如果不加以优化,比如每个包直接用sendto/sendmsg发送,势必会造成大量的系统调用,影响吞吐

  1. 通过sendmmsg接口进行优化,sendmmsg可以将用户态的多个UDP QUIC包通过一次系统调用发到内核态。内核态对于每个UDP QUIC包独立作为UDP包发出去
  2. 1解决了系统调用次数问题,开启GSO可以进步一分包延迟到发给网卡驱动前一刻,可以进一步提高吞吐,降低CPU消耗
  3. 2的基础上,现在主流网卡已经支持硬件GSO offload方案,可以进一步提高吞吐,降低cpu消耗

Connection层

Connection这一层面,其实是以Packet为单位进行管理的。一个Packet到来,终端需要解析出目标ConnectionID(DCID)字段,并将该Packet交给找到对应的QUIC Connection。一个Packet是由HeaderPayload两部分组成

Connection ID

不同于TCP的4元组唯一确认一条连接的方式,QUIC定义了一个和网络路由无关的ConnectionID来确认唯一连接的。这带来一个好处,可以在四元组发生变化时(比如nat rebinding或者终端网络切换wifi->4G),依然保持连接。当然,虽然连接状态依然保持,但由于路径发生变化,拥塞控制也需要能够及时调整

Packet头部

分为两种类型,Long Header, Short Header。其中Long Header有分为 initial, 0rtt, handshake, retry四种类型

QUIC规定Packet Number始终为自增的,就算某个Packet的内容为重传的Frame数据,其Packet Number也必须自增,这相对于TCP来说,带来一个优点,能够更加精确的采集到路径的RTT属性

Packet Number是一个0~262 -1的取值范围,QUIC为了节约空间,在计算Packet Number时,引入了unacked的概念,通过截断(只保留有效bit位)的方式,只用了1-4个字节,即可以encode/decode出正确的Packet Number

Packet头在安全传输中是被保护对象,这也意味着在没有SSL信息的情况下,无法使用wireshake对Packet进行时序分析。中间网络设备也无法向TCP那样获得Packet Number进行乱序重组

Packet荷载

在对Packet进行解密,且去除掉Packet Header后,Packet的荷载里就都是Frame了(至少包括1个)

如果Packet的荷载里,不包括ACK, PADDING, CONNECTION\_CLOSE这种三种类型的帧,那么这个Packet则被定义为ack-eliciting,意味着对端必须对这种Packet生成相应的ACK通知发送方,以确保数据没有丢失

Packet的荷载里Frames的类型在多达30种类型,每种类型都有自己的应用场景,如ACK Frame用于可靠传输(Recovery),Crypto用于安全传输(TLS握手),Stream Frame用于业务数据传递,MAX\_DATA/DATA\_BLOCKED用于流控,PING Frame可以用于MTU探测等

安全传输

QUIC的安全传输依赖TLS1.3,而boringssl是众多QUIC实现的依赖库。协议对Packet的头部以及荷载均进行了保护(包括Packet Number)。
TLS1.3 0RTT的能力,在提供数据保护的同时,能在第一时间(服务端收到第一个请求报文时)就将Response Header发给客户端。大大降低了HTTP业务中的首包时间。为了支持0RTT,客户端需要保存PSK信息,以及部分Transport Parament信息

安全传输也经常会涉及到性能问题,在目前主流的服务端,AESG由于CPU提供了硬件加速,所以性能表现最好。CHACHA20则需要更多的CPU资源。在短视频业务上,出于对首帧的要求,通常直接使用明文传输

Transport Paramenter(TP)协商是在安全传输的握手阶段完成,除了协议规定的TP外,用户也可以扩展私有TP内容,这一特性带来了很大的便利,比如:客户端可以利用TP告知服务端进行明文传输

可靠传输

QUIC协议是需要像TCP能够进行可靠传输,所以QUIC单独有一个RFC描述了丢包检测和拥塞控制的话题

  • 丢包检测

协议利用两种方式来判断丢包是否发生:
一种是基于ACK的检测,通过time thresholdpacket threshold根据已经到达的Packet,推断在此包之前发出去的包是否丢失。
第二种,在失去了参考包的情况下,那么只能通过PTO的方式来推断包是否丢失。一般来说,大量被触发的应该是ACK的检测方式。如果PTO被大量触发,会影响发包效率

  • 拥塞控制

QUIC针对TCP协议中的一些缺陷,专门做了优化。比如始终递增的Packet Number,丰富的ack range,host delay计算等。
同时TCP的拥塞控制需要内核态实现,而QUIC在用户态实现,这大大降低了研究高效率的可靠传输协议的门槛。Recovery协议中,描述了newReno的实现方式。
Google Chrome中,实现了cubic, bbr, bbrv2,而mvfst项目则更为丰富,包括了ccp, copa协议

Stream层

Stream是一个抽象的概念,它表达了一个有序传输的字节流,而这些字节其实就是由Stream Frame排在一起构成。在一个QUIC Connection上,可以同时传输多条流

Stream头部

QUIC协议里,Stream分为单向流或双向流,又分为客户端发起或服务端发起。Stream的不同类型定义在HTTP3中得到了充分的利用

Stream荷载

Stream的荷载即为一系列Stream Frame,通过Stream Frame头部的Stream ID来确认单个流。

TCP里,如果一个Segment传递丢失,那么后续Segment乱序到达,也不会被应用层使用,只到丢失的Segment重传成功为止,因此TCP实现的HTTP2的多路复用能力受到制约。
QUIC协议中,有序的概念仅维护在单个Stream中,Stream之间和Packet都不要求有序,假设某个Packet丢失,只会影响包含在这个包里的Stream,其他Stream仍然可以从后续乱序到达的Packet中提取到自己所需要的数据交给应用层

HTTP3层

在引入HTTP3后,Stream的单向流类型被扩展成:控制流,Push流和其他保留类型。其中HTTP3的setting则是在控制流中传输,而HTTP数据传输是在客户端发起的双向流中,所以读者会发现,HTTP数据传输的Stream ID都是模4等于0的

引入QPACK后,单向流被进一步扩展了两个类型,encoder流,decoder流,QPACK中动态表的更新则依赖这两个流

QPACK的作用是头部压缩。类似HPACKQPACK定义了静态表,动态表用于头部索引。静态表是针对常见的头部,协议预先定义的。
动态表则是在该QUIC Connection服务HTTP过程中,逐渐建立的。QPACK所建立的Encoder/Decoder流是伴随用于HTTP事务的QUIC Connection生命周期。
动态表不是HTTP3能够运行的必须项,所以在某些QUIC开源项目中,并没有实现复杂的动态表功能

QPACK的动态表业务中,数据流,编码流,解码流3种对象共同参与,编码流和解码流负责维护动态表变化,数据流则解析出头部的索引号,去动态表中查询,得到最终的头部定义

其它

Flow Control 流控

QUIC协议引入了Flow Control的概念,用于表达接收端的接受能力。流控分两级,Connection级别,和Stream级别。
发送端发送的数据偏移量不能超过流控的限制,如果达到限制,那么发送端应该通过 DATA\_BLOCKED/STREAM\_DATA\_BLOCKED来通知接收端。
如果为了传输性能,接收端应该尽量保持限制足够大,比如达到MAX\_DATA的一半时,就及时更新MAX\_DATA传给发送端。如果接收端不希望太快接受数据,也可以利用流控对发送端进行约束

QUIC版本

不同版本可能是无法互通的,比如不同版本安全传输的salt变量规定不一样。所以IQUIC引入了版本协商的功能,用于不同的客户端和服务端协商出可以互通的版本

要求一个服务能够同时服务GQUIC的不同版本,又能服务IQUIC的不同版本。这就要求服务在收取到Packet后,需要对Packet作出判断,然后进行逻辑分流


本文不允许转载。
  目录