《HTTP/2 基礎教程》 閱讀摘要

SHERlocked93發表於2019-04-16

最近粗線了不少 HTTP2 相關的帖子和討論,感覺新一輪的潮流在形成,所以最近找了本 HTTP2 相關書籍做知識儲備,剛好記成筆記以備後詢 ~

這本書本身不錯,缺點就是翻譯的有點蹩腳,另外因為是 2017 年出的書,所以有些內容時效性不太好,比如關於 Chrome 的部分,所以我根據 Chrome 的官方文件增加了點內容 ?

1. HTTP進化史

1.1 HTTP/0.9、HTTP/1.0、HTTP/1.1

  1. HTTP/0.9: 是個相當簡單的協議。它只有一個方法(GET),沒有首部,其設計目標也無非是獲取 HTML(也就是說沒有圖片,只有文字)。
  2. HTTP/1.0: 多了很多功能,首部、錯誤碼、重定向、條件請求等,但仍存在很多瑕疵,尤其是不能讓多個請求共用一個連線、缺少強制的 Host 首部、快取的選擇也相當簡陋,這三點影響了 Web 可擴充套件的方式。
  3. HTTP/1.1: 增加了快取相關首部的擴充套件、OPTIONS 方法、Upgrade 首部、Range 請求、壓縮和傳輸編碼、管道化等功能。因為強制要求客戶端提供 Host 首部,所以虛擬主機託管成為可能,也就是在一個 IP 上提供多個 Web 服務。另外使用了 keep-alive 之後,Web 伺服器也不需要在每個響應之後關閉連線。這對於提升效能和效率而言意義重大,因為瀏覽器再也不用為每個請求重新發起 TCP 連線了。

1.2 HTTP/2

HTTP2 被希望達到以下特性:

  • 相比 HTTP/1.1,效能顯著提高;
  • 解決 HTTP 中的隊頭阻塞問題;
  • 並行的實現機制不依賴與伺服器建立多個連線,從而提升 TCP 連線的利用率,特別是在擁塞控制方面;
  • 保留 HTTP/1.1 的語義,可以利用已有的文件資源,包括(但不限於) HTTP 方法、狀態碼、URI 和首部欄位;
  • 明確定義 HTTP/2.0 和 HTTP/1.x 互動的方法,特別是通過中介時的方法(雙向);
  • 明確指出它們可以被合理使用的新的擴充套件點和策略。

2. HTTP/2 快速入門

2.1 啟動並執行

很多網站已經在用HTTP/2(h2)了,比如 Facebook、Instagram、Twitter 等,下面介紹以下如何自己搭建 h2 伺服器。要執行 h2 伺服器,主要分兩步:

  1. 獲取並安裝一個支援 h2 的 Web 伺服器
  2. 下載並安裝一張 TLS 證照,讓瀏覽器和伺服器通過 h2 連線

2.2 獲取證照

證照可以通過三種方式獲取:

  1. 使用線上資源
  2. 自己建立一張證照
  3. 從數字證照認證機構(CA)申請一張證照

前兩個方法 將建立自簽名證照,僅用於測試,由於不是 CA 簽發的,瀏覽器會報警

後面關於建立 h2 伺服器的步驟就不記了,可以百度下

3. Web優化『黑魔法』的動機與方式

3.1 當前的效能挑戰

3.1.1 剖析Web頁面請求

從使用者在瀏覽器中點選連結到頁面呈現在螢幕上,在此期間到底發生了什麼?瀏覽器請求 Web 頁面時,會執行重複流程,獲取在螢幕上繪製頁面需要的所有資訊。為了更容易理解,我們把這一過程分成兩部分:資源獲取、頁面解析/渲染。

資源請求流程圖:

《HTTP/2 基礎教程》 閱讀摘要

流程為:

  1. 把待請求 URL 放入佇列
  2. 解析 URL 中域名的 IP 地址(A)
  3. 建立與目標主機的 TCP 連線(B)
  4. 如果是 HTTPS 請求,初始化並完成 TLS 握手(C)
  5. 向頁面對應的 URL 傳送請求。

資源響應流程圖:

《HTTP/2 基礎教程》 閱讀摘要

  1. 接收響應
  2. 如果(接收的)是主體 HTML,那麼解析它,並針對頁面中的資源觸發優先獲取機制(A)
  3. 如果頁面上的關鍵資源已經接收到,就開始渲染頁面(B)
  4. 接收其他資源,繼續解析渲染,直到結束(C)

頁面上的每一次點選,都需要重複執行前面那些流程,給網路頻寬和裝置資源帶來壓力。Web 效能優化的的核心,就是加快甚至乾脆去掉其中的某些步驟。

3.1.2 關鍵效能指標

下面網路級別的效能指標,它會影響整個 Web 頁面載入。

  1. 延遲: 指 IP 資料包從一個網路端點到另一個網路端點所花費的時間。
  2. 頻寬: 只要頻寬沒有飽和,兩個網路端點之間的連線會一次處理儘可能多的資料量。
  3. DNS查詢: 在客戶端能夠獲取 Web 頁面前,它需要通過域名系統(DNS)把主機名稱轉換成 IP 地址。
  4. 建立連線時間: 在客戶端和伺服器之間建立連線需要三次握手。握手時間一般與客戶端和伺服器之間的延遲有關。
  5. TLS協商時間: 如果客戶端發起 HTTPS 連線,它還需要進行傳輸層安全協議(TLS)協商,TLS 會造成額外的往返傳輸。
  6. 首位元組時間(TTFB): TTFB 是指客戶端從開始定位到 Web 頁面,至接收到主體頁面響應的第一位元組所耗費的時間。它包含了之前提到的各種耗時,還要加上伺服器處理時間。對於主體頁面上的資源,TTFB 測量的是從瀏覽器發起請求至收到其第一位元組之間的耗時。
  7. 內容下載時間: 等同於被請求資源的最後位元組到達時間(TTLB)。
  8. 開始渲染時間: 客戶端的螢幕上什麼時候開始顯示內容?這個指標測量的是使用者看到空白頁面的時長。
  9. 文件載入完成時間: 這是客戶端瀏覽器認為頁面載入完畢的時間。

3.1.3 HTTP/1 的問題

HTTP/1 的問題自然是 HTTP/2 要解決的核心問題

1. 隊頭阻塞

瀏覽器很少只從一個域名獲取一份資源,一般希望能同時獲取許多資源。h1 有個特性叫管道化(pipelining),允許一次傳送一組請求,但是隻能按照傳送順序依次接收響應。管道化備受互操作性和部署的各種問題的困擾,基本沒有實用價值。 在請求應答過程中,如果出現任何狀況,剩下所有的工作都會被阻塞在那次請求應答之後。這就是『隊頭阻塞』,它會阻礙網路傳輸和 Web 頁面渲染,直至失去響應。為了防止這種問題,現代瀏覽器會針對單個域名開啟 6 個連線,通過各個連線分別傳送請求。它實現了某種程度上的並行,但是每個連線仍會受到的影響。

