HTTP3 RFC 9114 发布,深入剖析HTTP3协议
经过了多年的努力,在 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 协议概览
HTTP over QUIC
即HTTP/3
的含义HTTP/3
, QUIC是绕不过去的, 下面是几个重要的QUIC特性.0 RTT建立连接
RTT: round-trip time, 仅包括请求访问来回的时间
HTTP/2
的连接建立需要3 RTT, 如果考虑会话复用
, 即把第一次握手计算出来的对称密钥缓存起来, 那也需要2 RTT. 更进一步的, 如果TLS升级到1.3, 那么HTTP/2连接需要2RTT, 考虑会话复用需要1RTT. 如果HTTP/2不急于HTTPS, 则可以简化, 但实际上几乎所有浏览器的设计都要求HTTP/2需要基于HTTPS.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返回数据.
连接迁移
Connection ID
, 即使IP或者端口发生变化, 只要Connection ID没有变化, 那么连接依然可以维持.队头阻塞/多路复用
HTTP/1.1
和HTTP/2
都存在队头阻塞的问题(Head Of Line blocking).ACK
消息, 以确认对象已接受数据. 如果每次请求都要在收到上次请求的ACK
消息后再请求, 那么效率无疑很低. 后来HTTP/1.1
提出了Pipeline
技术, 允许一个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
3. 单调递增的Packer Number
Sequence Number
和ACK来确认消息是否有序到达, 但这样的设计存在缺陷.RTT: Round Trip Time, 往返事件 RTO: Retransmission Timeout, 超时重传时间
Sequence Number
不同, Packet Number
严格单调递增, 如果Packet N
丢失了, 那么重传时Packet的标识就不会是N, 而是比N大的数字, 比如N+M, 这样发送方接收到确认消息时, 就能方便的知道ACK对应的原始请求还是重传请求.4. ACK Delay
5. 更多的ACK块
ACK
, 而QUIC最多可以捎带256个ACK block
, 在丢包率比较严重的网络下, 更多的ACK可以减少重传量, 提升网络效率.浏览控制
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 接收窗口
参考链接
推荐阅读: