經(jīng)過(guò)了多年的努力,在 6 月 6 號(hào),IETF (互聯(lián)網(wǎng)工程任務(wù)小組) 正式發(fā)布了 HTTP/3 的 RFC, 這是超文本傳輸協(xié)議(HTTP)的第三個(gè)主要版本,完整的 RFC 超過(guò)了 20000 字,非常詳細(xì)的解釋了 HTTP/3。
HTTP 歷史
1991 HTTP/1.1
2009 Google 設(shè)計(jì)了基于TCP的SPDY
2013 QUIC
2015 HTTP/2
2018 HTTP/3
HTTP3是在保持QUIC穩(wěn)定性的同時(shí)使用UDP來(lái)實(shí)現(xiàn)高速度(選擇QUIC就是選擇UDP), 同時(shí)又不會(huì)犧牲TLS的安全性. ?
HTTP2協(xié)議雖然大幅提升了HTTP/1.1的性能,然而,基于TCP實(shí)現(xiàn)的HTTP2遺留下3個(gè)問(wèn)題:
有序字節(jié)流引出的隊(duì)頭阻塞(Head-of-line blocking),使得HTTP2的多路復(fù)用能力大打折扣;
TCP與TLS疊加了握手時(shí)延,建鏈時(shí)長(zhǎng)還有1倍的下降空間;
基于TCP四元組確定一個(gè)連接,這種誕生于有線網(wǎng)絡(luò)的設(shè)計(jì),并不適合移動(dòng)狀態(tài)下的無(wú)線網(wǎng)絡(luò),這意味著IP地址的頻繁變動(dòng)會(huì)導(dǎo)致TCP連接、TLS會(huì)話反復(fù)握手,成本高昂。
HTTP3協(xié)議解決了這些問(wèn)題:
HTTP3基于UDP協(xié)議重新定義了連接,在QUIC層實(shí)現(xiàn)了無(wú)序、并發(fā)字節(jié)流的傳輸,解決了隊(duì)頭阻塞問(wèn)題(包括基于QPACK解決了動(dòng)態(tài)表的隊(duì)頭阻塞);
HTTP3重新定義了TLS協(xié)議加密QUIC頭部的方式,既提高了網(wǎng)絡(luò)攻擊成本,又降低了建立連接的速度(僅需1個(gè)RTT就可以同時(shí)完成建鏈與密鑰協(xié)商);
HTTP3 將Packet、QUIC Frame、HTTP3 Frame分離,實(shí)現(xiàn)了連接遷移功能,降低了5G環(huán)境下高速移動(dòng)設(shè)備的連接維護(hù)成本。
QUIC 協(xié)議概覽
QUIC(Quick UDP Internet Connections, 快速UDP網(wǎng)絡(luò)連接)是基于UDP的協(xié)議, 利用了UDP的速度和效率, 同時(shí)整合TCP, TLS和HTTP/2的優(yōu)點(diǎn)并加以優(yōu)化. 用一張圖可以清晰的表示他們之間的關(guān)系.
QUIC是用來(lái)替代TCP, SSL/TLS的傳輸層協(xié)議, 在傳輸層之上還有應(yīng)用層. 我們熟知的應(yīng)用層協(xié)議有HTTP, FTP, IMAP等, 這些協(xié)議理論上都可以運(yùn)行在QUIC上, 其中運(yùn)行在QUIC之上的協(xié)議被稱為HTTP/3, 這就是HTTP over QUIC即HTTP/3的含義,因此想要了解HTTP/3, QUIC是繞不過(guò)去的, 下面是幾個(gè)重要的QUIC特性.
0 RTT建立連接
RTT: round-trip time, 僅包括請(qǐng)求訪問(wèn)來(lái)回的時(shí)間
HTTP/2的連接建立需要3 RTT, 如果考慮會(huì)話復(fù)用, 即把第一次握手計(jì)算出來(lái)的對(duì)稱密鑰緩存起來(lái), 那也需要2 RTT. 更進(jìn)一步的, 如果TLS升級(jí)到1.3, 那么HTTP/2連接需要2RTT, 考慮會(huì)話復(fù)用需要1RTT. 如果HTTP/2不急于HTTPS, 則可以簡(jiǎn)化, 但實(shí)際上幾乎所有瀏覽器的設(shè)計(jì)都要求HTTP/2需要基于HTTPS. ?
HTTP/3首次連接只需要1RTT, 后面的鏈接只需要0RTT, 意味著客戶端發(fā)送給服務(wù)端的第一個(gè)包就帶有請(qǐng)求數(shù)據(jù), 其主要連接過(guò)程如下:
首次連接, 客戶端發(fā)送Inchoate Client Hello, 用于請(qǐng)求連接;
服務(wù)端生成g, p, a, 根據(jù)g, p, a算出A, 然后將g, p, A放到Server Config中在發(fā)送Rejection消息給客戶端.
客戶端接收到g,p,A后, 自己再生成b, 根據(jù)g,p,a算出B, 根據(jù)A,p,b算出初始密鑰K, B和K算好后, 客戶端會(huì)用K加密HTTP數(shù)據(jù), 連同B一起發(fā)送給服務(wù)端.
服務(wù)端接收到B后, 根據(jù)a,p,B生成與客戶端同樣的密鑰, 再用這密鑰解密收到的HTTP數(shù)據(jù). 為了進(jìn)一步的安全(前向安全性), 服務(wù)端會(huì)更新自己的隨機(jī)數(shù)a和公鑰, 在生成新的密鑰S, 然后把公鑰通過(guò)Server Hello發(fā)送給客戶端. 連同Server Hello消息, 還有HTTP返回?cái)?shù)據(jù). ?
這里使用DH密鑰交換算法, DH算法的核心就是服務(wù)端生成a,g,p3個(gè)隨機(jī)數(shù), a自己持有, g和p要傳輸給客戶端, 而客戶端會(huì)生成b這1個(gè)隨機(jī)數(shù), 通過(guò)DH算法客戶端和服務(wù)端可以算出同樣的密鑰. 在這過(guò)程中a和b并不參與網(wǎng)絡(luò)傳輸, 安全性大大提升. 因?yàn)閜和g是大數(shù), 所以即使在網(wǎng)絡(luò)傳輸中p, g, A, B都被劫持, 靠現(xiàn)在的計(jì)算力算力也無(wú)法破解.
連接遷移
TCP連接基于四元組(源IP, 源端口, 目的IP, 目的端口), 切換網(wǎng)絡(luò)時(shí)至少會(huì)有一個(gè)因素發(fā)生變化, 導(dǎo)致連接發(fā)送變化. 當(dāng)連接發(fā)送變化是, 如果還是用原來(lái)的TCP連接, 則會(huì)導(dǎo)致連接失敗, 就得等到原來(lái)的連接超時(shí)后重新建立連接, 所以我們有時(shí)候發(fā)現(xiàn)切換到一個(gè)新的網(wǎng)絡(luò)時(shí), 即使網(wǎng)絡(luò)狀況良好, 但是內(nèi)容還是需要加載很久. 如果實(shí)現(xiàn)的好, 當(dāng)檢測(cè)到網(wǎng)絡(luò)變化時(shí), 立即建立新的TCP連接, 即使這樣, 建立新的連接還是需要幾百毫秒時(shí)間. ? QUIC不受四元組的影響, 當(dāng)這四個(gè)元素發(fā)生變化時(shí), 原連接依然維持. 原理如下:QUIC不以四元素作為表示, 而是使用一個(gè)64位的隨機(jī)數(shù), 這個(gè)隨機(jī)數(shù)被稱為Connection ID, 即使IP或者端口發(fā)生變化, 只要Connection ID沒(méi)有變化, 那么連接依然可以維持.
隊(duì)頭阻塞/多路復(fù)用
HTTP/1.1和HTTP/2都存在隊(duì)頭阻塞的問(wèn)題(Head Of Line blocking).
TCP是個(gè)面向連接的協(xié)議, 即發(fā)送請(qǐng)求后需要收到ACK消息, 以確認(rèn)對(duì)象已接受數(shù)據(jù). 如果每次請(qǐng)求都要在收到上次請(qǐng)求的ACK消息后再請(qǐng)求, 那么效率無(wú)疑很低. 后來(lái)HTTP/1.1提出了Pipeline技術(shù), 允許一個(gè)TCP連接同時(shí)發(fā)送多個(gè)請(qǐng)求. 這樣就提升了傳輸效率.
在這樣的背景下, 隊(duì)頭阻塞發(fā)生了. 比如, 一個(gè)TCP連接同時(shí)傳輸10個(gè)請(qǐng)求, 其中1,2,3個(gè)請(qǐng)求給客戶端接收, 但是第四個(gè)請(qǐng)求丟失, 那么后面第5-10個(gè)請(qǐng)求都被阻塞. 需要等第四個(gè)請(qǐng)求處理完畢后才能被處理. 這樣就浪費(fèi)了帶寬資源.
因此, HTTP一般又允許每個(gè)主機(jī)建立6個(gè)TCP連接, 這樣可以更加充分的利用帶寬資源, 但每個(gè)連接中隊(duì)頭阻塞的問(wèn)題還是存在的.
HTTP/2的多路復(fù)用解決了上述的隊(duì)頭阻塞問(wèn)題. 在HTTP/2中, 每個(gè)請(qǐng)求都被拆分為多個(gè)Frame通過(guò)一條TCP連接同時(shí)被傳輸, 這樣即使一個(gè)請(qǐng)求被阻塞, 也不會(huì)影響其他的請(qǐng)求.
但是,?HTTP/2雖然可以解決請(qǐng)求這一粒度下的阻塞, 但HTTP/2的基礎(chǔ)TCP協(xié)議本身卻也存在隊(duì)頭阻塞的問(wèn)題. HTTP/2的每個(gè)請(qǐng)求都會(huì)被拆分成多個(gè)Frame, 不同請(qǐng)求的Frame組合成Stream, Stream是TCP上的邏輯傳輸單元, 這樣HTTP/2就達(dá)到了一條連接同時(shí)發(fā)送多個(gè)請(qǐng)求的目標(biāo), 其中Stram1已經(jīng)正確送達(dá), Stram2中的第三個(gè)Frame丟失, TCP處理數(shù)據(jù)是有嚴(yán)格的前后順序, 先發(fā)送的Frame要先被處理, 這樣就會(huì)要求發(fā)送方重新發(fā)送第三個(gè)Frame, Steam3和Steam4雖然已到達(dá)但卻不能被處理, 那么這時(shí)整條鏈路都會(huì)被阻塞.
不僅如此, 由于HTTP/2必須使用HTTPS, 而HTTPS使用TLS協(xié)議也存在隊(duì)頭阻塞問(wèn)題. TLS基于Record組織數(shù)據(jù), 將一對(duì)數(shù)據(jù)放在一起加密, 加密完成后又拆分成多個(gè)TCP包傳輸. 一般每個(gè)Record 16K, 包含12個(gè)TCP包, 這樣如果12個(gè)TCP包中有任何一個(gè)包丟失, 那么整個(gè)Record都無(wú)法解密.
隊(duì)頭阻塞會(huì)導(dǎo)致HTTP/2在更容易丟包的弱網(wǎng)絡(luò)環(huán)境下比HTTP/1.1更慢. QUIC是如何解決隊(duì)頭阻塞的問(wèn)題的? 主要有兩點(diǎn):
QUIC的傳輸單位是Packet, 加密單元也是Packet, 整個(gè)加密, 傳輸, 解密都基于Packet, 這就能避免TLS的阻塞問(wèn)題.
QUIC基于UDP, UDP的數(shù)據(jù)包在接收端沒(méi)有處理順序, 即使中間丟失一個(gè)包, 也不會(huì)阻塞整條連接. 其他的資源會(huì)被正常處理.
擁塞控制
擁塞控制的目的是避免過(guò)多的數(shù)據(jù)一下子涌入網(wǎng)絡(luò), 導(dǎo)致網(wǎng)絡(luò)超出最大負(fù)荷. QUIC的擁塞控制與TCP類似, 并在此基礎(chǔ)上做了改進(jìn). 先來(lái)看看TCP的擁塞控制.
慢啟動(dòng): 發(fā)送方像接收方發(fā)送一個(gè)單位的數(shù)據(jù), 收到確認(rèn)后發(fā)送2個(gè)單位, 然后是4個(gè), 8個(gè)依次指數(shù)增長(zhǎng), 這個(gè)過(guò)程中不斷試探網(wǎng)絡(luò)的擁塞程度.
避免擁塞: 指數(shù)增長(zhǎng)到某個(gè)限制之后, 指數(shù)增長(zhǎng)變?yōu)榫€性增長(zhǎng)
快速重傳: 發(fā)送方每一次發(fā)送都會(huì)設(shè)置一個(gè)超時(shí)計(jì)時(shí)器, 超時(shí)后認(rèn)為丟失, 需要重發(fā)
快速恢復(fù): 在上面快速重傳的基礎(chǔ)上, 發(fā)送方重新發(fā)送數(shù)據(jù)時(shí), 也會(huì)啟動(dòng)一個(gè)超時(shí)定時(shí)器, 如果收到確認(rèn)消息則進(jìn)入擁塞避免階段, 如果仍然超時(shí), 則回到慢啟動(dòng)階段.
QUIC重新實(shí)現(xiàn)了TCP協(xié)議中的Cubic算法進(jìn)行擁塞控制, 下面是QUIC改進(jìn)的擁塞控制的特性:
1. 熱插拔
TCP中如果要修改擁塞控制策略, 需要在系統(tǒng)層面今次那個(gè)操作, QUIC修改擁塞控制策略只需要在應(yīng)用層操作, 并且QUIC會(huì)根據(jù)不同的網(wǎng)絡(luò)環(huán)境, 用戶來(lái)動(dòng)態(tài)選擇擁塞控制算法.
2. 前向糾錯(cuò) FEC
QUIC使用前向糾錯(cuò)(FEC, Forword Error Correction)技術(shù)增加協(xié)議的容錯(cuò)性. 一段數(shù)據(jù)被切分為10個(gè)包后, 一次對(duì)每個(gè)包進(jìn)行異或運(yùn)算, 運(yùn)算結(jié)果會(huì)作為FEC包與數(shù)據(jù)包一起被傳輸, 如果傳輸過(guò)程中有一個(gè)數(shù)據(jù)包丟失, 那么就可以根據(jù)剩余9個(gè)包以及FEC包推算出丟失的那個(gè)包的數(shù)據(jù), 這樣就大大增加了協(xié)議的容錯(cuò)性.
這是符合現(xiàn)階段網(wǎng)絡(luò)傳輸技術(shù)的一種方案, 現(xiàn)階段帶寬已經(jīng)不是網(wǎng)絡(luò)傳輸?shù)钠款i, 往返時(shí)間才是, 所以新的網(wǎng)絡(luò)傳輸協(xié)議可以適當(dāng)增加數(shù)據(jù)冗余, 減少重傳操作.
3. 單調(diào)遞增的Packer Number
TCP為了保證可靠性, 使用Sequence Number和ACK來(lái)確認(rèn)消息是否有序到達(dá), 但這樣的設(shè)計(jì)存在缺陷. 超時(shí)發(fā)生后客戶端發(fā)起重傳, 后來(lái)接受到了ACK確認(rèn)消息, 但因?yàn)樵颊?qǐng)求和重傳請(qǐng)求接受到的ACK消息一樣, 所以客戶端就不知道這個(gè)ACK對(duì)應(yīng)的是原始請(qǐng)求還是重傳請(qǐng)求. 這就會(huì)造成歧義.
RTT: Round Trip Time, 往返事件
RTO: Retransmission Timeout, 超時(shí)重傳時(shí)間
如果客戶端認(rèn)為是重傳的ACK, 但實(shí)際上是右圖的情形, 會(huì)導(dǎo)致RTT偏小, 反之會(huì)導(dǎo)致RTT偏大.
QUCI解決了上面的的歧義問(wèn)題, 與Sequence Number不同,?Packet Number嚴(yán)格單調(diào)遞增, 如果Packet N丟失了, 那么重傳時(shí)Packet的標(biāo)識(shí)就不會(huì)是N, 而是比N大的數(shù)字, 比如N+M, 這樣發(fā)送方接收到確認(rèn)消息時(shí), 就能方便的知道ACK對(duì)應(yīng)的原始請(qǐng)求還是重傳請(qǐng)求.
4. ACK Delay
TCP計(jì)算RTT時(shí)沒(méi)有考慮接收方接受到數(shù)據(jù)發(fā)發(fā)送方確認(rèn)消息之間的延遲, 如下圖所示, 這段延遲即ACK Delay. QUIC考慮了這段延遲, 使得RTT的計(jì)算更加準(zhǔn)確.
5. 更多的ACK塊
一般來(lái)說(shuō), 接收方收到發(fā)送方的消息后都應(yīng)該發(fā)送一個(gè)ACK恢復(fù), 表示收到了數(shù)據(jù). 但每收到一個(gè)數(shù)據(jù)就返回一個(gè)ACK恢復(fù)實(shí)在太麻煩, 所以一般不會(huì)立即回復(fù), 而是接受到多個(gè)數(shù)據(jù)后再回復(fù), TCP SACK最多提供3個(gè)ACK block. 但在有些場(chǎng)景下, 比如下載, 只需要服務(wù)器返回?cái)?shù)據(jù)就好, 但按照TCP的設(shè)計(jì), 每收到三個(gè)數(shù)據(jù)包就要返回一個(gè)ACK, 而QUIC最多可以捎帶256個(gè)ACK block, 在丟包率比較嚴(yán)重的網(wǎng)絡(luò)下, 更多的ACK可以減少重傳量, 提升網(wǎng)絡(luò)效率.
瀏覽控制
TCP?會(huì)對(duì)每個(gè)TCP連接進(jìn)行流量控制, 流量控制的意思是讓發(fā)送方不要發(fā)送太快, 要讓接收方來(lái)得及接受, 不然會(huì)導(dǎo)致數(shù)據(jù)溢出而丟失, TCP的流量控制主要通過(guò)滑動(dòng)窗口來(lái)實(shí)現(xiàn)的. 可以看到, 擁塞控制主要是控制發(fā)送方的發(fā)送策略, 但沒(méi)有考慮接收方的接收能力, 流量控制是對(duì)部分能力的不起.
QUIC只需要建立一條連接, 在這條連接上同時(shí)傳輸多條Stream, 好比有一條道路, 量都分別有一個(gè)倉(cāng)庫(kù), 道路中有很多車輛運(yùn)送物資. QUIC的流量控制有兩個(gè)級(jí)別: 連接級(jí)別(Connection Level)和Stream 級(jí)別(Stream Level).
對(duì)于單條的Stream的流量控制: Stream還沒(méi)傳輸數(shù)據(jù)時(shí), 接收窗口(flow control recevice window)就是最大接收窗口, 隨著接收方接收到數(shù)據(jù)后, 接收窗口不斷縮小. 在接收到的數(shù)據(jù)中, 有的數(shù)據(jù)已被處理, 而有的數(shù)據(jù)還沒(méi)來(lái)得及處理. 如下圖, 藍(lán)色塊表示已處理數(shù)據(jù), 黃色塊表示違背處理數(shù)據(jù), 這部分?jǐn)?shù)據(jù)的到來(lái), 使得Stream的接收窗口縮小.
隨著數(shù)據(jù)不斷被處理, 接收方就有能力處理更多數(shù)據(jù). 當(dāng)滿足(flow control receivce offset - consumed bytes) < (max receive window/2)時(shí), 接收方會(huì)發(fā)送WINDOW_UPDATE frame告訴發(fā)送方你可以再多發(fā)送數(shù)據(jù), 這時(shí)候flow control receive offset就會(huì)偏移, 接收窗口增大, 發(fā)送方可以發(fā)送更多數(shù)據(jù)到接收方.
Stream級(jí)別對(duì)防止接收端接收過(guò)多數(shù)據(jù)作用有限, 更需要借助Connection級(jí)別的流量控制. 理解了Stream流量那么也很好理解Connection的流控. Stream中,
接收窗口=最大接受窗口 - 已接收數(shù)據(jù)而對(duì)于Connection來(lái)說(shuō):
接收窗口 = Stream1 接收窗口 + Stream2 接收窗口 + ... + StreamN 接收窗口
審核編輯:湯梓紅
評(píng)論
查看更多