QUIC(Quick UDP Internet Connection)是Google提出的一个基于UDP的传输协议,因其高效的传输效率和多路并发的能力,已经成为下一代互联网协议HTTP/3的底层传输协议。除了应用于Web领域,它的优势同样适用于一些通用的需要低延迟、高吞吐特性的传输场景。本文从QUIC的由来和优势出发,分享实际项目中需要考虑的问题和解决思路,通过测试对比QUIC和TCP的实际传输能力,希望有助于大家理解和实践QUIC协议。
PART 01 为什么需要QUIC?
众所周知,HTTP从最初的HTTP/0.9,经历了HTTP/1.x,HTTP/2到最新的HTTP/3这几个大的更新版本。在HTTP/3版本之前,HTTP底层都是用TCP传输数据,而伴随着移动互联网的发展,网络交互场景越来越丰富并要求及时性,传统TCP固有的性能瓶颈越来越不能满足需求,原因有以下几点:
建立连接的握手延迟大
HTTPS包含两个握手:1)TCP三次握手,1个RTT;2)TLS握手,2个RTT。完整握手总共需要3个RTT,对于直播等需要首帧秒开场景,握手延迟太大。
多路复用的队首阻塞
以HTTP/2多路复用为例,多个数据请求作为不同的流,共用一条TCP连接发送,所有的流应用层都必须按序处理。若某个流的数据丢失,后面其他流的数据都会被阻塞,直到丢失的流数据重传完成其他流才能被继续传输。即使接收端已经收到之后流的数据包,HTTP协议也不会通知应用层去处理。
TCP协议的更新滞后
TCP协议是实现在操作系统内核内,但是用户端的操作系统版本升级非常困难,很多老旧的系统像WindowsXP还有大量用户使用,因此TCP协议的一些更新很难被快速推广。
正是考虑到以上的这些问题,QUIC在应用层之上基于UDP实现丢包恢复,拥塞控制,加解密,多路复用等功能,既能优化握手延迟,同时又完全解决内核协议更新滞后问题。
PART 02 QUIC的优势
这里列举下QUIC的主要优势。
握手建连更快
QUIC建连时间大约0~1 RTT,在两方面做了优化:
1)传输层使用了UDP,减少了1个RTT三次握手的延迟。
2)加密协议采用了TLS 协议的最新版本TLS 1.3,相对之前的TLS 1.1-1.2,TLS1.3允许客户端无需等待TLS握手完成就开始发送应用程序数据的操作,可以支持1 RTT和0RTT。
对于QUIC协议,客户端第一次建连的握手协商需1-RTT,而已建连的客户端重新建连可以使用之前协商好的缓存信息来恢复TLS连接,仅需0-RTT时间。因此QUIC建连时间大部分0-RTT、极少部分1-RTT,相比HTTPS的3-RTT的建连,具有极大的优势。
避免队首阻塞的多路复用
QUIC同样支持多路复用,相比HTTP/2,QUIC的流与流之间完全隔离的,互相没有时序依赖。如果某个流出现丢包,不会阻塞其他流数据的传输和应用层处理,所以这个方案并不会造成队首阻塞。
支持连接迁移
什么是连接迁移?举个例子,当你用手机使用蜂窝网络参加远程会议,当你把网络切换到WLAN时,会议客户端会立马重连,视频同时出现一瞬间的卡顿。这是因为,TCP采用四元组(包括源IP、源端口、目标地址、目标端口)标识一个连接,在网络切换时,客户端的IP发生变化,TCP连接被瞬间切断然后重连。连接迁移就是当四元组中任一值发生变化时,连接依旧能保持,不中断业务。QUIC支持连接迁移,它用一个(一般是64位随机数)ConnectionID标识连接,这样即使源的IP或端口发生变化,只要ConnectionID一致,连接都可以保持,不会发生切断重连。
可插拔的拥塞控制
QUIC是应用层协议,用户可以插拔式选择像Cubic、BBR、Reno等拥塞控制算法,也可以根据具体的场景定制私有算法。
前向纠错(FEC)
QUIC支持前向纠错,弱网丢包环境下,动态的增加一些FEC数据包,可以减少重传次数,提升传输效率。
PART 03 基于QUIC的服务架构
实际项目在应用QUIC之前,首先要对整体服务架构做一些宏观上的思考,对下面的问题进行一定的思考后,再去考虑项目本身的细节,可以规避一些技术弯路。
哪些链路需要提升传输效率?
TCP的性能瓶颈主要体现在具有数据丢包的网络,由于TCP的AIMD(加性增、乘性减)的拥塞避免策略,网络上出现少量丢包,TCP拥塞控制算法会指数级降低传输速率,导致其无法有效利用带宽。对于一些比较理想的网络,比如局域网内,TCP与QUIC的传输能力相当,传输速率受限于实际带宽,引入QUIC反而增加了复杂度。
如何兼容老的客户端?
新的架构不能影响老的客户端,因此需要同时支持TCP和QUIC。
如何实现连接迁移?
用户在4G和WIFI之间切换,如何实现连接迁移,保证业务层不中断?
QUIC是否会带来一些弊端,如何解决?
新事物产生往往会伴随新的问题,QUIC实现在内核之上的应用层,用UDP替代TCP传输,也带来了一些问题:
(1)国内运营商针对UDP做QOS限速和丢包,一些企业的局域网防火墙有时候会禁用 UDP 协议,导致网络UDP传输低效不可用。
(2)QUIC的协议栈运行在用户态,同时因为协议的复杂度,对CPU的消耗会比TCP高不少,实际实现时如何尽可能的减少QUIC对服务器性能的影响?
(3)UDP的报文长度受限于MTU,对于大块数据,QUIC在应用层切成大量的小包,收发会造成大量的系统调用sendmsg/recvmsg,因此QUIC的网络吞吐相比TCP有很大劣势。Linux系统提供了多种优化手段:1)支持批量发送函数sendmmsg,多个UDP报文,通过一次系统调用完成发送;2)开启内核GRO/GSO,在网卡驱动完成拆包和组包;3)网卡支持硬件UDP GSO offload。
考虑到以上问题之后,一般QUIC服务可以类似按照下图的架构设计。
加速链路
对以下两段链路使用QUIC加速:1)最后一公里:因受wifi设备性能、蜂窝网络信号覆盖范围强弱等因素影响,用户终端的最后一公里网络能力参差不齐,是比较容易出现弱网的一段链路。2)数据中心间级联:数据中心之间的数据一般会走骨干网,但在一些流量高峰时段,可能会出现网络拥塞,比较容易出现数据丢包,延时加剧。
双链路备份
客户端一般会同时支持TCP和QUIC两种传输通道,互为备份,根据两条链路的实际状况(RTT、丢包等)智能选择最优传输通道。
连接迁移的实现
如前文所述,QUIC socket采用UDP收发数据,一个连接用一个ConnectionID唯一标识,无论用户在蜂窝网络与WLAN之间如何切换,只要数据包中的ConnectionID不变,服务端可以将不同的四元组关联到同一个连接上下文,从而避免出现断网重连。但是实现无缝的连接迁移困难并不小,为了实现高并发,服务端一般都会采用多线程,多进程的架构,负载均衡根据四元组将连接ConnectionID关联到特定的运行环境(线程或进程),连接迁移大概率会导致缓存的连接上下文失效。以下图多线程的设计举例,设想CID1连接迁移发生时,用户源IP和端口由A变成C,socket绑定的线程由1迁移到2,因线程2查找不到CID1连接的上下文,导致连接迁移失败。
上图只是多线程的架构,我们可以想办法把新的socket重新bind回线程1。如果是多进程架构,负载均衡会寻址到不同的服务器上,如何将连接重新寻址到原始服务器?业内一般的解决思路借鉴了雪花算法,在建连完成就给Server的destination ConnectionId打上初始服务器的地址标记,根据这些标记信息(集群ID+机器ID+线程ID),让新的服务器寻址到初始的服务器,后续数据再透明转发给初始服务器,整个过程仅增加了一次转发动作,对用户没有任何感知。
PART 04 传输时延测试
建连时延
上图可以看到建连有将近50%的提升,QUIC节省了2-RTT时间,对于一些RTT高网络,效果更显著。
丢包时延
为了对比TCP和QUIC在丢包网络下的表现,分别对5%、15%、30%的丢包率场景做了对比测试。每一种场景,进行1000次测试,每次测试发送10KB数据,统计最终落在指定时延以下的概率。最终的结果参考下图,横坐标表示时延,纵坐标用百分比表示指定时延下的样本数量,曲线收敛速度越快,落在较低时延范围的样本数量越多,所以时延表现越好。
5%丢包下,200ms内传输完成的测试样本,QUIC占95%,TCP占78%,QUIC比TCP提升了17%左右。
15%丢包下,200ms内传输完成的测试样本,QUIC占90%,TCP占50%,QUIC比TCP提升了90%左右。
30%丢包下,200ms内传输完成的测试样本,QUIC占62%,TCP占22%,QUIC比TCP提升了300%左右。
总结起来,QUIC相比TCP的弱网丢包下的延时提升比较比较明显,丢包率越高,提升越明显。
PART 05 总结
本文对QUIC实践中遇到的主要问题和相应解决思路做了一些分享,实际项目应用时,由于需求的多样性、现有服务架构的差异,需要因地制宜,将QUIC的一些好的特性以比较优雅地姿势应用。
关注拍乐云Pano,了解更多音视频相关技术、产品实践!