【HTTP】843- 揭秘 HTTP2
前言前段時間組內(nèi)小伙伴遇到了一個問題:一個頁面上有 10 個視頻,因為瀏覽器對 tcp 連接數(shù)的限制,導(dǎo)致同時只能加載 6 個視頻??紤]到http2協(xié)議的多路復(fù)用可以解決這個問題,特地整理此篇關(guān)于http2的內(nèi)容和大家分享。
下面我們先從http1.1說起。
一、http1.1 存在的問題
1.容易觸發(fā)瀏覽器 tcp 連接數(shù)限制
對于同一個域名,瀏覽器最多只能同時創(chuàng)建 6~8 個 TCP 連接 (不同瀏覽器不一樣)。因為一個tcp連接一次承載一個請求,也就是說一個時刻最多只能發(fā)起6~8個請求,這就是上文說到的只能同時發(fā)起 6 個視頻請求的問題。為了解決這個限制,行業(yè)內(nèi)慣用域名分區(qū)的方案,即將資源分散到不同域名下 (比如二級子域名),這樣就可以針對不同域名創(chuàng)建連接并請求。但多域名隨之而來的是更多的 dns 查詢耗時,以及更多 tcp 連接開銷。
2.“隊頭阻塞”問題
我們都知道,http1.1默認設(shè)置請求頭部字段keep-alive以保持 tcp 持久連接,以實現(xiàn)多個請求復(fù)用同一個 tcp 連接,避免重復(fù)建立連接造成的時間開銷。但一個問題是這時的 tcp 連接同一時刻只能處理一個 http 請求,即請求時序為“請求1->響應(yīng)1->請求2->響應(yīng)2...”,如果請求1沒完成,后續(xù)的請求2只能等待。
為了盡可能并行發(fā)送請求,http1.1 引入了管線技術(shù)(pipelining),優(yōu)化效果對比如下圖:

管線技術(shù)部分解決了請求并發(fā)的問題,仍存在隊頭阻塞的問題,原因如下:
請求可以并行發(fā)出,但是響應(yīng)必須串行返回。 前一個響應(yīng)未及時返回,后面的響應(yīng)就會被阻塞,這就是隊頭阻塞問題。 必須是冪等請求( GET和HEAD)才能管道化。因為,意外中斷時候,客戶端需要把未收到響應(yīng)的請求重發(fā),非冪等請求,會造成資源破壞。
那http2是如何解決這些問題的呢?
二、http2 的優(yōu)點
http2通過多路復(fù)用解決了http1.1隊頭阻塞和tcp連接數(shù)的問題,大家可以先通過下面這個例子(并行加載大量小圖)直觀感受出http2比http1.1快了很多。

讓我們來看看http2是如何做到的!
1.多路復(fù)用
http2把原來http所傳輸?shù)男畔澐譃槎鄠€粒度更小的幀,并對其進行二進制編碼,然后將其映射到屬于特定流的消息。
在一個 TCP 連接上,我們可以向?qū)Ψ讲粩喟l(fā)送幀,每幀的 stream identifier 的標明這一幀屬于哪個流,然后在對方接收時,根據(jù) stream identifier 拼接每個流的所有幀組成一整塊數(shù)據(jù)。我們可以把每個請求或者響應(yīng)都當(dāng)作一個流,那么多個請求變成多個流,這不同流的數(shù)據(jù)被分成多個幀,在一個連接中交錯地發(fā)送給對方,這就是 http2 中的多路復(fù)用。

多路復(fù)用依賴一個關(guān)鍵技術(shù)點,那就是二進制分幀:
二進制分幀層
二進制分幀層指示如何在客戶端和服務(wù)器之間封裝和傳輸http消息。它會將所有傳輸?shù)男畔⒎指顬榱6雀〉膸?,首部信息則被封裝到Headers幀,body則封裝到Data幀里面。每個幀都以固定的9字節(jié)首部開始,里面會至少標明其所屬的流。一個流則是一個請求或者響應(yīng)。正是基于幀和流,且來自不同流的幀可以交錯發(fā)送,才使多路復(fù)用可以實現(xiàn)。

我們前面說到了一個連接里面承載了多個流,并且不同流的幀可以交錯發(fā)送,那么客戶端和服務(wù)器交付不同流的幀的順序成為了關(guān)鍵的性能考慮因素,因為不同資源的優(yōu)先級是不一樣的,為了實現(xiàn)這一點,引入了流優(yōu)先級。
2.流優(yōu)先級
http2允許每個流具有流依賴關(guān)系以及相關(guān)的權(quán)重:
權(quán)重:可以為每個流分配1到256之間的整數(shù)權(quán)重
流依賴關(guān)系:每個流可以明確依賴一個流
客戶端使用權(quán)重和流依賴關(guān)系的組合信息,向服務(wù)端構(gòu)造和傳遞“優(yōu)先級樹”,該樹表明其希望如何接收響應(yīng),即我們期望優(yōu)先級越高的請求越快得到響應(yīng),服務(wù)端使用此信息確定流處理的優(yōu)先級,控制cpu、內(nèi)存和其他資源的分配。一旦響應(yīng)數(shù)據(jù)可用,就分配帶寬以確保向客戶端最佳的傳遞高優(yōu)先級響應(yīng)。那么如何確認流的優(yōu)先級呢?
流優(yōu)先級的計算

通過引用另一個流的唯一標識符作為其父級來聲明http2中的流依賴性; 如果省略,則稱該流依賴于“根流”。流依賴性表明,如果可能,則希望在處理它之前先為父流分配資源。例如:C依賴于D,則表明請在響應(yīng)C之前先處理并響應(yīng)D。
共享相同父級的流應(yīng)該按其權(quán)重比例分配資源。例如對于上圖流A和流B,他們都是根流,A的權(quán)重為12,B的權(quán)重為4,則A應(yīng)該接收到資源的比例為12/16=3/4。B接收到資源的比例為1/4。
不過,值得注意的是,流優(yōu)先級只是表達了一種傳輸偏好,不表示絕對的要求,因此不保證特定的處理或傳輸順序。雖然看上去覺得違反直覺,畢竟設(shè)置優(yōu)先級就是希望資源按照我設(shè)定的順序返回,可是卻又并不能保證絕對的順序。但其實這是合理的行為:當(dāng)高優(yōu)先級的資源阻塞的時候,低優(yōu)先級的資源不會被阻塞。
流優(yōu)先級的設(shè)置
流優(yōu)先級是由客戶端設(shè)置,發(fā)給服務(wù)端的。瀏覽器中有一個默認的優(yōu)先級。瀏覽器基于自身對資源重要性的判讀,為不同的資源分配相應(yīng)的優(yōu)先級。例如,頁面 中的
