HTTP/3正式发布,深入理解HTTP/3协议
经过了多年的努力,在 6 月 6 号,IETF (互联网工程任务小组) 正式发布了 HTTP/3 的 RFC, 这是超文本传输协议(HTTP)的第三个主要版本,完整的 RFC 超过了 20000 字,非常详细的解释了 HTTP/3。
HTTP 历史
1991 HTTP/1.1 2009 Google 设计了基于TCP的SPDY 2013 QUIC 2015 HTTP/2 2018 HTTP/3
HTTP2协议虽然大幅提升了HTTP/1.1的性能,然而,基于TCP实现的HTTP2遗留下3个问题:
有序字节流引出的队头阻塞(Head-of-line blocking),使得HTTP2的多路复用能力大打折扣;
TCP与TLS叠加了握手时延,建链时长还有1倍的下降空间;
基于TCP四元组确定一个连接,这种诞生于有线网络的设计,并不适合移动状态下的无线网络,这意味着IP地址的频繁变动会导致TCP连接、TLS会话反复握手,成本高昂。
HTTP3协议解决了这些问题:
HTTP3基于UDP协议重新定义了连接,在QUIC层实现了无序、并发字节流的传输,解决了队头阻塞问题(包括基于QPACK解决了动态表的队头阻塞);
HTTP3重新定义了TLS协议加密QUIC头部的方式,既提高了网络攻击成本,又降低了建立连接的速度(仅需1个RTT就可以同时完成建链与密钥协商);
HTTP3 将Packet、QUIC Frame、HTTP3 Frame分离,实现了连接迁移功能,降低了5G环境下高速移动设备的连接维护成本。
QUIC 协议概览
QUIC是用来替代TCP, SSL/TLS的传输层协议, 在传输层之上还有应用层. 我们熟知的应用层协议有HTTP, FTP, IMAP等, 这些协议理论上都可以运行在QUIC上, 其中运行在QUIC之上的协议被称为HTTP/3, 这就是HTTP over QUIC即HTTP/3
的含义,因此想要了解HTTP/3
, QUIC是绕不过去的, 下面是几个重要的QUIC特性.
0 RTT建立连接
RTT: round-trip time, 仅包括请求访问来回的时间
HTTP/3首次连接只需要1RTT, 后面的链接只需要0RTT, 意味着客户端发送给服务端的第一个包就带有请求数据, 其主要连接过程如下:
首次连接, 客户端发送Inchoate Client Hello, 用于请求连接; 服务端生成g, p, a, 根据g, p, a算出A, 然后将g, p, A放到Server Config中在发送Rejection消息给客户端. 客户端接收到g,p,A后, 自己再生成b, 根据g,p,a算出B, 根据A,p,b算出初始密钥K, B和K算好后, 客户端会用K加密HTTP数据, 连同B一起发送给服务端. 服务端接收到B后, 根据a,p,B生成与客户端同样的密钥, 再用这密钥解密收到的HTTP数据. 为了进一步的安全(前向安全性), 服务端会更新自己的随机数a和公钥, 在生成新的密钥S, 然后把公钥通过Server Hello发送给客户端. 连同Server Hello消息, 还有HTTP返回数据.
这里使用DH密钥交换算法, DH算法的核心就是服务端生成a,g,p3个随机数, a自己持有, g和p要传输给客户端, 而客户端会生成b这1个随机数, 通过DH算法客户端和服务端可以算出同样的密钥. 在这过程中a和b并不参与网络传输, 安全性大大提升. 因为p和g是大数, 所以即使在网络传输中p, g, A, B都被劫持, 靠现在的计算力算力也无法破解.
连接迁移
队头阻塞/多路复用
TCP是个面向连接的协议, 即发送请求后需要收到ACK消息, 以确认对象已接受数据. 如果每次请求都要在收到上次请求的ACK消息后再请求, 那么效率无疑很低. 后来HTTP/1.1提出了Pipeline技术, 允许一个TCP连接同时发送多个请求. 这样就提升了传输效率.
在这样的背景下, 队头阻塞发生了. 比如, 一个TCP连接同时传输10个请求, 其中1,2,3个请求给客户端接收, 但是第四个请求丢失, 那么后面第5-10个请求都被阻塞. 需要等第四个请求处理完毕后才能被处理. 这样就浪费了带宽资源.
因此, HTTP一般又允许每个主机建立6个TCP连接, 这样可以更加充分的利用带宽资源, 但每个连接中队头阻塞的问题还是存在的.
Frame
通过一条TCP连接同时被传输, 这样即使一个请求被阻塞, 也不会影响其他的请求.但是, HTTP/2虽然可以解决请求
这一粒度下的阻塞, 但HTTP/2
的基础TCP协议本身却也存在队头阻塞的问题. HTTP/2的每个请求都会被拆分成多个Frame, 不同请求的Frame组合成Stream, Stream是TCP上的逻辑传输单元, 这样HTTP/2就达到了一条连接同时发送多个请求的目标, 其中Stram1
已经正确送达, Stram2中的第三个Frame丢失, TCP处理数据是有严格的前后顺序, 先发送的Frame要先被处理, 这样就会要求发送方重新发送第三个Frame, Steam3和Steam4虽然已到达但却不能被处理, 那么这时整条链路都会被阻塞.
QUIC的传输单位是Packet, 加密单元也是Packet, 整个加密, 传输, 解密都基于Packet, 这就能避免TLS的阻塞问题. QUIC基于UDP, UDP的数据包在接收端没有处理顺序, 即使中间丢失一个包, 也不会阻塞整条连接. 其他的资源会被正常处理.
拥塞控制
慢启动: 发送方像接收方发送一个单位的数据, 收到确认后发送2个单位, 然后是4个, 8个依次指数增长, 这个过程中不断试探网络的拥塞程度. 避免拥塞: 指数增长到某个限制之后, 指数增长变为线性增长 快速重传: 发送方每一次发送都会设置一个超时计时器, 超时后认为丢失, 需要重发 快速恢复: 在上面快速重传的基础上, 发送方重新发送数据时, 也会启动一个超时定时器, 如果收到确认消息则进入拥塞避免阶段, 如果仍然超时, 则回到慢启动阶段.
1. 热插拔
2. 前向纠错 FEC
QUIC使用前向纠错(FEC, Forword Error Correction)技术增加协议的容错性. 一段数据被切分为10个包后, 一次对每个包进行异或运算, 运算结果会作为FEC包与数据包一起被传输, 如果传输过程中有一个数据包丢失, 那么就可以根据剩余9个包以及FEC包推算出丢失的那个包的数据, 这样就大大增加了协议的容错性.
这是符合现阶段网络传输技术的一种方案, 现阶段带宽已经不是网络传输的瓶颈, 往返时间才是, 所以新的网络传输协议可以适当增加数据冗余, 减少重传操作.
3. 单调递增的Packer Number
Sequence Number
和ACK来确认消息是否有序到达, 但这样的设计存在缺陷.RTT: Round Trip Time, 往返事件 RTO: Retransmission Timeout, 超时重传时间
QUCI解决了上面的的歧义问题, 与Sequence Number
不同, Packet Number
严格单调递增, 如果Packet N
丢失了, 那么重传时Packet的标识就不会是N, 而是比N大的数字, 比如N+M, 这样发送方接收到确认消息时, 就能方便的知道ACK对应的原始请求还是重传请求.
4. ACK Delay
TCP计算RTT时没有考虑接收方接受到数据发发送方确认消息之间的延迟, 如下图所示, 这段延迟即ACK Delay. QUIC考虑了这段延迟, 使得RTT的计算更加准确.
5. 更多的ACK块
ACK
, 而QUIC最多可以捎带256个ACK block
, 在丢包率比较严重的网络下, 更多的ACK可以减少重传量, 提升网络效率.浏览控制
TCP 会对每个TCP连接进行流量控制, 流量控制的意思是让发送方不要发送太快, 要让接收方来得及接受, 不然会导致数据溢出而丢失, TCP的流量控制主要通过滑动窗口来实现的. 可以看到, 拥塞控制主要是控制发送方的发送策略, 但没有考虑接收方的接收能力, 流量控制是对部分能力的不起.
QUIC只需要建立一条连接, 在这条连接上同时传输多条Stream
, 好比有一条道路, 量都分别有一个仓库, 道路中有很多车辆运送物资. QUIC的流量控制有两个级别: 连接级别(Connection Level)和Stream 级别(Stream Level).
随着数据不断被处理, 接收方就有能力处理更多数据. 当满足(flow control receivce offset - consumed bytes) < (max receive window/2)
时, 接收方会发送WINDOW_UPDATE frame
告诉发送方你可以再多发送数据, 这时候flow control receive offset
就会偏移, 接收窗口增大, 发送方可以发送更多数据到接收方.
Stream级别对防止接收端接收过多数据作用有限, 更需要借助Connection
级别的流量控制. 理解了Stream
流量那么也很好理解Connection
的流控. Stream中,
接收窗口=最大接受窗口 - 已接收数据
而对于Connection来说:
接收窗口 = Stream1 接收窗口 + Stream2 接收窗口 + ... + StreamN 接收窗口
参考链接
HTTP/3 原理实战
链接:https://mp.weixin.qq.com/s/RX_dMXMfv7NMZMroCz49tA
(版权归原作者所有,侵删)