2. 低效的 TCP 利用

傳輸控制協議(TCP)的設計思路是:對假設情況很保守,並能夠公平對待同一網路的不同流量的應用。涉及的核心概念就是擁塞視窗(congestion window)。『擁塞視窗』是指,在接收方確認資料包之前,傳送方可以發出的 TCP 包的數量。 例如,如果擁塞視窗指定為 1,那麼傳送方發出 1 個資料包之後,只有接收方確認了那個包,才能傳送下一個。

TCP 有個概念叫慢啟動(Slow Start),它用來探索當前連線對應擁塞視窗的合適大小。慢啟動的設計目標是為了讓新連線搞清楚當前網路狀況,避免給已經擁堵的網路繼續添亂。它允許傳送者在收到每個確認回覆後額外傳送 1 個未確認包。這意味著新連線在收到 1 個確認回覆之後,可以傳送 2 個資料包;在收到 2 個確認回覆之後,可以發 4 個,以此類推。這種幾何級數增長很快到達協議規定的發包數上限,這時候連線將進入擁塞避免階段。

TCP擁塞控制

這種機制需要幾次往返資料請求才能得知最佳擁塞視窗大小。但在解決效能問題時,就這區區幾次資料往返也是非常寶貴的時間成本。如果你把一個資料包設定為最大值下限 1460 位元組,那麼只能先傳送 5840 位元組(假定擁塞視窗為 4),然後就需要等待接收確認回覆。理想情況下,這需要大約 9 次往返請求來傳輸完整個頁面。除此之外,瀏覽器一般會針對同一個域名開啟 6 個併發連線,每個連線都免不了擁塞視窗調節。

傳統 TCP 實現利用擁塞控制演算法會根據資料包的丟失來反饋調整。如果資料包確認丟失了,演算法就會縮小擁塞視窗。這就類似於我們在黑暗的房間摸索,如果腿碰到了桌子就會馬上換個方向。如果遇到超時,也就是等待的回覆沒有按時抵達,它甚至會徹底重置擁塞視窗並重新進入慢啟動階段。新的演算法會把其他因素也考慮進來,例如延遲,以提供更妥善的反饋機制。

前面提到過,因為 h1 並不支援多路複用,所以瀏覽器一般會針對指定域名開啟 6 個併發連線。這意味著擁塞視窗波動也會並行發生 6 次。TCP 協議保證那些連線都能正常工作,但是不能保證它們的效能是最優的。

3. 臃腫的訊息首部

雖然 h1 提供了壓縮被請求內容的機制,但是訊息首部卻無法壓縮。訊息首部可不能忽略,儘管它比響應資源小很多,但它可能佔據請求的絕大部分(有時候可能是全部)。如果算上 cookie,就更大了。

訊息首部壓縮的缺失也容易導致客戶端到達頻寬上限,對於低頻寬或高擁堵的鏈路尤其如此。『體育館效應』(Stadium Effect)就是一個經典例子。如果成千上萬人同一時間出現在同一地點(例如重大體育賽事),會迅速耗盡無線蜂窩網路頻寬。這時候,如果能壓縮請求首部,把請求變得更小,就能夠緩解頻寬壓力,降低系統的總負載。

4. 受限的優先順序設定

如果瀏覽器針對指定域名開啟了多個 socket(每個都會受隊頭阻塞問題的困擾),開始請求資源,這時候瀏覽器能指定優先順序的方式是有限的:要麼發起請求,要麼不發起。然而 Web 頁面上某些資源會比另一些更重要,這必然會加重資源的排隊效應。這是因為瀏覽器為了先請求優先順序高的資源,會推遲請求其他資源。但是優先順序高的資源獲取之後,在處理的過程中,瀏覽器並不會發起新的資源請求,所以伺服器無法利用這段時間傳送優先順序低的資源,總的頁面下載時間因此延長了。還會出現這樣的情況:一個高優先順序資源被瀏覽器發現,但是受制於瀏覽器處理的方式,它被排在了一個正在獲取的低優先順序資源之後。

5. 第三方資源

如今 Web 頁面上請求的很多資源完全獨立於站點伺服器的控制,我們稱這些為第三方資源。現代 Web 頁面載入時長中往往有一半消耗在第三方資源上。雖然有很多技巧能把第三方資源對頁面效能的影響降到最低,但是很多第三方資源都不在 Web 開發者的控制範圍內,所以很可能其中有些資源的效能很差,會延遲甚至阻塞頁面渲染。令人掃興的是, h2 對此也束手無策。

3.2 Web效能優化技術

2010 年,谷歌把 Web 效能作為影響頁面搜尋評分的重要因素之一,效能指標開始在搜尋引擎中發揮作用。對於很多 Web 頁面,瀏覽器的大塊時間並不是用於呈現來自網站的主體內容(通常是 HTML),而是在請求所有資源並渲染頁面。

因此,Web 開發者逐漸更多地關注通過減少客戶端網路延遲和優化頁面渲染效能來提升Web 效能。

3.2.1 Web效能的最佳實踐

1. DNS 查詢優化

在與服務主機建立連線之前,需要先解析域名;那麼,解析越快就越好。下面有一些方法:

  1. 限制不同域名的數量。當然,這通常不是你能控制的
  2. 保證低限度的解析延遲。瞭解你的 DNS 服務基礎設施的結構,然後從你的終端使用者分佈的所有地域定期監控解析時間
  3. 在 HTML 或響應中利用 DNS 預取指令。這樣,在下載並處理 HTML 的同時,預取指令就能開始解析頁面上指定的域名
 <link rel="dns-prefetch" href="//ajax.googleapis.com">
複製程式碼
2. 優化 TCP 連線

本章前面提到過,開啟新連線是一個耗時的過程。如果連線使用 TLS(也確實應該這麼做),開銷會更高。降低這種開銷的方法如下

  1. 儘早終止並響應。藉助 CDN,在距離請求使用者很近的邊緣端點上,請求就可以獲得響應,所以可以終止連線,大幅減少建立新連線的通訊延遲。
  2. 實施最新的 TLS 最佳實踐來優化 HTTPS。
  3. 利用 preconnect 指令,連線在使用之前就已經建立好了,這樣處理流程的關鍵路徑上就不必考慮連線時間了,preconnect 不光會解析 DNS,還會建立 TCP 握手連線和 TLS 協議(如果需要)
<link rel="preconnect" href="//fonts.example.com" crossorigin>
複製程式碼

如果要從同一個域名請求大量資源,瀏覽器將自動開啟到伺服器的併發連線,避免資源獲取瓶頸。雖然現在大部分瀏覽器支援 6 個或更多的併發連線數目,但你不能直接控制瀏覽器針對同一域名的併發連線數。

3. 避免重定向

