大家好,我是前端西瓜哥。今天我們來聊聊 HTTP/2 的多路復(fù)用。
HTTP/1 下的請求,并不能很好地地利用帶寬:一個 TCP 連接同時只能有一個 HTTP 請求和響應(yīng)。如果正在發(fā)送一個 HTTP 請求,那其他的 HTTP 請求就得排隊。
這種排隊會產(chǎn)生一個請求隊列,當(dāng)隊頭的請求發(fā)生意外(比如丟包、服務(wù)器響應(yīng)緩慢),導(dǎo)致比平時要慢得多,就會導(dǎo)致后面的請求被延遲。這種情況我們稱為 隊頭阻塞(Head-of-line blocking)。
為了緩解這個問題,瀏覽器會對同一個域名建立多個 TCP 連接,來實現(xiàn) HTTP 的并發(fā)。
但這也對服務(wù)器造成不小的負擔(dān),所以瀏覽器做了限制,同一個域名下 TCP 連接數(shù)最多會在 6 ~ 8 個左右。
如果網(wǎng)頁一次性加載的資源太多,比如大量圖片,6 個 TCP 連接數(shù)可能也會頂不住。為了解決一個問題,我們會使用 域名分片(Domain sharding) 的方法,就是將資源放到不同的域名下。
比如將圖片放到專門的 static.xxx.com ,或者 CDN。因為域名不同,所以總的 TCP 連接數(shù)就能突破 6 的限制。達到 域名數(shù) x 6。
HTTP/1.1 有一個 pipeline 機制,意圖解決不能并發(fā)的問題,但因為實現(xiàn)上的缺陷,實質(zhì)上已經(jīng)廢棄。瀏覽器也默認關(guān)閉 pipeline。
為了解決這個問題,HTTP/2 使用了 多路復(fù)用。
HTTP/2 引入了流(stream)和幀(frame)的概念。
幀是最小的數(shù)據(jù)單位,HTTP 報文不再是原來的明文的 ASCII 編碼,而是會被拆分成一個個的二進制形式的幀。幀上面除了 HTTP 數(shù)據(jù),還包含數(shù)據(jù)長度、流標(biāo)識符、幀類型等信息。
流是一個建立連接后的雙向的虛擬字節(jié)流,可以承載多個消息。幀通過自己的流 ID,確定自己屬于哪個報文,就可以不按順序進行請求響應(yīng)了。
HTTP/2 會將所有 HTTP 請求打散成幀,在一個 TCP 連接上做并發(fā)請求,充分利用 TCP 帶寬。現(xiàn)在瀏覽器對于 HTTP2,只會建立一個 TCP 連接,減輕了服務(wù)端不小壓力。
例子
我們舉個例子講解 HTTP/1 升級為 HTTP/2 后利用多路復(fù)用帶來的優(yōu)勢。
假設(shè)依次請求一個很大的 JS 文件,和一個很小的 css 文件。
在 HTTP/1 時,TCP 的發(fā)送的包是這樣的(JS 用多個 1 表示,CSS 用多個 2 表示):
111111111111111111111111222
JS 很大,會讓 CSS 延遲,我們可能希望比較小的 CSS 能早一點請求完,早一點做解析。而且 JS 一旦發(fā)生了意外發(fā)生阻塞,CSS 就更晚才能獲取到了。
現(xiàn)在我們用 HTTP/2,就變成了下面這樣:
121212111111111111111111111
因為并行的原因,CSS 不僅不用再擔(dān)心 JS 導(dǎo)致的阻塞,還能更早請求并獲取到資源。
結(jié)尾
HTTP/2 的多路復(fù)用能夠解決 HTTP 隊頭阻塞問題,更充分地利用 TCP 帶寬。
但因為還是在 TCP 上的協(xié)議,所以不能解決 TCP 隊頭阻塞問題,這個問題要交給 HTTP/3 通過 UDP 來解決了,期待一下。
我是前端西瓜哥,歡迎關(guān)注我,學(xué)習(xí)更多前端知識。