聽到 HTTP/3 基于 UDP 協(xié)議的消息,不少人可能都跟我一樣驚呆了。 我們從開始學(xué)習(xí)網(wǎng)絡(luò)協(xié)議就一定會接觸到 HTTP,而教科書或者老師一直以來說的都是“UDP 不可靠,所以 HTTP 基于 TCP 協(xié)議”,雖然偶爾會思考“UDP 與 TCP 都是比較底層的協(xié)議,用 TCP 來定義上層的 HTTP 協(xié)議,也是需要經(jīng)過一系列設(shè)計和封裝的,那憑什么 UDP 就不可以試試呢?”、“是成本問題?HTTP 在 TCP 之上設(shè)計的成本也不低啊,比如三次握手、四次揮手、滑動窗口等構(gòu)思精妙的算法,也都是在經(jīng)過無數(shù)次設(shè)計與嘗試之后確定下來的。”……但是總之 HTTP 只能基于 TCP,而不能是 UDP 這一思維還是在一道道試題和一次次編程 request-response 的過程中固定在腦海里。 所以 HTTP/3 不再基于 TCP 而是采用了 UDP,這一消息還是挺讓人驚訝的。 看到這里可能有人會驚訝于另一個點:什么?!HTTP 協(xié)議都發(fā)展到 v3 了? 其實目前正逐漸走向主流的 HTTP 協(xié)議是 HTTP/2,它相比于 HTTP/1,大幅度提高了性能,網(wǎng)站只需要升級到新版本協(xié)議就可以減少很多之前需要做的性能優(yōu)化工作,當(dāng)然兼容問題以及如何優(yōu)雅降級是比較棘手的問題,這也應(yīng)該是國內(nèi)目前還不普遍使用 HTTP/2 的重要原因。 雖然 HTTP/2 帶來了許多優(yōu)點,但是并不代表它已經(jīng)是完美的了,HTTP/3 就是為了解決 HTTP/2 所存在的一些問題而被推出來的。 本文接下來會從基礎(chǔ)的 HTTP/1 開始講起,從第一代協(xié)議到第三代分別針對性地介紹,試圖把 HTTP 這一協(xié)議的技術(shù)發(fā)展過程以簡要通俗的方式分享給讀者,并讓大家明白,為什么經(jīng)過這么多年的發(fā)展,HTTP 協(xié)議最終竟然采用了不安全的 UDP。 一、HTTP 協(xié)議HTTP 是 HyperText Transfer Protocol(超文本傳輸協(xié)議)的縮寫,它是互聯(lián)網(wǎng)上應(yīng)用最為廣泛的一種網(wǎng)絡(luò)協(xié)議,所有 WWW 文件都必須遵守這個標(biāo)準(zhǔn)。 伴隨著計算機(jī)網(wǎng)絡(luò)和瀏覽器的誕生,HTTP 1.0/1.1 也隨之而來,它建立在 TCP 協(xié)議之上,處于計算機(jī)網(wǎng)絡(luò)中的應(yīng)用層,所以 HTTP 協(xié)議的瓶頸及其優(yōu)化技巧都是基于 TCP 協(xié)議本身的特性,例如 TCP 建立連接的 3 次握手和斷開連接的 4 次揮手,以及每次建立連接帶來的 RTT 延遲時間等。 HTTP 1.0 與 1.1 的主要區(qū)別在于長連接支持、多路復(fù)用、帶寬節(jié)約與數(shù)據(jù)壓縮等,相對于 HTTP/2,本文將其通稱為 HTTP/1。 二、HTTP/1 的缺陷HTTP/1 在 Web 時代迅速崛起,但是隨著采用日漲,其缺陷也暴露出來。 不管是 1.0 還是 1.1 版本,HTTP/1 都主要存在以下幾個方面的缺陷:
三、SPDY 協(xié)議因為 HTTP/1 的問題,我們會引入雪碧圖、將小圖內(nèi)聯(lián)、使用多個域名等等的方式來提高性能。不過這些優(yōu)化都繞開了協(xié)議本身,直到 2009 年,谷歌公開了自行研發(fā)的 SPDY 協(xié)議,它主要解決 HTTP/1.1 效率不高的問題。 直到這時,才算是正式改造了 HTTP 協(xié)議本身。SPDY 進(jìn)行延遲降低、header 壓縮等改進(jìn),其實踐證明了這些優(yōu)化的效果,也最終帶來 HTTP/2 的誕生。 SPDY 協(xié)議在 Chrome 瀏覽器上證明可行以后,就被當(dāng)作 HTTP/2 的基礎(chǔ),主要特性都在 HTTP/2 之中得到繼承,下面我們就來講講這一部分內(nèi)容。 四、HTTP/22015 年,繼承于 SPDY 的 HTTP/2 協(xié)議發(fā)布了。HTTP/2 是 HTTP/1 的替代品,但它不是重寫,協(xié)議中還保留著第一代的一些內(nèi)容,比如 HTTP 方法、狀態(tài)碼與語義等都與 HTTP/1 一樣。 HTTP/2 基于SPDY3,專注于性能,最大的一個目標(biāo)是在用戶和網(wǎng)站間只用一個連接。 HTTP/2 由兩個規(guī)范組成:
五、HTTP/2 特性二進(jìn)制傳輸HTTP/2 采用二進(jìn)制格式傳輸數(shù)據(jù),而非 HTTP/1 的文本格式,二進(jìn)制協(xié)議解析起來更高效。 HTTP/1 的請求和響應(yīng)報文,都是由起始行、首部和實體正文(可選)組成,各部分之間以文本換行符分隔。HTTP/2 將請求和響應(yīng)數(shù)據(jù)分割為更小的幀,并且它們采用二進(jìn)制編碼。 接下來我們介紹幾個重要的概念:
HTTP/2 中,同域名下所有通信都在單個連接上完成,該連接可以承載任意數(shù)量的雙向數(shù)據(jù)流。每個數(shù)據(jù)流都以消息的形式發(fā)送,而消息又由一個或多個幀組成。多個幀之間可以亂序發(fā)送,根據(jù)幀首部的流標(biāo)識可以重新組裝。 多路復(fù)用在 HTTP/2 中引入了多路復(fù)用技術(shù)。多路復(fù)用很好地解決了瀏覽器限制同一個域名下的請求數(shù)量的問題,同時也更容易實現(xiàn)全速傳輸,畢竟新開一個 TCP 連接都需要慢慢提升傳輸速度。 大家可以通過這個鏈接(http2.akamai.com/demo)直觀感受下 HTTP/2 比 HTTP/1 到底快了多少。
這一特性,使性能有了極大提升:
Header 壓縮在 HTTP/1 中,我們使用文本的形式傳輸 header,在 header 攜帶 cookie 的情況下,可能每次都需要重復(fù)傳輸幾百到幾千字節(jié)。 為了減少這塊的資源消耗并提升性能, HTTP/2 對這些首部采取了壓縮策略:
例如下圖中的兩個請求, 請求 1 發(fā)送了所有頭部字段,第二個請求則只需要發(fā)送差異數(shù)據(jù),這樣可以減少冗余數(shù)據(jù),降低開銷: Server PushServer Push 即服務(wù)端能通過 push 的方式將客戶端需要的內(nèi)容預(yù)先推送過去,也叫“cache push”。 可以想象以下情況:某些資源客戶端是一定會請求的,這時就可以采取服務(wù)端 push 的技術(shù),提前給客戶端推送必要的資源,這樣就可以相對減少一點延遲時間。當(dāng)然在瀏覽器兼容的情況下你也可以使用 prefetch。 例如服務(wù)端可以主動把 JS 和 CSS 文件推送給客戶端,而不需要客戶端解析 HTML 時再發(fā)送這些請求。 服務(wù)端可以主動推送,客戶端也有權(quán)利選擇是否接收。如果服務(wù)端推送的資源已經(jīng)被瀏覽器緩存過,瀏覽器可以通過發(fā)送 RST_STREAM 幀來拒收。主動推送也遵守同源策略,換句話說,服務(wù)器不能隨便將第三方資源推送給客戶端,而必須是經(jīng)過雙方確認(rèn)才行。 六、HTTP/3雖然 HTTP/2 解決了很多之前舊版本的問題,但是它還是存在一個巨大的問題,主要是底層支撐的 TCP 協(xié)議造成的。 上文提到 HTTP/2 使用了多路復(fù)用,一般來說同一域名下只需要使用一個 TCP 連接。但當(dāng)這個連接中出現(xiàn)了丟包的情況,那就會導(dǎo)致 HTTP/2 的表現(xiàn)情況反倒不如 HTTP/1 了。 因為在出現(xiàn)丟包的情況下,整個 TCP 都要開始等待重傳,也就導(dǎo)致了后面的所有數(shù)據(jù)都被阻塞了。但是對于 HTTP/1.1 來說,可以開啟多個 TCP 連接,出現(xiàn)這種情況反到只會影響其中一個連接,剩余的 TCP 連接還可以正常傳輸數(shù)據(jù)。 那么可能就會有人考慮到去修改 TCP 協(xié)議,其實這已經(jīng)是一件不可能完成的任務(wù)了,因為 TCP 存在的時間實在太長,已經(jīng)充斥在各種設(shè)備中,并且這個協(xié)議是由操作系統(tǒng)實現(xiàn)的,更新起來不大現(xiàn)實。 基于這個原因,Google 就自己架起爐灶搞了一個基于 UDP 協(xié)議的 QUIC 協(xié)議,并且使用在了 HTTP/3 上,HTTP/3 之前名為 HTTP-over-QUIC,從這個名字中我們也可以發(fā)現(xiàn),HTTP/3 最大的改造就是使用了 QUIC。 QUIC 雖然基于 UDP,但是在原本的基礎(chǔ)上新增了很多功能,接下來我們重點介紹幾個 QUIC 功能。 QUIC 功能
通過使用類似 TCP 快速打開的技術(shù),緩存當(dāng)前會話的上下文,在下次恢復(fù)會話的時候,只需要將之前的緩存?zhèn)鬟f給服務(wù)端驗證通過就可以進(jìn)行傳輸了。0RTT 建連可以說是 QUIC 相比 HTTP/2 最大的性能優(yōu)勢。那什么是 0RTT 建連呢? 這里面有兩層含義: 1、傳輸層 0RTT 就能建立連接。 2、加密層 0RTT 就能建立加密連接。 因為這里考慮到安全性,我們就拿加了 TLS 的“安全的 HTTP 協(xié)議”HTTPS 來對比。上圖左邊是 HTTPS 的一次完全握手的建連過程,需要 3 個 RTT,就算是會話復(fù)用也需要至少 2 個 RTT。 而 QUIC 呢?由于建立在 UDP 的基礎(chǔ)上,同時又實現(xiàn)了 0RTT 的安全握手,所以在大部分情況下,只需要 0 個 RTT 就能實現(xiàn)數(shù)據(jù)發(fā)送,在實現(xiàn)前向加密的基礎(chǔ)上,并且 0RTT 的成功率相比 TLS 的會話記錄單要高很多。
QUIC 原生實現(xiàn)了多路復(fù)用功能,并且傳輸?shù)膯蝹€數(shù)據(jù)流可以保證有序交付且不會影響其它數(shù)據(jù)流,這樣的技術(shù)就解決了前邊提到的 TCP 多路復(fù)用存在的問題。 同 HTTP/2 一樣,同一個 QUIC 連接上可以創(chuàng)建多個 stream 來發(fā)送多個 HTTP 請求,但是,QUIC 是基于 UDP 的,因為一個連接上的多個 stream 之間沒有依賴,所以不存在 HTTP/2 中的問題。比如下圖中 stream2 丟了一個 UDP 包,不會影響后面跟著 Stream3 和 Stream4,不存在 TCP 隊頭阻塞。雖然 stream2 的那個包需要重新傳,但是 stream3、stream4 的包無需等待就可以發(fā)給用戶。
TCP 協(xié)議頭部沒有經(jīng)過任何加密和認(rèn)證,所以在傳輸過程中很容易被中間網(wǎng)絡(luò)設(shè)備篡改、注入和竊聽,比如修改序列號與滑動窗口。這些行為有可能是出于性能優(yōu)化,也有可能是主動攻擊。 相比之下,QUIC 的 packet 可以說是武裝到了牙齒。除了個別報文比如 PUBLIC_RESET 和 CHLO,所有報文頭部都是經(jīng)過認(rèn)證的,報文 Body 都是經(jīng)過加密的。 這樣只要是針對 QUIC 報文進(jìn)行了任何修改,接收端都能夠及時發(fā)現(xiàn),有效地降低了安全風(fēng)險。 如上圖所示,紅色部分是 Stream Frame 的報文頭部,有認(rèn)證;綠色部分是報文內(nèi)容,全部經(jīng)過加密。
QUIC 協(xié)議有一個非常獨特的特性,稱為前向糾錯(Forward Error Correction,F(xiàn)EC),每個數(shù)據(jù)包除了它本身的內(nèi)容之外,還包括了部分其它數(shù)據(jù)包的數(shù)據(jù),因此少量的丟包可以通過其它包的冗余數(shù)據(jù)直接組裝而無需重傳。 前向糾錯犧牲了每個數(shù)據(jù)包可以發(fā)送數(shù)據(jù)的上限,但是減少了因為丟包導(dǎo)致的數(shù)據(jù)重傳次數(shù)。這會取得更好的效果,因為數(shù)據(jù)重傳將會消耗更多的時間,包括確認(rèn)數(shù)據(jù)包丟失、請求重傳與等待新數(shù)據(jù)包等步驟。 假如說這次我要發(fā)送三個包,那么協(xié)議會算出這三個包的異或值并單獨發(fā)出一個校驗包,也就是總共發(fā)出了四個包,當(dāng)出現(xiàn)其中的非校驗包丟包的情況時,可以通過另外三個包計算出丟失的數(shù)據(jù)包的內(nèi)容。當(dāng)然這種技術(shù)只能使用在丟失一個包的情況下,如果出現(xiàn)丟失多個包就不能使用糾錯機(jī)制了,只能使用重傳的方式了。 七、總結(jié)
從 HTTP/1 到 HTTP/3,HTTP 協(xié)議經(jīng)過不斷進(jìn)化,性能越來越高,在這個過程中,底層協(xié)議甚至從 TCP 改為了之前被認(rèn)定為不適合 UDP,這其中不斷探索的設(shè)計思想值得學(xué)習(xí)。雖然本文是簡單的介紹,但已經(jīng)把這一演進(jìn)過程簡單地總結(jié)了出來,希望讀者能夠有所收獲。 作者介紹浪里行舟,專注于前端領(lǐng)域。個人公眾號:前端工匠,致力于推送適合初中級工程師快速吸收的優(yōu)質(zhì)文章。 |
|