重定向通常觸發與額外域名建立連線。在行動網路中,一次額外的重定向可能把延遲增加數百毫秒,這不利於使用者體驗,並最終會影響到網站上的業務。簡單的解決方案就是徹底消滅重定向,因為對於重定向的使往往並沒有合理原因。如果它們不能被直接消滅,你還有兩個選擇:

  1. 利用 CDN 代替客戶端在雲端實現重定向
  2. 如果是同一域名的重定向,使用 Web 伺服器上的 rewrite 規則,避免重定向
4. 客戶端快取

沒有什麼比直接從本地快取獲取資源來得更快,因為它根本就不需要建立網路連線。

  1. 所謂的純靜態內容,例如圖片或帶版本的資料,可以在客戶端永久快取。即便 TTL 被設定得很長,比如一個月,它還是會因為快取提早回收或清理而過期,這時客戶端可能不得不從源頭再次獲取。
  2. CSS/JS 和個性化資源,快取時間大約是會話(互動)平均時間的兩倍。這段時間足夠長,保證大多數使用者在瀏覽網站時能夠從本地拉取資源;同時也足夠短,幾乎能保證下次會話時從網路上拉取最新內容。

可以通過 HTTP 首部指定 cache control 以及鍵 max-age(以秒為單位),或者 expires 首部。

5. 網路邊緣的快取

個人資訊(使用者偏好、財務資料等)絕對不能在網路邊緣快取,因為它們不能共享。時間敏感的資源也不應該快取,例如實時交易系統上的股票報價。這就是說,除此之外其他一切都是可以快取的,即使僅僅快取幾秒或幾分鐘。對於那些不是經常更新,然而一旦有變化就必須立刻更新的資源,例如重大新聞,可以利用各大 CDN 廠商提供的快取清理(purging)機制處理。這種模式被稱為『一直保留,直到被通知』(Hold til Told),意思是永久快取這些資源,等收到通知後才刪除。

6. 條件快取

如果快取 TTL 過期,客戶端會向伺服器發起請求。在多數情況下,收到的響應其實和快取的版本是一樣的,重新下載已經在快取裡的內容也是一種浪費。HTTP 提供條件請求機制,客戶端能以有效方式詢問伺服器:『如果內容變了,請返回內容本身;否則,直接告訴我內容沒變。』當資源不經常變化時,使用條件請求可以顯著節省頻寬和效能;但是,保證資源的最新版迅速可用也是非常重要的。使用條件快取可以通過以下方法。

  1. 在請求中包含 HTTP 首部 Last-Modified-Since。僅當最新內容在首部中指定的日期之後被更新過,伺服器才返回完整內容;否則只返回 304 響應碼,並在響應首部中附帶上新的時間戳 Date 欄位。
  2. 在請求體中包含實體校驗碼,或者叫 ETag;它唯一標識所請求的資源。ETag 由伺服器 提供,內嵌於資源的響應首部中。伺服器會比較當前 ETag 與請求首部中收到的 ETag,如果一致,就只返回 304 響應碼;否則返回完整內容。

一般來說,大多數 Web 伺服器會對圖片和 CSS/JS 使用這些技術,但你也可以將其用到其他資源。

7. 壓縮和程式碼極簡化

所有的文字內容(HTML、JS、CSS、SVG、XML、JSON、字型等),都可以壓縮和極簡化。這兩種方法組合起來,可以顯著減少資源大小。更少位元組數對應著更少的請求與應答,也就意味著更短的請求時間。

極簡化(minification, 混淆)是指從文字資源中剝離所有非核心內容的過程。通常,要考慮方便人類閱讀和維護,而瀏覽器並不關心可讀性,放棄程式碼可讀性反而能節省空間。在極簡化的基礎上,壓縮可以進一步減少位元組數。它通過可無損還原的演算法減少資源大小。在傳送資源之前,如果伺服器進行壓縮處理,可以節省 90% 的大小。

8. 避免阻塞 CSS/JS

在螢幕上繪製第一個畫素之前,瀏覽器必須確保 CSS 已經下載完整。儘管瀏覽器的前處理器很智慧,會盡早請求整個頁面所需要的 CSS,但是把 CSS 資源請求放在頁面靠前仍然是種最佳實踐,具體位置是在文件的 head 標籤裡,而且要在任何 JS 或圖片被請求和處理之前。

預設情況下,如果在 HTML 中定位了 JS,它就會被請求、解析,然後執行。在瀏覽器處理完這個 JS 之前,會阻止其後任何資源的下載渲染。然而大多數時候,這種預設的阻塞行為導致了不必要的延遲,甚至會造成單點故障。為了減輕 JS 阻塞帶來的潛在影響,下面針對己方資源(你能控制的)和第三方資源(你不能控制的)推薦了不同的策略

  1. 定期校驗這些資源的使用情況。隨著時間的變遷,Web 頁面可能會持續下載一些不再需要的 JS,這時最好去掉它。
  2. 如果 JS 執行順序無關緊要,並且必須在 onload 事件觸發之前執行,那麼可以設定 async 屬性,像這樣:
    <script async src="/js/myfile.js">
    複製程式碼
    只需做到下載 JS 與解析 HTML 並行,就能極大地提升整體使用者體驗。慎用 document.write 指令,因為很可能中斷頁面執行,所以需要仔細測試。
  3. 如果 JS 執行順序很重要,並且你也能承受指令碼在 DOM 載入完之後執行,那麼請使用 defer 屬性。像這樣
    <script defer src="/js/myjs.js">
    複製程式碼
  4. 對不會影響到頁面初次展示的 JS 指令碼,必須在 onload 事件觸發之後請求(處理)它。
  5. 如果你不想延遲主頁面的 onload 事件,可以考慮通過 iframe 獲取 JS,因為它的處理獨立於主頁面。但是,通過 iframe 下載的 JS 訪問不了主頁面上的元素。
9. 圖片優化

對大多數網站而言,圖片的重要性和比重在不斷增加。既然圖片主導了多數現代網站,優化它們就能夠獲得最大的效能回報。所有圖片優化手段的目標都是在達到指定視覺質量的前提下傳輸最少的位元組。

  1. 圖片元資訊,例如題材地理位置資訊、時間戳、尺寸和畫素資訊,通常包含在二進位制資料裡,應該在傳送給客戶端之前去掉(務必保留版權和色彩描述資訊)。這種無損處理能夠在圖片生成時完成。對於 PNG 圖片,一般會節省大概 10% 的空間。
  2. 圖片過載(image overloading)是指,圖片最終被瀏覽器自動縮小,要麼因為原始尺寸超過了瀏覽器可視區中的佔位大小,要麼因為畫素超過裝置的顯示能力。這不僅浪費頻寬,消耗的 CPU 資源也很可觀,這些計算資源有時在手持裝置上相當寶貴。想要解決圖片過載,可以使用技術手段,針對使用者的裝置、網路狀況和預期的視覺質量,提供裁剪過的圖片(就尺寸和質量而言)。

3.2.2 反模式

HTTP/2 對每個域名只會開啟一個連線,所以 HTTP/1.1 下的一些訣竅對它來說只會適得其反。

