共计 6040 个字符,预计需要花费 16 分钟才能阅读完成。
能加速互联网的 QUIC 协议有哪些,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
众所周知,QUIC(Quick UDP Internet Connection)是谷歌制定的一种互联网传输层协议,它基于 UDP 传输层协议,同时兼具 TCP、TLS、HTTP/ 2 等协议的可靠性与安全性,可以有效减少连接与传输延迟,更好地应对当前传输层与应用层的挑战。下面将由低向上分层讨论 QUIC 协议的特点。
QUIC 协议是一系列协议的集合,主要包括:
传输协议(Transport)
丢包检测与拥塞控制(Recovery)
安全传输协议(TLS)
HTTP3 协议
HTTP 头部压缩协议(QPACK)
负载均衡协议(Load Balance)
基于 quic 的讨论均基于 quic-34 系列版本。
QUIC 协议类似快递公司,在收到用户数据后,将数据打包,传输到对端,再进行拆包,将用户数据交给了最终目标用户。QUIC 是基于 UDP 协议,实现了类似 TCP 的可靠传输,并在此基础上,结合 HTTP3/QPACK,更好地服务互联网上海量的 HTTP Request/Response 需求。如其名发音,QUIC(quick),其目标就是希望比基于 TCP 的 HTTP 交互有更好的体验。
QUIC/HTTP3 的特点:
有序传输:用 stream 的概念,确保数据有序。不同的 stream 或者 packet,不保证有序到达。
报文压缩,提高荷载比率:比如 QUIC 引入了 variable-length integer encoding。又比如引入 QPACK 进行头部压缩
可靠传输:支持丢包检测和重传
安全传输:TLS 1.3 安全协议
分层的协议
QUIC 是在 UDP 的基础上,构建类似 TCP 的可靠传输协议。HTTP3 则在 QUIC 基础上完成 HTTP 事务。
网络总是分层讨论的,在此我们由低向上分层讨论 quic 协议
UDP 层: 在 UDP 层传输的是 UDP 报文,此处关注的是 UDP 报文荷载内容是什么,以及如何高效发送 UDP 报文
Connection 层: Connection 通过 CID 来确认唯一连接,connection 对 packet 进行可靠传输和安全传输
Stream 层: Stream 在相应的 Connection 中,通过 StreamID 进行唯一流确认,stream 对 stream frame 进行传输管理
HTTP3 层:HTTP3 建立在 QUIC Stream 的基础上,相对于 HTTP1.1 和 HTTP2.0,HTTP3 提供更有效率的 HTTP 事务传输。HTTP3 中通过 QPACK 协议进行头部压缩
UDP 层
本章节讨论 QUIC 发包的 UDP 部分的相关问题。
UDP 荷载大小
荷载大小受限于 3 个对象:QUIC 协议规定;路径 MTU;终端接受能力
1、QUIC 不能运行在不支持 1200 字节的单个 UDP 传输网络路径上 QUIC 有规定 initial 包大小不得小于 1200,如果数据本身不足 1200(比如 initial ack),那么需要用 padding 方式至少填充到 1200 字节
2、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 的 MTU 为 1280,大于这个值,某些网络会存在丢包现象。
3、终端能接受 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 消耗
上面介绍的发送方式,事实上可以理解为 udp burst 发送方式,这带来了一个问题,拥塞控制需要 pacing 能力!
Connection 层
在我们讨论时,可知 1 个 udp 报文里传输的其实是一个或多个 quic 协议定义的 packet。那么在 Connection 这一层面,其实是以 packet 为单位进行管理的。一个 packet 到来,终端需要解析出目标 ConnectionID(DCID) 字段,并将该 packet 交给找到对应的 quic connection。一个 packet 是由 header 加 payload 两部分组成。
connection id
不同于 tcp 的 4 元组唯一确认一条连接的方式,QUIC 定义了一个和网络路由无关的 ConnectionID 来确认唯一连接的。这带来一个好处,可以在四元组发生变化时(比如 nat rebinding 或者终端网络切换 wifi- 4G),依然保持连接。当然,虽然连接状态依然保持,但由于路径发生变化,拥塞控制也需要能够及时调整。
packet 头部
IETF 的 quic header 分为两种类型,long header, short header。其中 long header 有分为 initial, 0rtt, handshake, retry 四种类型。类型的定义可以直接参考 rfc 文档,此处不再赘述。
quic 规定 packet number 始终为自增的,就算某个 packet 的内容为重传的 frame 数据,其 packet number 也必须自增,这相对于 TCP 来说,带来一个优点,能够更加精确的采集到路径的 RTT 属性。
packet number 编解码: packet number 是一个 0~262 - 1 的取值范围,quic 为了节约空间,在计算 packet number 时,引入了 unacked 的概念,通过截断(只保留有效 bit 位)的方式,只用了 1 - 4 个字节,即可以 encode/decode 出正确的 packet number。rfc 文档中有附录详细讲解了 enc/dec 的过程。
packet 头在安全传输中是被保护对象,这也意味着在没有 ssl 信息的情况下,无法使用 wireshake 对 packet 进行时序分析。中间网络设备也无法向 TCP 那样获得 packet number 进行乱序重组。
packet 荷载
在对 packet 进行解密,且去除掉 packet header 后,packet 的荷载里就都是 frame 了(至少包括 1 个)。
如果 packet 的荷载里,不包括 ACK, PADDING, and CONNECTION_CLOSE 这种三种类型的帧,那么这个 packet 则被定义为 ack-eliciting,意味着对端必须对这种 packet 生成相应的 ack 通知发送方,以确保数据没有丢失。
packet 的荷载里 frames 的类型在多达 30 种类型,每种类型都有自己的应用场景,如 ACK Frame 用于可靠传输(Recovery),Crypto 用于安全传输(TLS 握手),Stream Frame 用于业务数据传递,MAX_DATA/DATA_BLOCKED 用于流控,PING Frame 可以用于 mtu 探测,具体描述参考 rfc 文档。
安全传输
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 threshold 和 packet 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 层
stream 分类
在引入 HTTP3 后,stream 的单向流类型被扩展成:控制流,Push 流和其他保留类型。其中 HTTP3 的 setting 则是在控制流中传输,而 HTTP 数据传输是在客户端发起的双向流中,所以读者会发现,HTTP 数据传输的 stream id 都是模 4 等于 0 的。
在引入 QPACK 后,单向流被进一步扩展了两个类型,encoder 流,decoder 流,QPACK 中动态表的更新则依赖这两个流。
QPACK
QPACK 的作用是头部压缩。类似 HPACK,QPACK 定义了静态表,动态表用于头部索引。静态表是针对常见的头部,协议预先定义的。动态表则是在该 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 版本
QUIC 一开始由 google 主导设计开发,在 chromium 项目中,可以看到 google quic(GQUIC) 版本号被定义为 Q039,Q043,Q046,Q050 等。
随着 IETF 版本的 QUIC 推出,ietf quic(IQUIC) 也有很多版本,如 29,30,34(最新版) 等,不同版本可能是无法互通的,比如不同版本安全传输的 salt 变量规定不一样。所以 IQUIC 引入了版本协商的功能,用于不同的客户端和服务端协商出可以互通的版本。
在实践中,还会遇到一个需求,要求一个服务能够同时服务 GQUIC 的不同版本,又能服务 IQUIC 的不同版本。这就要求服务在收取到 packet 后,需要对 packet 作出判断,分析出它属于 iquic 的,还是 gquic 的,然后进行逻辑分流。
QUIC 应用及未来展望
目前阿里云 CDN 线上提供 GQUIC 版本服务,适用的产品包含静态内容分发(图片小文件、大文件下载、视音频点播)和动态内容分发(全站加速)。用户只需在 CDN、全站加速控制台对域名开启【QUIC 协议开关】功能,支持 QUIC 协议的客户端即可通过 QUIC 协议与阿里云 CDN 节点通信。
QUIC 应用场景
图片小文件:明显降低文件下载总耗时,提升效率
视频点播:提升首屏秒开率,降低卡顿率,提升用户观看体验
动态请求:适用于动态请求,提升访问速度,如网页登录、交易等交互体验提升
弱网环境:在丢包和网络延迟严重的情况下仍可提供可用的服务,并优化卡顿率、请求失败率、秒开率、提高连接成功率等传输指标
大并发连接:连接可靠性强,支持页面资源数较多、并发连接数较多情况下的访问速率提升
加密连接:具备安全、可靠的传输性能
看完上述内容,你们掌握能加速互联网的 QUIC 协议有哪些的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注丸趣 TV 行业资讯频道,感谢各位的阅读!