HTTP/3 ,它来了,你学到了什么?
HTTP 3.0 是HTTP 協議的第三個主要版本,前兩個分別是HTTP 1.0 和HTTP 2.0 ,但其實HTTP 1.1 我認為才是真正的HTTP 1.0。
我們大家知道,HTTP 是應用層協議,應用層產生的數據會通過傳輸層協議作為載體來傳輸到互聯網上的其他主機中,而其中的載體就是TCP 協議,這是HTTP 2 之前的主流模式。
但是隨著TCP 協議的缺點不斷暴露出來,新一代的HTTP 協議- HTTP 3.0 毅然決然切斷了和TCP 的聯繫,轉而擁抱了UDP 協議,這麼說不太準確,其實HTTP 3.0 其實是擁抱了 QUIC 協議,而QUIC 協議是建立在UDP 協議基礎上的。
HTTP 3.0
HTTP 3.0 於2022 年6 月6 日正式發布,IETF 把HTTP 3.0 標準制定在了 RFC 9114 中,HTTP 3.0 其實相較於HTTP 2.0 要比HTTP 2.0 相較於HTTP 1.1 的變化來說小很多,最大的提升就在於效率,替換TCP 協議為UDP 協議,HTTP 3.0 具有更低的延遲,它的效率甚至要比HTTP 1.1 快3 倍以上。
其實每一代HTTP 協議的不斷發展都是建立在上一代HTTP 的缺點上的,就比如HTTP 1.0 最大的問題就是傳輸安全性和不支持持久連接上,針對此出現了HTTP 1.1 ,引入了Keep-Alive 機制來保持長鏈接和TLS 來保證通信安全性。但此時的HTTP 協議並發性還做的不夠好。
隨著網絡的不斷發展,每個網站所需資源(CSS、JavaScript、圖像等)的數量逐年增加,瀏覽器發現自己在獲取和呈現網頁時需要越來越多的並發性。但是由於HTTP 1.1 只能夠允許客戶端/服務器進行一次HTTP 請求交換,因此在網絡層獲得併發性的唯一方法是並行使用多個TCP 連接到同一個源,不過使用多個TCP 鏈接就失去了 keep-Alive 的意義。
然後出現了 SPDY 協議,主要解決HTTP 1.1 效率不高的問題,包括降低延遲,壓縮header 等等,這些已經被Chrome 瀏覽器證明能夠產生優化效果,後來HTTP 2.0 基於SPDY ,並且引入了**流( Stream )**的概念,它允許將不同的HTTP 交換多路復用到同一個TCP 連接上,從而達到讓瀏覽器重用TCP 鏈接的目的。
TCP 的主要作用是以正确的顺序将整个字节流从一个端点传输到另一个端点,但是当流中的某些数据包丢失时,TCP 需要重新发送这些丢失的数据包,等到丢失的数据包到达对应端点时才能够被 HTTP 处理,这被称为 TCP 队头阻塞问题。
那么可能就会有人考虑到去修改 TCP 协议,其实这已经是一件不可能完成的任务了。因为 TCP 存在的时间实在太长,已经充斥在各种设备中,并且这个协议是由操作系统实现的,更新起来不大现实。
基于这个原因,Google 就更起炉灶搞了一个基于 UDP 协议的 QUIC 协议,并且使用在了 HTTP/3 上,HTTP/3 之前名为 HTTP-over-QUIC,从这个名字中我们也可以发现,HTTP/3 最大的改造就是使用了 QUIC。
QUIC 协议
QUIC 的小写是 quic,谐音 quick,意思就是快。它是 Google 提出来的一个基于 UDP 的传输协议,所以 QUIC 又被叫做快速 UDP 互联网连接。
首先 QUIC 的第一个特征就是快,为什么说它快,它到底快在哪呢?
我们大家知道,HTTP 协议在传输层是使用了 TCP 进行报文传输,而且 HTTPS 、HTTP/2.0 还采用了 TLS 协议进行加密,这样就会导致三次握手的连接延迟:即 TCP 三次握手(一次)和 TLS 握手(两次),如下图所示。
对于很多短连接场景,这种握手延迟影响较大,而且无法消除。毕竟 RTT 是人类和效率的终极斗争。
相比之下,QUIC 的握手连接更快,因为它使用了 UDP 作为传输层协议,这样能够减少三次握手的时间延迟。而且 QUIC 的加密协议采用了 TLS 协议的最新版本 TLS 1.3,相对之前的 TLS 1.1-1.2,TLS1.3 允许客户端无需等待 TLS 握手完成就开始发送应用程序数据的操作,可以支持1 RTT 和 0 RTT,从而达到快速建立连接的效果。
我们上面还说过,HTTP/2.0 虽然解决了队头阻塞问题,但是其建立的连接还是基于 TCP,无法解决请求阻塞问题。
而 UDP 本身没有建立连接这个概念,并且 QUIC 使用的 stream 之间是相互隔离的,不会阻塞其他 stream 数据的处理,所以使用 UDP 并不会造成队头阻塞。
在 TCP 中,TCP 为了保证数据的可靠性,使用了序号+确认号机制来实现,一旦带有 synchronize sequence number 的包发送到服务器,服务器都会在一定时间内进行响应,如果过了这段时间没有响应,客户端就会重传这个包,直到服务器收到数据包并作出响应为止。
那么 TCP 是如何判断它的重传超时时间呢?
TCP 一般采用的是自适应重传算法,这个超时时间会根据往返时间 RTT 动态调整的。每次客户端都会使用相同的 syn 来判断超时时间,导致这个 RTT 的结果计算的不太准确。
虽然 QUIC 没有使用 TCP 协议,但是它也保证了可靠性,QUIC 实现可靠性的机制是使用了 Packet Number,这个序列号可以认为是 synchronize sequence number 的替代者,这个序列号也是递增的。与 syn 所不同的是,不管服务器有没有接收到数据包,这个 Packet Number 都会 + 1,而 syn 是只有服务器发送 ack 响应之后,syn 才会 + 1。
比如有一个 PN = 10 的数据包在发送的过程中由于某些原因迟迟没到服务器,那么客户端会重传一个 PN = 11 的数据包,经过一段时间后客户端收到 PN = 10 的响应后再回送响应报文,此时的 RTT 就是 PN = 10 这个数据包在网络中的生存时间,这样计算相对比较准确。
虽然 QUIC 保证了数据包的可靠性,但是数据的可靠性是如何保证的呢?
QUIC 引入了一个 stream offset 的概念,一个 stream 可以传输多个 stream offset,每个 stream offset 其实就是一个 PN 标识的数据,即使某个 PN 标识的数据丢失,PN + 1 后,它重传的仍旧是 PN 所标识的数据,等到所有 PN 标识的数据发送到服务器,就会进行重组,以此来保证数据可靠性。到达服务器的 stream offset 会按照顺序进行组装,这同时也保证了数据的顺序性。
众所周知,TCP 协议的具体实现是由操作系统内核来完成的,应用程序只能使用,不能对内核进行修改,随着移动端和越来越多的设备接入互联网,性能逐渐成为一个非常重要的衡量指标。虽然移动网络发展的非常快,但是用户端的更新却非常缓慢,我仍然看见有很多地区很多计算机还仍旧使用 xp 系统,尽管它早已发展了很多年。服务端系统不依赖用户升级,但是由于操作系统升级涉及到底层软件和运行库的更新,所以也比较保守和缓慢。
QUIC 协议的一个重要特点就是可插拔性,能够动态更新和升级,QUIC 在应用层实现了拥塞控制算法,不需要操作系统和内核的支持,遇到拥塞控制算法切换时,只需要在服务器重新加载一边即可,不需要停机和重启。
我们知道 TCP 的流量控制是通过滑动窗口来实现的,如果你对滑动窗口不太熟悉,你可以看下我写的这篇文章
TCP 基础知识
在文章后面有提到了滑动窗口的一些概念。
而 QUIC 也实现了流量控制,QUIC 的流量控制也是使用了窗口更新 window_update,来告诉对端它可以接受的字节数。
TCP 协议头部没有经过加密和认证,所以在传输的过程中很可能被篡改,与之不同的是,QUIC 中的报文头部都是经过认证,报文也经过加密处理。这样只要对 QUIC 的报文有任何修改,接收端都能够及时发现,保证了安全性。
总的来说,QUIC 具有下面这些优势
- 使用 UDP 协议,不需要三次连接进行握手,而且也会缩短 TLS 建立连接的时间。
- 解决了队头阻塞问题。
- 实现动态可插拔,在应用层实现了拥塞控制算法,可以随时切换。
- 报文头和报文体分别进行认证和加密处理,保障安全性。
- 连接能够平滑迁移。
连接平滑迁移指的是,你的手机或者移动设备在 4G 信号下和 WiFi 等网络情况下切换,不会断线重连,用户甚至无任何感知,能够直接实现平滑的信号切换。
QUCI 协议已经被写在了 RFC 9000 中。