詳細看 6.7 節

3.3 小結

HTTP/1.1 孕育了各種效能優化手段與訣竅,可以幫助我們深入理解 Web 及其內部實現。HTTP/2 的目標之一就是淘汰掉眾多(並不是全部)此類訣竅。

4. HTTP/2 遷移

在升級到 HTTP/2 之前,你應該考慮:

  1. 瀏覽器對 h2 的支援情況
  2. 遷移到 TLS(HTTPS)的可能性
  3. 對你的網站做基於 h2 的優化(可能對 h1 有反作用)
  4. 網站上的第三方資源
  5. 保持對低版本客戶端的相容

4.1 瀏覽器的支援情況

任何不支援 h2 的客戶端都將簡單地退回到 h1,並仍然可以訪問你的站點基礎設施。

《HTTP/2 基礎教程》 閱讀摘要

4.2 遷移到 TLS

所有主流瀏覽器只能訪問基於 TLS(即 HTTPS 請求)的 h2。

4.3 撤銷針對 HTTP/1.1 的優化

Web 開發者之前花費了大量心血來充分使用 h1,並且已經總結了一些訣竅,例如資源合併、域名拆分、極簡化、禁用 cookie 的域名、生成精靈圖,等等。所以,當得知這些實踐中有些在 h2 下變成反模式時,你可能會感到吃驚。例如,資源合併(把很多 CSS/JS 檔案拼合成一個)能避免瀏覽器發出多個請求。對 h1 而言這很重要,因為發起請求的代價很高;但是在 h2 的世界裡,這部分已經做了深度優化。放棄資源合併的結果可能是,針對單個資源發起請求的代價很低,但瀏覽器端可以進行更細粒度的快取。

詳細看 6.7 節

5. HTTP/2 協議

本章將全面探討 HTTP/2 的底層工作原理,深入到資料層傳輸的幀及其通訊方式。

5.1 HTTP/2 分層

HTTP/2 大致可以分為兩部分

  1. 分幀層 即 h2 多路複用能力的核心部分,主要目的還是傳輸 HTTP
  2. 資料或 http 層 其中包含傳統上被認為是 HTTP 及其關聯資料的部分,向後相容 HTTP/1.1

h2 有些特點需要關注一下:

  1. 二進位制協議 :h2 的分幀層是基於幀的二進位制協議,這方便了機器解析,但是肉眼識別比較困難
  2. 首部壓縮 :僅使用二進位制協議還不夠,h2 的首部還會被深度壓縮。這將顯著減少傳輸中的冗餘位元組
  3. 多路複用 :在除錯工具裡檢視基於 h2 傳輸的連線的時候,你會發現請求和響應交織在一起
  4. 加密傳輸 :線上傳輸的絕大部分資料是加密過的,所以在中途讀取會更加困難

5.2 連線

與完全無狀態的 h1 不同的是,h2 把它所承載的幀(frame)和流(stream)共同依賴的連線層元素捆綁在一起,其中既包含連線層設定也包含首部表。也就是說,與之前的 HTTP 版本不同,每個 h2 連線都有一定的開銷。之所以這麼設計,是考慮到收益遠遠超過其開銷。

在連線的時候,HTTP/2 提供兩種協議發現的機制:

  1. 在連線不加密的情況下,客戶端會利用 Upgrade 首部來表明期望使用 h2。如果伺服器也可以支援 h2,它會返回一個101 Switching Protocols(協議轉換)響應。這增加了一輪完整的請求-響應通訊
  2. 如果連線基於 TLS,情況就不同了。客戶端在 ClientHello 訊息中設定 ALPN (Application-Layer Protocol Negotiation,應用層協議協商)擴充套件來表明期望使用 h2 協議,伺服器用同樣的方式回覆。如果使用這種方式,那麼 h2 在建立 TLS 握手的過程中完成協商,不需要多餘的網路通訊。

在協議制定過程中,很早就把小數點去掉了,這表明未來的 HTTP 版本不能保證語義的向後相容,也就是說只有 HTTP/2 沒有 HTTP/2.0、HTTP/2.2

5.3 幀

HTTP/2 是基於幀(frame)的協議,採用分幀是為了將重要資訊都封裝起來,讓協議的解析方可以輕鬆閱讀、解析並還原資訊。 相比之下,h1 不是基於幀的,而是以文字分隔。所以解析 h1 的請求或響應可能會出現以下問題:

  1. 一次只能處理一個請求或響應,完成之前不能停止解析
  2. 無法預判解析需要多少記憶體。這會帶來一系列問題:你要把一行讀到多大的緩衝區裡;如果行太長會發生什麼;應該增加並重新分配記憶體,還是返回 400 錯誤

從另一方面來說,有了幀,處理協議的程式就能預先知道會收到什麼。下圖是一個 HTTP/2 幀的結構

HTTP/2 幀結構

前 9 個位元組對於每個幀是一致的。解析時只需要讀取這些位元組,就可以準確地知道在整個幀中期望的位元組數。其中每個欄位的說明如下

名稱 長度 描述
Length 3 位元組 表示幀負載的長度(取值範圍為 2^14~2^24-1 位元組)。 請注意,214 位元組是預設的最大幀大小,如果需要更大的幀,必須在 SETTINGS 幀中設定
Type 1 位元組 當前幀型別
Flags 1 位元組 具體幀型別的標識
R 1 位 保留位,不要設定,否則可能帶來嚴重後果
Stream Identifier 31 位 每個流的唯一 ID
Frame Payload 長度可變 真實的幀內容,長度是在 Length 欄位中設定的

相比依靠分隔符的 h1,h2 還有另一大優勢:如果使用 h1 的話,你需要傳送完上一個請求或者響應,才能傳送下一個;由於 h2 是分幀的,請求和響應可以交錯甚至多路複用。多路複用有助於解決類似隊頭阻塞的問題。

h2 有十種不同的幀型別:

名稱 ID (Type) 描述
DATA 0x0 資料幀,傳輸流的核心內容
HEADERS 0x1 報頭幀,包含 HTTP 首部,和可選的優先順序引數
PRIORITY 0x2 優先順序幀,指示或者更改流的優先順序和依賴
RST_STREAM 0x3 流終止幀,允許一端停止流(通常由於錯誤導致的)
SETTINGS 0x4 設定幀,協商連線級引數
PUSH_PROMISE 0x5 推送幀,提示客戶端,伺服器要推送些東西
PING 0x6 PING 幀,測試連線可用性和往返時延(RTT)
GOAWAY 0x7 GOAWAY 幀,告訴另一端,當前端已結束
WINDOW_UPDATE 0x8 視窗更新幀,協商一端將要接收多少位元組(用於流量控制)
CONTINUATION 0x9 延續幀,用以擴充套件 HEADER 資料塊

