# http3
http3,即 QUIC(Quick UDP Internet Connection),是谷歌推出的一套基于UDP的传输协议,目的是保证可靠性的同时降低网络延迟。
http1 和 http2 全部都是基于 TCP 传输,一旦 TCP 传输出现丢包,整个 TCP 都要开始等待重传,会导致后面的所有数据都被阻塞。
修改 TCP 协议是一件不可能的事情,所以谷歌就推出一套基于 UDP 协议。
# 0-RTT(Round-Trip Time, 往返延迟)
0-RTT 建连可以说是 QUIC 相比 http2 最大的性能优势。只需要一次往返就能建立 https 连接。
以下为 http1/2 和 QUIC 的对比图:
http1/2 采用的加密算法可以参考 https。这里 QUIC 链接采用的是 HD 密钥交换算法:
DH 算法的核心就是服务端生成 a、g、p 3 个随机数,a 自己持有,g 和 p 要传输给客户端,而客户端会生成 b 这 1 个随机数,通过 DH 算法客户端和服务端可以算出同样的密钥。在这过程中 a 和 b 并不参与网络传输,安全性大大提高。因为 p 和 g 是大数,所以即使在网络中传输的 p、g、A、B 都被劫持,那么靠现在的计算机算力也没法破解密钥。
具体过程如下:
Step1:首次连接时,客户端发送 Inchoate Client Hello 给服务端,用于请求连接;
Step2:服务端生成 g、p、a,根据 g、p 和 a 算出 A,然后将 g、p、A 放到 Server Config 中再发送 Rejection 消息给客户端;
Step3:客户端接收到 g、p、A 后,自己再生成 b,根据 g、p、b 算出 B,根据 A、p、b 算出初始密钥 K。B 和 K 算好后,客户端会用 K 加密 HTTP 数据,连同 B 一起发送给服务端;
Step4:服务端接收到 B 后,根据 a、p、B 生成与客户端同样的密钥,再用这密钥解密收到的 HTTP 数据。为了进一步的安全(前向安全性),服务端会更新自己的随机数 a 和公钥,再生成新的密钥 S,然后把公钥通过 Server Hello 发送给客户端。连同 Server Hello 消息,还有 HTTP 返回数据;
Step5:客户端收到 Server Hello 后,生成与服务端一致的新密钥 S,后面的传输都使用 S 加密。
所以,QUIC 客户端与服务端建立链接一共发了 1 RTT,后面的连接如果客户端缓存了 Server Config,那么就可以直接发送 HTTP 数据,实现 0 RTT 建立连接。
# 队头阻塞
http1/2 都会穿在对头阻塞问题,包括:
TCP 阻塞。TCP 处理数据时有严格的前后顺序,先发送的要先被处理,如果有包丢失,则会开启重传机制,后面所有的数据都需要等待。
TLS 阻塞。TLS 基于 Record 组织数据,将一堆数据放在一起(即一个 Record)加密,加密完后又拆分成多个 TCP 包传输。一般每个 Record 16K,包含 12 个 TCP 包,这样如果 12 个 TCP 包中有任何一个包丢失,那么整个 Record 都无法解密。
QUIC 针对这两个问题解决了队头阻塞的问题:
QUIC 基于 UDP,UDP 的数据包在接收端没有处理顺序,即使中间丢失一个包,也不会阻塞整条连接,其他的资源会被正常处理。
QUIC 的传输单元是 Packet,加密单元也是 Packet,整个加密、传输、解密都基于 Packet,这样就能避免 TLS 的队头阻塞问题;
# 拥塞控制
QUIC 重新实现了 TCP 协议的 Cubic 算法进行拥塞控制,并在此基础上做了不少改进。下面介绍一些 QUIC 改进的拥塞控制的特性。
# 热插拔
TCP 中如果要修改拥塞控制策略,需要在系统层面进行操作。QUIC 修改拥塞控制策略只需要在应用层操作,并且 QUIC 会根据不同的网络环境、用户来动态选择拥塞控制算法。
# 前向纠错(FEC,Forward Error Correction)
一段数据被切分为 10 个包后,依次对每个包进行异或运算,运算结果即 FEC 包,数据包一起被传输,如果不幸在传输过程中有一个数据包丢失,那么就可以根据剩余 9 个包以及 FEC 包推算出丢失的那个包的数据,这样就大大增加了协议的容错性。
# 递增 Packet Number
QUIC 同样是一个可靠的协议,它使用 Packet Number 代替了 TCP 的 sequence number,每个 Packet Number 都严格递增。
TCP 重传的 sequence number 和原始的 sequence number 不变,服务端返回 ACK 的时候客户端无法知道是哪次传输被确认接受。就导致了 TCP 重传的歧义。
而 QUIC 使用递增的 Packet Number 就解决了歧义的问题。
# Stream Offset
但是单纯依靠严格递增的 Packet Number 肯定是无法保证数据的顺序性和可靠性。QUIC 又引入了一个 Stream Offset 的概念。
即一个 Stream 可以经过多个 Packet 传输,Packet Number 严格递增,没有依赖。服务端收到所有 Packet 之后,根据 Stream 的 Offset 来保证应用数据的顺序。
# 更多的 Ack 块
一般来说,接收方收到发送方的消息后都应该发送一个 ACK 回复,表示收到了数据。
但每收到一个数据就返回一个 ACK 回复太麻烦,所以一般不会立即回复,而是接收到多个数据后再回复,TCP SACK 最多提供 3 个 ACK block,即每收到 3 个数据包就要返回一个 ACK,但有些场景下,比如下载,只需要服务器返回数据就好,但按照 TCP 的设计,每收到 3 个数据包就要“礼貌性”地返回一个 ACK。而 QUIC 最多可以捎带 256 个 ACK block。在丢包率比较严重的网络下,更多的 ACK block 可以减少重传量,提升网络效率。
# 总结
QUIC 用到的算法是 HD 密钥交换算法,保证可靠性主要依赖 前向纠错、递增 Packet Number、Stream Offset
参考:
- https://zhuanlan.zhihu.com/p/143464334