QUIC 网络传输协议
小结:
1)跟 TCP 用四元组标识一个唯一连接不同,QUIC 使用一个 64 位的 ConnectionID 来标识连接,基于这个特点,QUIC 的使用连接迁移机制,在四元组发生变化时(比如客户端从 WIFI 切换到蜂窝网络),尝试“保留”先前的连接,从而维持数据传输不中断。
提速 30%!腾讯TQUIC 网络传输协议 https://mp.weixin.qq.com/s/Sf8JsZKeZYxT9WBZrh_etg
提速 30%!腾讯TQUIC 网络传输协议
作者:腾讯 sTGW-TQUIC
腾讯sTGW如何助力核心业务用户登录耗时降低30%,下载场景500ms内请求成功率从HTTPS的60%提升到90%,移动端APP在弱网、跨网场景下同样取得媲美正常网络的用户体验。
腾讯核心业务用户登录耗时降低 30%,下载场景 500ms 内请求成功率从 HTTPS 的 60%提升到 90%,腾讯的移动端 APP 在弱网、跨网场景下取得媲美正常网络的用户体验。这是腾讯网关 sTGW 团队打造的 TQUIC 网络协议栈在实时通信、音视频、在线游戏、在线广告等多个腾讯业务落地取得的成果。TQUIC 基于下一代互联网传输协议 HTTP3/QUIC 深度优化,日均请求量级突破千亿次,在腾讯云 CLB、CDN 开放云客户使用。
本文重点分享了 sTGW 团队在协议栈基础能力、私有协议、明文传输等功能,并且针对弱网场景,分享腾讯如何基于 0-RTT 握手、连接迁移、实时传输等能力帮助业务用户体验提升。
一、 QUIC/HTTP3 协议介绍
QUIC 全称 quick udp internet connection,“快速 UDP 互联网连接”,(和英文 quick 谐音,简称“快”)是由 Google 提出的基于 UDP 进行可靠传输的协议。QUIC 在应用层实现了丢包恢复、拥塞控制、滑窗机制等保证数据传输的可靠性,同时对传输的数据具备前向安全的加密能力。HTTP3 则是 IETF(互联网工程任务组)基于 QUIC 协议基础进行设计的新一代 HTTP 协议。
QUIC/HTTP3 分层模型及与 HTTP2 对比:
二、 QUIC 核心优势是什么?
1. 0-RTT 建立连接
QUIC 基于的 UDP 协议本身无需握手,并且它早于 TLS 1.3 协议,就实现了自己的 0-RTT 加密握手。下图分别代表了 1-RTT 握手(首次建连),成功的 0-RTT 握手,以及失败回退的握手。
2.无队头阻塞的多路复用
相比于 HTTP/2 的多路复用,QUIC 不会受到队头阻塞的影响,各个流更独立,多路复用的效果也更好。
3.连接迁移
跟 TCP 用四元组标识一个唯一连接不同,QUIC 使用一个 64 位的 ConnectionID 来标识连接,基于这个特点,QUIC 的使用连接迁移机制,在四元组发生变化时(比如客户端从 WIFI 切换到蜂窝网络),尝试“保留”先前的连接,从而维持数据传输不中断。
4.全应用态协议栈
QUIC 核心逻辑都在用户态,能灵活的修改连接参数、替换拥塞算法、更改传输行为。而 TCP 核心实现在内核态,改造需要修改内核并且进行系统重启,成本极高。
三、 QUIC 网络协议栈的选型
虽然 QUIC 各个特性看上去很美好,但需要客户端/服务端的网络协议栈都支持 QUIC 协议。截止目前,除 iOS 15 在指定接口 NSURLSession 及限制条件前提下,支持了 HTTP3,其他系统及主流网络库均不支持 QUIC。
如何让业务快速将 QUIC 协议用起来,用这些先性加速网络性能?QUIC 协议栈的实现成本非常高,主要体现在两方面:
-
实现复杂度很高,如上面介绍,QUIC/HTTP3 横跨传输层、安全、应用层,相当于要把 TCP+TLS+HTTP 重新实现一次。
-
QUIC 一直保持着高速发展,分为 gQUIC(Google QUIC)、iQUIC(IETF-QUIC)两大类,衍生的 QUIC 子版本有几十个。
为了快速把 QUIC 协议落地,给业务提升网络性能,我们选择了开源的 Chromium cronet 网络协议栈作为基础。Chomium,作为占领全球浏览器绝对地位的 Chrome 的开源代码,有 Google 强大的研发团队支撑,其网络协议栈是一个相对独立的组件,被称为 Cronet。
- 协议栈完整性:完善的 QUIC 协议栈,还包括 HTTP2/WEBSOCKET/FTP/SOCKS 协议;
- QUIC 版本支持:支持 gQUIC 和 iQUIC,并且还在不断保持更新;
- 跨平台性:非常好,基于 chrome 的跨平台能力,对于各类操作系统、终端都有适配。
四、QUIC 协议栈的工程实践
Cronet 能直接用起来吗?结合我们的实践与业务同学的反馈,直接使用的问题和接入困难度是比较大的。
问题一:代码体积过大,逻辑层级多,不利于集成和安装包体积控制(移动端)
Cronet 核心及关联的第三方库代码有大概 85w 行,涉及 2800 多个类。但其实 Cronet 里大部分代码都与 QUIC 没有关系,由于其作为浏览器的网络协议栈,集成了大量浏览器行为逻辑,而这些能力对于网络协议栈是不需要的。
其次,QUIC 协议只是 Cronet 里众多通信协议之一,除 QUIC 外的其他协议,通用的平台或软件(例如 Nginx)本身就已经有实现,没有必要重复建设,这些协议的存在除了增加协议栈内部逻辑复杂度,还增大了整个库的体积,例如在安卓平台上,cronet 动态库的体积接近 3MB,这对于一些体积敏感的应用是一个巨大的挑战。
针对体积问题,我们进行了代码精简和 lib 体积缩减的探究。
第一步,分析归纳
通过对 cronet 代码的分析和理解,冗余的代码被我们分成了三种:
-
无用的内部逻辑,例如 HTTP 模块里包含了很多浏览器才会用到的代码和功能;
-
无需用到的的协议,例如 FTP、Websocket 等;
-
与 quic 无关的功能模块,例如 tcp 连接池等。
第二步,代码裁剪
针对分析归纳中的三种问题,我们做了针对性的裁剪。首先是精简了关键类,例如协议管控的类中,核心流程步骤被从 21 步压缩到了 5 步,函数数量从 146 个减少到 24 个,将浏览器相关的冗余逻辑去除。
接着对用不到的协议类型、模块组件做了剔除。裁剪后的效果如下:
五、打磨协议栈的易用性
虽然在工程方向,解决了体积大小和编译集成的问题。实际接入使用时,Cronet 的易用性依然不够好。
通过挖掘业务需求,寻找共性,我们整理出了如下痛点:
在通道直连方面,我们将底层 udp socket 粒度的接口进行封装后直接对外可见,用户可通过 socket 粒度的接口直接发起 IP 直连的 QUIC 请求,同时也保留了 DNS 建连能力,在保持原生能力的同时,拓展了用户的使用场景。
在网络参数配置和性能数据打点能力上,我们深入协议栈细节,逐个分析了多个核心模块,将关键的参数和性能数据抽象出来。并且在控制面上将配置参数、性能打点整合对外呈现。
六、进阶之路:私有协议和明文传输
除了基础功能的打磨,TQUIC 协议栈也提供了进阶功能进一步满足业务丰富的使用需求。在 Cronet 中,要想使用 QUIC 协议,应用层传输的报文必须是 HTTP,也就是所谓的 HTTP3 协议。但 HTTP 报文对于游戏、音视频等业务是个巨大的阻碍,它们当前都是通过 TCP 或者 UDP 传输自定义的协议的,如果为了接入 QUIC 而把应用层数据从私有协议强行改为 HTTP3,无疑是本末倒置。另外,由于是自定义协议,这些报文一般不需要 QUIC 进行加密,但加密是 QUIC 协议的标配,这会消耗额外的性能。
为此,在仔细研究了 Quic 核心代码后,我们研发对私有协议、明文传输的支持,来满足业务传输自定义协议的需求。首先是在 QuicStream 中,允许 stream 直接收发数据报文,HTTP 流程只是其中一个选择。
为了实现明文传输,如果直接去改加解密流程,对代码的入侵较大,如果考虑不周容易引入未知风险。为了尽量较少代码入侵以及维护原生实现的安全运行,我们将 QuicFramer 中的加解密套件选择处进行了 hook,引入了 FakeEncrypt/FakeDecrypt 替换真实的加解密套件,以极小的入侵代价低成本的实现了明文传输。
在做完明文传输方案后,我们意识到由于这是一个非常底层的修改,对于客户端和服务端来需要高度一致的,要么双端都选择加密,要么双端都选择明文。如果双端不统一,则握手就会失败。为了使兼容性更好,减少运维成本和失败风险,我们在握手协商过程中,加入了明文传输的协商。
如下图流程,当前的握手过程,使用了 AEAD 这个 tag 标识了待协商的加密算法。
改进后,AEAD 可以携带明文的加密算法,客户端如果也认可,则在下一次 CHLO 中选择该算法,则之后两边都进行明文传输。
七、弱网优化之连接迁移
随着移动互联网的发展,移动端 APP 的能力越来越强大,我们可以轻松通过手机进行会议通话、视频直播、在线游戏等,这类应用实时性要求很高。而移动端的特点是网络会经常发生变化,例如通过手机进行在线会议的同时,从办公区走到电梯,随着网络信号衰退到切换,APP 需要重新建立连接,这会触发用户重新登录,体验很差。前面背景介绍的 QUIC 连接迁移可以做到跨网场景自动迁移,业务无任何感知。那是不是使用 QUIC 后就可以直接用起来呢?
我们以 Google cronet 为例,在 iOS 下使用 Cronet 进行了一次切网尝试,通过抓包发现网络切换后,quic 其实进行了重新握手,并没有如预期内的进行连接迁移。
通过代码分析原因后,我们发现 Google 其实并没有将连接迁移在各个系统版本上进行支持,仅在安卓平台“打折扣”的支持了,他们是在 java 层注册系统通知才能使用连接迁移,对于 native 的网络库以及其他操作系统是很不友好的。既然原生实现并不好用,经过我们的改造,使用跨平台通用的内核层调用,达到了预期的效果,并且不依赖任何特定的操作系统组件。
在逐步的实践过程中,我们还发现,如果仅仅是切网时候发起连接迁移并不完美。有时候客户端处在弱网环境,wifi 信号并未彻底断开,但传输数据实际上已经有损。为了解决这种场景下的问题,我们建立了一套弱网评估模型,通过主动探测和弱网评估进行启发式分析,在数据通道有损的情况下,尽可能早的主动进行连接迁移,减少对上层业务的影响。
通过研发跨平台通用的连接迁移,以及启发式主动迁移能力,最终在客户端看到的连接迁移效果如下,当切网发生时,连接并未断开,无需重连即可续传数据。
业务在使用 QUIC 连接迁移后,对网络切换无感知,数据不中断。
或许有同学发现了,这里仅仅讲了连接迁移在客户端上的实现和效果,毕竟连接迁移后,客户端源 IP 发生了变化,云端接入层难道不需要做适配吗?答案是需要的,sTGW 作为公司云 CLB 和自研的主要七层网关接入,在 QUIC 连接迁移方面做了完整的解决方案,目前也已经将能力开放出来,在 QUIC 集群上全量上线。sTGW 对于连接迁移的解决方案可参见sTGW 大规模运营 QUIC 之路。
八、弱网优化之完全 0-RTT 握手与金融级前向安全
如下是 GQUIC 的握手流程,原生实现里,应用发生冷启动时,首先会进行 1RTT 握手,拿到服务端的证书和 ServerConfig(简称 SCFG),随后 SCFG 会被作为随机数用于生成非前向安全的密钥。
因此在下一次发送数据包时,便可用非前向安全的密钥进行加密。直到收到了服务端发来的 Server Hello 后,通过类似 DHE 算法生成前向安全秘钥,自此开始发送前向安全的数据包。
在原生实现中,SCFG 会被临时存储在进程内存堆区,下次发起新连接或者热启动时,直接就能进入 0-RTT 流程,但冷启动则永远是 1-RTT。基于原生的实现情况,我们针对握手流程做了两个方向的优化。
-
实现 100% 0-RTT 成功率,用于在握手时延极其敏感的业务上,例如广告请求、API 调用、短连接下载等。
-
强制前向安全,针对安全敏感型业务,例如金融业务,0-RTT 中发送的非前向安全数据包风险远高于前向安全数据包,因此对于金融型业务,可以强制 1-RTT 握手生产前向安全密钥后,再发送数据包。
改进后的两种握手流程如下图:
九、弱网优化之实时传输
实时传输是 QUIC 的一个拓展功能,目前在 IETF 草稿阶段。实时传输适用于对数据可靠性要求不高,但非常注重数据实时性的业务。例如音视频传输、互动游戏等。实时传输在 QUIC 中的定位,以及与可靠传输的区别如下:
相同点:
-
在 QUIC 连接建立、创建 QUIC 数据包、数据加解密这些基础功能,不可靠数据与可靠数据都是共用的。
-
不可靠传输也有拥塞控制、ACK 机制,与可靠传输一致。
不同点:
-
不可靠数据不受滑动窗口限制,滑窗窗口满只限制可靠数据传输。
-
发生丢包重传时,只重传可靠数据帧,不可靠数据帧不进行重传。
不可靠数据没有 quic stream 概念,只是 frame 粒度。这其中,一个关键点在于数据是否重传,IETF 草稿的定义对这块比较开放,可以完全不重传,也可以选择性重传。
为此,TQUIC 在实现实时传输时,做了灵活的改造,对于实时传输的数据,提供多种重传策略供使用者选择,可以完全不重传,也可以选择性重传某个重要的数据(比如关键帧),我们也在尝试做动态重传控制,依托我们的弱网判断模型,动态调整重传策略。
十、总结
经过腾讯 sTGW 团队持续投入。TQUIC 在腾讯多个重要业务落地,覆盖业务包括实时通信、音视频、在线游戏、在线广告等。在登录成功率、登录耗时、握手时延、下载速度等性能指标上均取得了明显收益。腾讯云客户通过 CLB 使用 HTTP3 后,延迟降低了超过 20 个点,也欢迎大家接入使用。
附部分业务效果总结如下:
参考列表:
- https://datatracker.ietf.org/doc/html/rfc9000
- https://datatracker.ietf.org/doc/draft-ietf-quic-datagram/?include_text=1
- https://docs.google.com/document/d/1g5nIXAIkN_Y-7XJW5K45IblHd_L2f5LTaDUDwvZ5L6g/edit
- https://dl.acm.org/doi/abs/10.1145/3098822.3098842