擴充套件幀 :HTTP/2 內建了名為擴充套件幀的處理新的幀型別的能力。依靠這種機制,客戶端和伺服器的實現者可以實驗新的幀型別,而無需制定新協議。按照規範,任何客戶端不能識別的幀都會被丟棄,所以網路上新出現的幀就不會影響核心協議。當然,如果你的應用程式依賴於新的幀,而中間代理會丟棄它,那麼可能會出現問題。

5.4 流

HTTP/2 規範中的流(stream):HTTP/2 連線上獨立的、雙向的幀序列交換。你可以將流看作在連線上的一系列幀,用來傳輸一對請求/響應訊息。如果客戶端想要發出請求,它會開啟一個新的流,伺服器也在這個流上回復。這與 h1 的請求、響應流程類似,區別在於,因為有分幀,所以多個請求和響應可以交錯,而不會互相阻塞。流 ID(幀首部的第 6~9 位元組)用來標識幀所屬的流。

客戶端到伺服器的 h2 連線建立之後,通過傳送 HEADERS 幀來啟動新的流,如果首部需要跨多個幀,可能還發會送 CONTINUATION 幀。

5.4.1 訊息

HTTP 訊息泛指 HTTP 請求或響應。一個訊息至少由 HEADERS 幀(用來初始化流)組成,並且可以另外包含 CONTINUATIONDATA 幀,以及其他的 HEADERS 幀。 下圖是普通 GET 請求的示例流程

普通 GET 請求的示例流程

POST 和 GET 的主要差別之一就是 POST 請求通常包含客戶端發出的大量資料。下圖是 POST 訊息對應的各幀可能的樣子

普通 POST 請求的示例流程

h1 的請求和響應都分成訊息首部和訊息體兩部分;與之類似,h2 的請求和響應分成 HEADERS 幀和 DATA 幀。HTTP/1 和 HTTP/2 訊息的下列差別是需要注意

  1. 一切都是header :h1 把訊息分成請求/狀態行、首部兩部分。h2 取消了這種區分,並把這些行變成了魔法偽首部
  2. 沒有分塊編碼(chunked encoding) :只有在無法預先知道資料長度的情況下向對方傳送資料時,才會用到分塊。在使用幀作為核心協議的 h2 裡,不再需要分塊
  3. 不再有101的響應 :Switching Protocol 響應是 h1 的邊緣應用。它如今最常見的應用可能就是用以升級到 WebSocket 連線。ALPN 提供了更明確的協議協商路徑,往返的開銷也更小

5.4.2 流量控制

h2 的新特性之一是基於流的流量控制。h1 中只要客戶端可以處理,服務端就會盡可能快地傳送資料,h2 提供了客戶端調整傳輸速度的能力,服務端也同樣可以調整傳輸的速度。

WINDOW_UPDATE 幀用於執行流量控制功能,可以作用在單獨某個流上(指定具體 Stream Identifier)也可以作用整個連線 (Stream Identifier 為 0x0),只有 DATA 幀受流量控制影響。初始化流量視窗後,傳送多少負載,流量視窗就減少多少,如果流量視窗不足就無法傳送,WINDOW_UPDATE 幀可以增加流量視窗大小。流建立的時候,視窗大小預設 65535(2^16-1)位元組。

5.4.3 優先順序

流的最後一個重要特性是依賴關係。

現代瀏覽器會盡量以最優的順序獲取資源,由此來優化頁面效能。在沒有多路複用的時候,在它可以發出對新物件的請求之前,需要等待前一個響應完成。有了 h2 多路複用的能力,客戶端就可以一次發出所有資源的請求,服務端也可以立即著手處理這些請求。由此帶來的問題是,瀏覽器失去了在 h1 時代預設的資源請求優先順序策略。假設伺服器同時接收到了 100 個請求,也沒有標識哪個更重要,那麼它將幾乎同時傳送每個資源,次要元素就會影響到關鍵元素的傳輸。

h2 通過流的依賴關係來解決上面這個問題。通過 HEADERS 幀和 PRIORITY 幀,客戶端可以明確地和服務端溝通它需要什麼,以及它需要這些資源的順序。這是通過宣告依賴關係樹和樹裡的相對權重實現的。

  1. 依賴關係 為客戶端提供了一種能力,通過指明某些物件對另一些物件有依賴,告知伺服器這些物件應該優先傳輸
  2. 權重 讓客戶端告訴伺服器如何確定具有共同依賴關係的物件的優先順序

流可以被標記為依賴其他流,所依賴的流完成後再處理當前流。每個依賴 (dependency) 後都跟著一個權重 (weight),這一數字是用來確定依賴於相同的流的可分配可用資源的相對比例。

其他時候也可以通過 PRIORITY 幀調整流優先順序。

設定優先順序的目的是為了讓端點表達它所期望對端在併發的多個流之間如何分配資源的行為。更重要的是,當傳送容量有限時,可以使用優先順序來選擇用於傳送幀的流。

5.5 服務端推送

升單個物件效能的最佳方式,就是在它被用到之前就放到瀏覽器的快取裡面。這正是 h2 服務端推送的目的。

5.5.1 推送物件

如果伺服器決定要推送一個物件(RFC 中稱為『推送響應』),會構造一個 PUSH_PROMISE 幀。這個幀有很多重要屬性:

  1. PUSH_PROMISE 幀首部中的流 ID (Promised Stream ID)用來響應相關聯的請求。推送的響應一定會對應到客戶端已傳送的某個請求。如果瀏覽器請求一個主體 HTML 頁面,如果要推送此頁面使用的某個 JavaScript 物件,伺服器將使用請求對應的流 ID 構造 PUSH_PROMISE 幀。
  2. PUSH_PROMISE 幀的首部塊與客戶端請求推送物件時傳送的首部塊是相似的。所以客戶端有辦法放心檢查將要傳送的請求。
  3. 被髮送的物件必須確保是可快取的。
  4. :method 首部的值必須確保安全。安全的方法就是冪等的那些方法,這是一種不改變任何狀態的好辦法。例如,GET 請求被認為是冪等的,因為它通常只是獲取物件,而 POST 請求被認為是非冪等的,因為它可能會改變伺服器端的狀態。
  5. 理想情況下,PUSH_PROMISE 幀應該更早傳送,應當早於客戶端接收到可能承載著推送物件的 DATA 幀。假設伺服器要在傳送 PUSH_PROMISE 之前傳送完整的 HTML,那客戶端可能在接收到 PUSH_PROMISE 之前已經發出了對這個資源的請求。h2 足夠健壯,可以優雅地解決這類問題,但還是會有些浪費。
  6. PUSH_PROMISE 幀會指示將要傳送的響應所使用的流 ID

如果客戶端對 PUSH_PROMISE 的任何元素不滿意,就可以按照拒收原因選擇重置這個流(使用 RST_STREAM),或者傳送 PROTOCOL_ERROR (在 GOAWAY 幀中)。常見的情況是快取中已經有了這個物件。

假設客戶端不拒收推送,服務端會繼續進行推送流程,用 PUSH_PROMISE 中指明 ID 對應的流來傳送物件

服務端推送訊息處理

