自 2015 年以来,QUIC 协议开始在 IETF 进行标准化并被国内外各大厂商相继落地。鉴于 QUIC 具备“0RTT 建联”、“支持连接迁移”等诸多优势,并将成为下一代互联网协议:HTTP3.0 的底层传输协议,蚂蚁集团支付宝客户端团队与接入网关团队于 2018 年下半年开始在移动支付、海外加速等场景落地 QUIC。
本文是综述篇,介绍 QUIC 在蚂蚁的整体落地情况。之所以是综述,是因为 QUIC 协议过于复杂,如果对标已有的协议,QUIC 近似等于 HTTP + TLS +TCP,无法详细的毕其功于一役,因此我们通过综述的方式将落地的重点呈现给读者,主要介绍如下几个部分:
本文也是 QUIC 协议介绍的第一篇,后续我们会把更多的落地细节、体验优化手段、性能优化手段、安全与高可用、QUIC 新技术等呈现给大家。
【注】蚂蚁 QUIC 开发团队包括:支付宝客户端团队的梅男、苍茫、述言,以及接入网关的伯琴、子荃、毅丝。
QUIC 背景介绍
鉴于读者的背景可能不同,在开始本文之前,我们先简单介绍下 QUIC 相关的背景知识,如果您对这个协议的更多设计细节感兴趣,可以参见相关 Draft:https://datatracker.ietf.org/wg/quic/documents/
简单来说,QUIC (Quick UDP Internet Connections) 是一种基于 UDP 封装的安全 可靠传输协议,他的目标是取代 TCP 并自包含 TLS 成为标准的安全传输协议。下图是 QUIC 在协议栈中的位置,基于 QUIC 承载的 HTTP 协议进一步被标准化为 HTTP3.0。
在 QUIC 出现之前,TCP 承载了 90% 多的互联网流量,似乎也没什么问题,那又为何会出现革命者 QUIC 呢?这主要是因为发展了几十年的 TCP 面临 “协议僵化问题”,表现在几方面:1. 网络设备支持 TCP 时的僵化,表现在:对于一些防火墙或者 NAT 等设备,如果 TCP 引入了新的特性,比如增加了某些 TCP OPTION 等,可能会被认为是攻击而丢包,导致新特性在老的网络设备上无法工作。 2. 网络操作系统升级困难导致的 TCP 僵化,一些 TCP 的特性无法快速的被演进。 3. 除此之外,当应用层协议优化到 TLS1.3、 HTTP2.0 后, 传输层的优化也提上了议程,QUIC 在 TCP 基础上,取其精华去其糟粕具有如下的硬核优势:
下图是 QUIC 从创建到现在为止的一些比较重要的时间节点,2021 年,QUIC V1 即将成为 RFC,结束百花齐放的态势。
介绍完 QUIC 相关背景,之后我们来介绍蚂蚁的整个落地的内容,这里为了便于阐述,我们用蚂蚁 QUIC 的 一、二、三、四 来进行概括总结,即 “一套落地框架”、“两个落地场景”、“三篇创新专利保护”、“四项关键技术”。
一套落地框架
蚂蚁的接入网关是基于多进程的 NGINX 开发的 (内部称为 Spanner,协议卸载的扳手),而 UDP 在多进程编程模型上存在诸多挑战,典型的像无损升级等。为了设计一套完备的框架,我们在落地前充分考虑了服务端在云上部署上的方便性、扩展性、以及性能问题,设计了如下的落地框架以支撑不同的落地场景:
在这套框架中,包括如下两个组件:
当前框架支持的能力包括如下:1. 在不用修改内核的情况下,完全在用户态支持 QUIC 的连接迁移,以及连接迁移时 CID 的 Update 2. 在不用修改内核的情况下,完全在用户态支持 QUIC 的无损升级以及其他运维问题 3. 支持真正意义上的 0RTT ,并可提升 0RTT 的比例
为何能支持上述能力,我们后面会展开叙述
两个落地场景
我们由近及远的两个落地场景如下:
场景一、支付宝移动端落地
如下为我们落地架构的示意图,支付宝手机客户端通过 QUIC 携带 HTTP 请求,通过 QUIC LB 等四层网关将请求转发到 Spanner (蚂蚁内部基于 NGINX 开发的 7 层网关),在 Spanner 上我们将 QUIC 请求 Proxy 成 TCP 请求,发送给业务网关(RS)。
具体的方案选型如下:
a . Backup 模式,即在 TCP 链路无法使用的情况下,降级到 QUIC 链路。 b . Smart 模式,即 TCP和 QUIC 竞速,在 TCP 表现力弱于 QUIC 的情况下,下次请求主动使用 QUIC 链路。
在此场景下,通过使用 QUIC 可以获得的红利包括:
场景二、海外加速落地蚂蚁集团从 2018 年开始自研了海外的动态加速平台 AGNA(Ant Global Network Accelerator)以替换第三方厂商的加速服务。AGNA 通过在海外部署接入点:Local Proxy(LP) 以及在国内部署接出点:Remote Proxy (RP)的方式,将用户的海外请求通过 LP 和 RP 的加速链路回源国内。如下图所示,我们将 QUIC 部署在 LP 和 RP 之间的链路。
在海外接入点上(LP),每一个 TCP 连接都被 Proxy 成 QUIC 上的一个 Stream 进行承载,在国内接出点上(RP), 每一个 QUIC Stream 又被 Proxy 成一个 TCP 连接,LP 和 RP 之间使用 QUIC 长连接。
在此场景下,通过使用 QUIC 可以获得的红利包括:
三篇关键专利
到目前为止,我们把落地过程中一些创新的技术点通过申请专利进行了保护,并积极在 IETF 进行标准化分享我们的研究成果,包括:
我们将落地场景 2 中,通过 QUIC Stream 进行四层代理的手段来进行海外回源的加速方法进行专利保护,提出:“一种基于 QUIC 协议代理的的链路加速方法”,目前此专利已经获得美国专利授权,专利号:CN110213241A。
将我们落地框架中的 QUIC LB 组件作为专利进行保护,提出:“一种无状态、一致性、分布式的 QUIC 负载均衡设备”,目前此专利还在受理中。由于通过 QUIC LB 可以很好的支持 QUIC 协议的连接迁移问题,所以目前 IETF QUIC WG 上有关于 QUIC LB 相关的草案,我们目前已经参与到 Draft 的讨论和制定中,后序相关的方案也会继续推广到云上产品。
将我们解决的 UDP 的无损升级方法进行专利保护,提出 “一种 QUIC 服务器无损升级方案”,目前此专利还在受理中。由于 UDP 无损升级问题是一个业界难题,当前有些手段需要在用户态进行跳转,性能损失较大,我们的方案可以在我们的落地框架中解决当前问题,关于这个方案的细节我们再后面的关键技术中进行介绍。
四项关键技术
在整个的落地中,我们设计的方案围绕解决几个核心问题展开,形成了四项关键技术,分别如下
先说 连接迁移面临的问题 ,上文有提到,QUIC 有一项比较重要的功能是支持连接迁移。这里的连接迁移是指:如果客户端在长连接保持的情况下切换网络,比如从 4G 切换到 Wifi , 或者因为 NAT Rebinding 导致五元组发生变化,QUIC 依然可以在新的五元组上继续进行连接状态。QUIC 之所以能支持连接迁移,一个原因是 QUIC 底层是基于无连接的 UDP,另一个重要原因是因为 QUIC 使用唯一的 CID 来标识一个连接,而不是五元组。
如下图所示,是 QUIC 支持连接的一个示意图,当客户端出口地址从 A 切换成 B 的时候,因为 CID 保持不变,所以在 QUIC 服务器上,依然可以查询到对应的 Session 状态。
然而,理论很丰满,落地却很艰难,在端到端的落地过程中,因为引入了负载均衡设备,会导致在连接迁移时,所有依赖五元组 Hash 做转发或者关联 Session 的机制失效。以 LVS 为例,连接迁移后, LVS 依靠五元组寻址会导致寻址的服务器存在不一致。即便 LVS 寻址正确,当报文到达服务器时,内核根据五元组关联进程,依然会寻址出错。同时,IETF Draft 要求,连接迁移时 CID 需要更新掉,这就为仅依靠 CID 来转发的计划同样变的不可行。
再说 我们的解决方法,为了解决此问题,我们设计了开篇介绍的落地框架,这里我们将方案做一些简化和抽象,整体思路如下图所示:
a . 我们在 QUIC 的 CID 中扩展了一些字段(ServerInfo)用来关联 QUIC Server 的 IP 和 Working Port 信息。 b . 在发生连接迁移的时候,QUIC LoadBalancer 可以依赖 CID 中的 ServerInfo 进行路由,避免依赖五元组关联 Session 导致的问题。 c . 在 CID 需要 Update 的时候,NewCID 中的 ServerInfo 保留不变,这样就避免在 CID 发生 Update 时,仅依赖 CID Hash 挑选后端导致的寻址不一致问题。
2 . 在 QUIC 服务器多进程工作模式上,我们突破了 NGINX 固有的多 Worker 监听在相同端口上的桎梏,设计了多端口监听的机制,每个 Worker 在工作端口上进行隔离,并将端口的信息携带在对 First Initial Packet 的回包的 CID 中,这样代理的好处是:
a . 无论是否连接迁移,QUIC LB 都可以根据 ServerInfo,将报文转发到正确的进程。 b . 而业界普遍的方案是修改内核,将 Reuse port 机制改为 Reuse CID 机制,即内核根据 CID 挑选进程。即便后面可以通过 ebpf 等手段支持,但我们认为这种修改内核的机制对底层过于依赖,不利于方案的大规模部署和运维,尤其在公有云上。 c . 使用独立端口,也有利于多进程模式下,UDP 无损升级问题的解决,这个我们在技术点 3 中介绍。
技术点2.提升 0RTT 握手比例
这里先 介绍 QUIC 0RTT 原理。前文我们介绍过, QUIC 支持传输层握手和安全加密层握手都在一个 0RTT 内完成。TLS1.3 本身就支持加密层握手的 0RTT,所以不足为奇。而 QUIC 如何实现传输层握手支持 0RTT 呢?我们先看下传输层握手的目的,即:服务端校验客户端是真正想握手的客户端,地址不存在欺骗,从而避免伪造源地址攻击。在 TCP 中,服务端依赖三次握手的最后一个 ACK 来校验客户端是真正的客户端,即只有真正的客户端才会收到 Sever 的 syn_ack 并回复。
QUIC 同样需要对握手的源地址做校验,否则便会存在 UDP 本身的 DDOS 问题,那 QUIC 是如何实现的?依赖 STK(Source Address Token) 机制。这里我们先声明下,跟 TLS 类似,QUIC 的 0RTT 握手,是建立在已经同一个服务器建立过连接的基础上,所以如果是纯的第一次连接,仍然需要一个 RTT 来获取这个 STK。如下图所示,我们介绍下这个原理:
理论上说,只要客户端缓存了这个 STK,下次握手的时候带过来,服务端便可以直接校验通过,即实现传输层的 0RTT。但是真实的场景却存在如下两个问题:
再介绍下我们的解决方法。第一个问题比较好解,我们只要保证集群内的机器生成 STK 的秘钥一致即可。第二个问题,我们的解题思路是:
我们知道 UDP 无损升级是业界难题。无损升级是指在 reload 或者更新二进制时,老的进程可以处理完存量连接上的数据后优雅退出。以 NGINX 为例,这里先介绍下 TCP 是如何处理无损升级的,主要是如下的两个步骤:
而 UDP 无法做到无损升级是因为 UDP 只有一个 listening socket 没有类似 TCP 的连接套接字,所有的收发数据包都在这个 socket 上,导致下面的热升级步骤会存在问题:
这里介绍下相关的解决方法。针对此问题,业界有一些方法,比如:在数据包中携带进程号,当数据包收发错乱后,在新老进程之间做一次转发。考虑到接入层上的性能等原因,我们不希望数据再做一次跳转。结合我们的落地架构,我们设计了如下的 基于多端口轮转的无损升级方案,简单来说,我们让新老进程监听在不同的端口组并携带在 CID 中,这样 QUIC LB 就可以根据端口转发到新老进程。为了便于运维,我们采用端口轮转的方式,新老进程会在 reload N 次之后,重新开始之前选中的端口。如下图所示:
技术点4.客户端智能选路
尽管落地 QUIC 的愿望是好的,但是新事物的发展并不是一帆风顺的。由于 QUIC 是基于 UDP 的,而 UDP 相比于 TCP 在运营商的支持上并非友好,表现在:
同时,根据观察发现,不同的手机厂商对于 UDP 的支持能力也不同,所以在落地过程中,如果盲目的将所有流量完全切为 QUIC 可能会导致一些难以预料的结果。为此,我们在客户端上,设计了开篇介绍的 TCP 和 QUIC 相互 Backup 的链路,如下图所示,我们实时探测 TCP 链路和 QUIC 链路的 RTT、丢包率、请求完成时间、错误率等指标情况,并根据一定的量化方法对两种链路进行打分,根据评分高低,决定选择走哪种链路,从而避免寻址只走一条链路导致的问题。
做个总结
本文主要综述性的介绍了 QUIC 在蚂蚁的落地方案、场景以及一些关键技术。关键技术上,主要介绍了我们如何通过创造性的提出 QUIC LB 组件、以及多端口监听的机制来优雅的支持 QUIC 的连接迁移机制、QUIC 服务端的无损升级等,依赖这套方案我们的接入网关不需要像业界一样依赖底层内核的改动,这极大的方便了我们方案的部署,尤其在公有云场景下。除了连接迁移以外,我们还提出了 0RTT 建联提升方案、客户端智能选路方案,以最大化 QUIC 在移动端上的收益。截止当前,QUIC 已经在支付宝移动端以及全球加速链路两个场景上平稳运行,并带来了较好的业务收益。
未来规划
两年来,我们主要以社区的 gQuic 为基础,充分发挥 QUIC 的协议优势,并结合蚂蚁的业务特征以最大化移动端收益为目标,创造性的提出了一些解决方案,并积极向社区和 IETF 进行推广。在未来,随着蚂蚁在更多业务上的开展和探索以及HTTP3.0/QUIC 即将成为标准,我们会主要围绕以下几个方向继续深挖 QUIC 的价值:
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8