5.5.2 選擇要推送的資源

如果伺服器接收到一個頁面的請求,它需要決定是推送頁面上的資源還是等客戶端來請求。決策的過程需要考慮到如下方面

  1. 資源已經在瀏覽器快取中的概率
  2. 從客戶端看來,這些資源的優先順序 (參見 5.4.3 節)
  3. 可用的頻寬,以及其他類似的會影響客戶端接收推送的資源

如果伺服器選擇正確,那就真的有助於提升頁面的整體效能,反之則會損耗頁面效能。

5.6 首部壓縮

現代網頁平均有很多請求,這些請求之間幾乎沒有新的的內容,這是極大的浪費。

首部列表 (Header List) 是零個或多個首部欄位 (Header Field) 的集合。當通過連線傳送時,首部列表通過壓縮演算法(即下文 HPACK) 序列化成首部塊 (Header Block),不用 GZIP 是因為它有洩漏加密資訊的風險。HPACK 是種表查詢壓縮方案,它利用霍夫曼編碼獲得接近 GZIP 的壓縮率。

然後,序列化的首部塊又被劃分成一個或多個叫做首部塊片段 (Header Block Fragment) 的位元組序列,並通過 HEADERSPUSH_PROMISE,或者 CONTINUATION 幀進行有效負載傳送。

假設客戶端按順序傳送如下請求首部:

Header1: foo
Header2: bar
Header3: bat
複製程式碼

當客戶端傳送請求時,可以在首部資料塊中指示特定首部及其應該被索引的值。它會建立一張表:

索引 首部名稱
62 Header1 foo
63 Header2 bar
64 Header3 bat

如果服務端讀到了這些請求首部,它會照樣建立一張表。客戶端傳送下一個請求的時候, 如果首部相同,它可以直接傳送:62 63 64 ,伺服器會查詢先前的表格,並把這些數字還原成索引對應的完整首部。首部壓縮機制中每個連線都維護了自己的狀態。

HPACK 的實現比這個要複雜得多,比如:

  1. 請求端和響應端各維護了兩張表格。其中之一是動態表,建立方法和上面差不 多。另一張是靜態表,它由 61 個最常見的首部的鍵值組合而成。例如 :method: GET 在靜態表中索引為 2。按規定,靜態表包含 61 個條目,所以上例索引編號從 62 開始。
  2. 關於欄位如何索引,有很多控制規則:
    1. 傳送索引編號和文字值僅傳送文字值,不對它們進行索引(對於一次性或敏感首部)
    2. 傳送索引的首部名,值用文字表示,但不進行索引處理(如:path: /foo.html,其值每次都不同)
    3. 傳送索引過的首部名和值(如上例中的第二個請求)
  3. 使用打包方案的整數壓縮,以實現極高的空間效率
  4. 利用霍夫曼編碼表進一步壓縮字串

5.7 線上傳輸

線上傳輸的 h2 資訊是經過壓縮的二進位制資料。

一個簡單的GET請求

下面是一個簡單的 h2 的 get 請求

:authority: www.akamai.com
:method: GET
:path: /
:scheme: https
accept: text/html,application/xhtml+xml,...
accept-language: en-US,en;q=0.8
cookie: sidebar_collapsed=0; _mkto_trk=...
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh;...
複製程式碼

下面是 h2 的一個響應

:status: 200
cache-control: max-age=600
content-encoding: gzip
content-type: text/html;charset=UTF-8
date: Tue, 31 May 2016 23:38:47 GMT
etag: "08c024491eb772547850bf157abb6c430-gzip"
expires: Tue, 31 May 2016 23:48:47 GMT
link: <https://c.go-mpulse.net>;rel=preconnect
set-cookie: ak_bmsc=8DEA673F92AC...
vary: Accept-Encoding, User-Agent
x-akamai-transformed: 9c 237807 0 pmb=mRUM,1
x-frame-options: SAMEORIGIN
複製程式碼

在這個響應中,伺服器表示請求已成功受理(狀態碼 200),設定了 cookie(cookie 首部),表示返回的內容使用 gzip 壓縮(content-encoding 首部)

6. HTTP/2效能

HTTP/2 大部分情況下傳輸 web 頁面比 HTTP/1.1 快。

  1. 對於包含很多小型資源的頁面,h2 載入頁面的時間比 h1 更短。這是因為 h1 下(有 6 個 TCP 連線)伺服器只能並行傳送 6 個資源(由於隊頭阻塞),而 h2 下多個流(stream)可以共用連線。進一步說,隨著網路條件變差,h1 和 h2 下頁面載入時間(PLT)都會增加;但是 h2 由於單連線,如果唯一的連線發生了丟包,所有工作都會受影響。
  2. 對於包含少量大型資源的頁面,在所有網路條件下,h1 效能上都比 h2 表現要好。這個多少令人感到意外的結果是初始擁塞視窗導致的。如果開啟 6 個連線,h1 的初始擁塞視窗大小實際上是 h2 的 6 倍。因此在會話開始階段,h2 的連線視窗尚未增長到最佳值,但 h1 早就能更快地傳輸更多資料了。這個問題目前仍在解決,因為它導致初始擁塞視窗對 h2 而言太小,然而對 h1 而言又太大。 此外,h2 比 h1 更容易受丟包的影響。
  3. 對於包含一些極大型資源的 Web 頁面,兩者沒有任何差異。h2 的初始擁塞視窗劣勢被整體下載時長掩蓋了,多路複用此時也不再具有優勢。

6.1 客戶端實現

網路條件相同,使用不同瀏覽器客戶端,同樣的網站頁面載入效能可能差別很大。

  1. 協議的具體實現很重要
  2. 並非所有請求在任何情況下都會從 HTTP/2 受益,即便如此,URL 使用 h2 後效能提升的比例也依舊高於下降的比例

6.2 延遲

延遲是指資料包從一個端點到另一個端點所花的時間。有時,它也表示資料包到達接收方然後返回傳送方所需的時間,又稱為往返時延(RTT),長度一般以毫秒計。

影響延遲的因素眾多,但有兩個是最重要的:端點間的距離,以及所用傳輸介質

兩點之間的網線不會是筆直的,另外各種閘道器、路由器、交換機以及移動基站等(也包括伺服器應用本身)都會增加延遲

6.3 丟包

如果網路中傳輸的資料包沒有成功到達目的地,就會發生丟包,這通常是由網路擁堵造成的。

頻繁丟包會影響 h2 的頁面傳輸,主要是因為 h2 開啟單一 TCP 連線,每次有丟包/擁堵時,TCP 協議就會縮減 TCP 視窗。

如果 TCP 流上丟了一個資料包,那麼整個 h2 連線都會停頓下來,直到該資料包重發並被接收到。

6.4 服務端推送

服務端推送讓伺服器具備了在客戶端請求之前就推送資源的能力。 測試表明,如果合理使用推送,頁面渲染時間可以減少 20%~50%。

然而,推送也會浪費頻寬,這是因為服務端可能試圖推送那些在客戶端已經快取的資源,導致客戶端收到並不需要的資料。客戶端確實可以傳送 RST_STREAM 幀來拒絕伺服器的 PUSH_PROMISE 幀,但是 RST_STREAM 並不會即刻到達,所以伺服器還是會傳送一些多餘的資訊。

如果使用者第一次訪問頁面時,就能向客戶端推送頁面渲染所需的關鍵 CSS 和 JS 資源,那麼服務端推送的真正價值就實現了。不過,這要求伺服器端實現足夠智慧,以避免『推送承諾』(push promise)與主體 HTML 頁面傳輸競爭頻寬。

理想情況下,服務端正在處理 HTML 頁面主體請求時才會發起推送。有時候,服務端需要做一些後臺工作來生成 HTML 頁面。這時候服務端在忙,客戶端卻在等待,這正是開始向客戶端推送所需資源的絕佳時機。

在後臺處理的同時進行推送

6.5 首位元組時間

首位元組時間(TTFB)用於測量伺服器的響應能力。是從客戶端發起 HTTP 請求到客戶端瀏覽器收到資源的第一個位元組所經歷的時間。由 socket 連線時間、傳送 HTTP 請求所需時間、收到頁面第一個位元組所需時間組成。

h1 中,客戶端針對單個域名在每個連線上依次請求資源,而且伺服器會按序傳送這些資源。客戶端只有接收了之前請求的資源,才會再請求剩下的資源,伺服器接著繼續響應新的資源請求。這個過程會一直重複,直到客戶端接收完渲染頁面所需的全部資源。

與 h1 不同,通過 h2 的多路複用,客戶端一旦載入了 HTML,就會向伺服器並行傳送大量請求。相比 h1,這些請求獲得響應的時間之和一般會更短;但是因為是請求是同時發出的,而單個請求的計時起點更早,所以 h2 統計到的 TTFB 值會更高。

HTTP/2 比 h1 確實做了更多的工作,其目的就是為了從總體上提升效能。下面是一些 h1 沒有,但 h2 實現了的

  1. 視窗大小調節
  2. 依賴樹構建
  3. 維持首部資訊的靜態 / 動態表
  4. 壓縮 / 解壓縮首部
  5. 優先順序調整(h2 允許客戶端多次調整單一請求的優先順序)
  6. 預先推送客戶端尚未請求的資料流

下圖是使用 h1 和 h2 載入同一個頁面的載入時序對比,總體來說 h2 體驗更好

h1 與 h2 請求的時間序列

6.6 第三方資源

許多網站會使用各種統計、跟蹤、社交以及廣告平臺,就會引入各種第三方的資源。

  1. 第三方請求往往通過不同域名傳送;由於瀏覽器需要解析 DNS、建立 TCP 連線、協商 TLS,這將嚴重影響效能;
  2. 因為第三方資源在不同域名下,所以請求不能從服務端推送、資源依賴、請求優先順序等 h2 特性中受益。這些特性僅是為請求相同域名下的資源設計的;
  3. 你無法控制第三方資源的效能,也無法決定它們是否會通過 h2 傳輸;

6.7 HTTP/2反模式

h1 下的一些效能調優辦法在 h2 下會起到反作用。下面列出了一些用於優化 h1 請求的常用技巧,並標註了 h2 方面的考慮。

名稱 描述 備註
資源合併 把多個檔案(JavaScript、CSS) 合成一個檔案,以減少 HTTP 請求 在 HTTP/2 下這並非必要,因為請求的傳輸位元組數和時間成本更低,雖然這種成本仍然存在
極簡化 去除 HTML、JavaScript、CSS 這類檔案中無用的程式碼 很棒的做法,在 HTTP/2 下也要保留
域名拆分 把資源分佈到不同的域名上面去,讓瀏覽器利用更多的 socket 連線 HTTP/2 的設計意圖是充分利用單個 socket 連線,而拆分域名會違背這種意圖。建議取消域名拆分,但請注意本表格之後的附註框會介紹這個問題相關的各種複雜情況
禁用 cookie 的域名 為圖片之類的資源建立單獨的域名,這些域名不用 cookie,以儘可能減少請求尺寸 應該避免為這些資源單獨設立域名(參見域名拆分),但更重要的是,由於 HTTP/2 提供了首部壓縮,cookie 的開銷會顯著降低
生成精靈圖 把多張圖片拼合為一個檔案,使用 CSS 控制在 Web 頁面上展示的部分 與極簡化類似,只不過用 CSS 實現這種效果的代價高昂;不推薦在 HTTP/2 中使用

6.7.1 生成精靈圖和資源合併/內聯

精靈圖(spriting)是指把很多小圖片拼合成一張大圖,這樣只需發起一個請求就可以覆蓋多個圖片元素。在 HTTP/2 中,針對特定資源的請求不再是阻塞式的,很多請求可以並行處理;就效能而言,生成精靈圖已失去意義,因為多路複用和首部壓縮去掉了大量的請求開銷。

與之類似,小的文字資源,例如 JS 和 CSS,會依照慣例合併成一份更大的資源,或者直接內嵌在主體 HTML 中,這也是為了減少客戶端-伺服器連線數。這種做法有個問題是,那些小的 CSS 或 JS 自身也許可快取,但如果它們內嵌在不可快取的 HTML 中的話,當然也就不可快取了。把很多小的 JS 指令碼合併成一個大檔案可能仍舊對 h2 有意義,因為這樣可以更好地壓縮處理並節省 CPU。

6.7.2 域名拆分

域名拆分(sharding)是為了利用瀏覽器針對每個域名開啟多個連線的能力來並行下載資源。對於包含大量小型資源的網站,普遍的做法是拆分域名,以利用現代瀏覽器針能對每個域名開啟 6 個連線的特性,充分利用可用頻寬。

因為 HTTP/2 採取多路複用,所以域名拆分就不是必要的了,並且反而會讓協議力圖實現的目標落空。比較好的辦法就是繼續保持當前的域名拆分,但是確保這些域名共享同一張證照 [ 萬用字元 / 儲存區域網路(SAN)],並保持伺服器 IP 地址和埠相同,以便從瀏覽器網路歸併(network coalescence)中收益,這樣可以節省為單個域名連線建立的時間。

6.7.3 禁用cookie的域名

在 HTTP/1 下,請求和響應首部從不會被壓縮。隨著時間推移,首部大小已經增長了,超過單個 TCP 資料包的 cookie 可以說司空見慣。因此,在內容源和客戶端之間來回傳輸首部資訊的開銷可能造成明顯的延遲。

因此,對圖片之類不依賴於 cookie 的資源,設定禁用 cookie 的域名是個合理的建議。

但是 HTTP/2 中,首部是被壓縮的,並且客戶端和伺服器都會保留『首部歷史』,避免重複傳輸已知資訊。所以,如果你要重構站點,大可不必考慮禁用 cookie 的域名,這樣能減少很多包袱。

靜態資源也應該從同一域名提供;使用與主頁面 HTTP 相同的域名,消除了額外的 DNS 查詢以及(潛在的)socket 連線,它們都會減慢靜態資源的獲取。把阻塞渲染的資源放在同樣的域名下,也可以提升效能。

6.7.4 資源預取

資源預取也是一項 Web 效能優化手段,它提示瀏覽器只要有可能就繼續下載可快取資源,並把這些資源快取起來。儘管如此,如果瀏覽器很忙,或者資源下載花的時間太 長,預取請求將會被忽略。資源預取可以在 HTML 中插入 link 標籤實現:

<link rel="prefetch" href="/important.css">
複製程式碼

也可以使用 HTTP 響應中的 Link 首部: Link: </important.css>; rel=prefetch

資源預取與 h2 引入的服務端推送並沒多少關聯。服務端推送用於讓資源更快到達瀏覽器, 而資源預取相比推送的優點之一是,如果資源已經在快取裡,瀏覽器就不會浪費時間和頻寬重複請求它。所以,可以把它看作 h2 推送的補充工具,而不是將被替代的特性。

6.8 現實情況中的效能

網路丟包是 h2 的命門,一次丟包機會就會讓它的所有優化泡湯。

7. HTTP/2 實現

7.1 桌面Web瀏覽器

所有瀏覽器在進行 HTTP/2 傳輸時都需要使用 TLS(HTTPS),即使事實上HTTP/2 規範本身並沒有強制要求 TLS。這個原因是:

  1. 從之前對 WebSocket 和 SPDY 的實驗看來,使用 Upgrade 首部,通過 80 埠(明文的 HTTP 埠)通訊時,通訊鏈路上代理伺服器的中斷等因素會導致非常高的錯誤率。如果基於 443 埠(HTTPS 埠)上的 TLS 發起請求,錯誤率會顯著降低,並且協議通訊也更簡潔。
  2. 人們越來越相信,考慮到安全和隱私,一切都應該被加密。HTTP/2 被視為一次推動全網加密通訊發展的機會。

7.1.2 禁用HTTP/2

HTTP/2 畢竟是新鮮事物,現在很多瀏覽器都支援啟用或禁用 h2。

7.1.3 支援 HTTP/2 服務端推送

服務端推送是 h2 中最令人興奮也最難正確使用的特性之一,現在所有的主流瀏覽器都已經支援了此特性。

7.1.4 連線歸併

如果需要建立一個新連線,而瀏覽器支援連線歸併,那麼通過複用之前已經存在的連線,就能夠提升請求效能。這意味著可以跳過 TCP 和 TLS 的握手過程,改善首次請求新域名的效能。如果瀏覽器支援連線歸併,它會在開啟新連線之前先檢查是否已經建立了到相同目的地的連線。

相同目的地具體指的是:已經存在連線,其證照對新域名有效,此域名可以被解析成那個連線對應的 IP 地址。如果上述條件都滿足,那麼瀏覽器會在已建立的連線上向該域名發起 HTTP/2 請求。

7.2 伺服器、代理以及快取

如果要通過 h2 傳輸內容,我們有幾個選擇。支援 HTTP/2 的網路設施大致有以下兩類。

Web伺服器 :通常所說的提供靜態和動態內容服務的程式。

代理/快取 :一般處在伺服器和終端使用者之間,可以提供快取以減輕伺服器負載,或進行額外加工。許多代理也能扮演 Web 伺服器的角色。

在選擇 HTTP/2 伺服器時,我們需要檢查、評估一些關鍵點。除了基本的通用效能、作業系統支援、學習曲線、可擴充套件性以及穩定性,還應當關注 Web 請求的依賴項優先順序,以及對服務端推送的支援。

7.3 內容分發網路 CDN

內容分發網路(CDN)是反向代理伺服器的全球性分散式網路,它部署在多個資料中心。CDN 的目標是通過縮短與終端使用者的距離來減少請求往返次數,以此為終端使用者提供高可用、高效能的內容服務。

大多數主流 CDN 是支援 HTTP/2 的,選擇 CDN 時主要考慮的兩點是:對服務端推送的支援,以及它們處理優先順序的方式。這兩點對現實世界中的 Web 效能影響重大。

8. HTTP/2除錯

8.1 chrome devtools 視覺化

Chrome 開發者工具中的 Network 欄,有助於簡單直觀地跟蹤客戶端和服務端的通訊,它 按下面表格的形式展示了若干資訊:

  1. 資源名
  2. 資源大小
  3. 狀態碼
  4. 優先順序
  5. 總載入時間
  6. 使用時間線方式分解載入時間

開啟 devtools 的 Network 欄,滑鼠放在瀑布流 Waterfall 的資源上,就會看到資源載入過程中各個階段的詳細時間

《HTTP/2 基礎教程》 閱讀摘要

  1. Connection Setup (連線設定)
    1. Queueing :請求被渲染引擎或者網路層延遲的時間,瀏覽器在以下情況下對請求排隊
      • 存在更高優先順序的請求。
      • 此源已開啟六個 TCP 連線,達到限值。 僅適用於 HTTP/1.0 和 HTTP/1.1
      • 瀏覽器正在短暫分配磁碟快取中的空間
  2. Connection Start (開始連線階段)
    1. Stalled :請求可能會因 Queueing 中描述的任何原因而停止
    2. Proxy negotiation :瀏覽器與代理伺服器協商請求花費的時間
    3. DNS Lookup :瀏覽器解析請求的 IP 地址花費的時間
  3. Request/Response (請求 / 響應)
    1. Request Sent :傳送請求包含的資料花費的時間
    2. Waiting (TTFB) :等待初始響應花費的時間,也就是所說的首位元組時間;這個數字包括等待伺服器傳輸響應的時間,以及往返伺服器的延遲
    3. Content Download :接收響應的資料所花費的時間
  4. Explanation (總時間)
  5. 其他
    1. ServiceWorker Preparation :瀏覽器正在啟動 Service Worker
    2. Request to ServiceWorker :正在將請求傳送到 Service Worker
    3. Receiving Push :瀏覽器正在通過 HTTP/2 伺服器推送接收此響應的資料
    4. Reading Push :瀏覽器正在讀取之前收到的本地資料

9. 展望未來

HTTP/2 的弱點之一就是依賴主流 TCP 實現。在 3.1.3 節中已經討論過,TCP 連線受制於 TCP 慢啟動、擁塞規避,以及不合理的丟包處理機制。用單個連結承載頁面涉及的所有資源請求,就能享受多路複用帶來的好處;然而面對 TCP 層級的隊首阻塞時,我們還是束手無策。所以 Google 開發的 QUIC 採納了 HTTP/2 的優點,並且避免了這些缺點。


推介閱讀:

  1. HTTP2 詳解 | Wangriyu’s Blog

PS:歡迎大家關注我的公眾號【前端下午茶】,一起加油吧~

《HTTP/2 基礎教程》 閱讀摘要

相關文章