再過五分鐘,你就懂 HTTP 2.0 了!

程式設計師cxuan發表於2021-09-01

Hey guys ,各位小夥伴們大家好,這裡是程式設計師 cxuan,歡迎你收看我最新一期的文章。

這篇文章我們來聊一聊 HTTP 2.0,以及 HTTP 2.0 它在 HTTP 1.1 的基礎上做了哪些改變,以及 HTTP 2.0 都有哪些特徵,那麼廢話不多說,下面開始本篇文章。

哦對了,如果你沒有看過筆者的 HTTP 1.1 系列的相關文章,建議你先閱讀筆者的下面幾篇文章,非常 nice,看完保準你有收穫。

看完這篇HTTP,跟面試官扯皮就沒問題了

看完這篇 HTTPS,和麵試官扯皮就沒問題了

最初的 HTTP

HTTP 剛剛誕生之初只用於 web 端的內容獲取,一般就是用於頁面訪問,那個時候的頁面內容還不如現在這樣豐富,互動場景也不是很多,也沒有龐大繁雜的 CSS、JS ,頁面載入速度非常快。但是隨著 web 2.0 的出現以及更多的內容被展示、更精美的排版、更多的使用者互動場景一起出現,導致頁面的內容越來越大,使得頁面載入速度越來越慢。

HTTP 的瓶頸

影響一個 HTTP 網路請求的因素主要有兩個:頻寬和延遲

首先來說一下頻寬,如果我們還停留在撥號上網階段的話,頻寬很容易出現瓶頸,因為單位時間內傳輸的資料量很小。但是現在隨著光纖等通訊技術的不斷髮展,10Mbps、100Mbps、甚至 1000 Mbps 進入了每個家庭,我們不用再擔心頻寬成為網路請求的瓶頸了。

那麼剩下的就只剩下延遲了。

延遲主要有下面三個方面:

  • 瀏覽器阻塞(HOL blocking):瀏覽器會因為一些原因阻塞請求。瀏覽器對於同一個域名,同時只能有 4 個連線(這個根據瀏覽器核心不同可能會有所差異),超過瀏覽器最大連線數限制,後續請求就會被阻塞。
  • DNS 查詢(DNS Lookup):瀏覽器需要知道目標伺服器的 IP 才能建立連線。將域名解析為 IP 的這個系統就是 DNS。這個通常可以利用 DNS 快取結果來達到減少這個時間的目的。
  • 建立連線(Initial connection):HTTP 是基於 TCP 協議的,瀏覽器最快也要在第三次握手時才能捎帶 HTTP 請求報文,達到真正的建立連線,但是這些連線無法複用會導致每次請求都經歷三次握手和慢啟動。三次握手在高延遲的場景下影響較明顯,慢啟動則對檔案類大請求影響較大。

HTTP 1.0 有一個被抱怨最多的是連線無法複用,當每次有新的請求時都會重新經歷一次三次握手和四次揮手過程,並且連線的建立和釋放需要耗費大量的伺服器資源,在請求少的頁面還尚能應對,不過隨著請求的不斷增多,HTTP 1.0 越來越難頂。

不過,HTTP 1.0 議頭裡可以設定 Connection:Keep-Alive。在 header 裡設定 Keep-Alive 可以在一定時間內複用連線,具體複用時間的長短可以由伺服器控制,一般在 15s 左右。到 HTTP 1.1 之後 Connection 的預設值就是Keep-Alive,如果要關閉連線複用需要顯式的設定 Connection:Close。

HTTP 還有一個被抱怨最多的問題就是它的隊頭阻塞(head of blocking),隊頭阻塞問題會導致頻寬無法充分利用,導致後續的請求被阻塞。

假如有五個請求被同時發出,如果第一個請求沒有處理完成,就會導致後續的請求也無法得到處理,如下圖所示

image-20210828102427830

如果第一個請求沒有被處理,那麼 2 3 4 5 這四個請求會直接阻塞在客戶端,等到請求 1 被處理完畢後,才能逐個發出。網路通暢的時候效能影響不大,不過一旦請求 1 因為某些原因沒有抵達伺服器,或者請求因為網路阻塞沒有及時返回,影響的就是所有後續請求,導致後續請求無限阻塞下去,問題就變得比較嚴重了。

不過在 HTTP 1.1 中,也提出了流水線(pipelining)的設計,pipelining 就被用來解決隊頭阻塞的問題,如下圖所示

image-20210828151722164

雖然這種流水線的設計乍一看像是能夠解決阻塞問題,因為右圖中這三個請求沒有等到響應到達後再進行傳送,而是直接依次傳送,但是實際上,並不是那麼回事。

pipelining 並不是救世主,它也存在不少缺陷:

  • 因為只有冪等的請求比如 GET、HEAD 才能使用 pipelining ,非冪等請求比如 POST 則不能使用,因為請求之間可能存在先後依賴關係。
  • 其實隊頭阻塞問題並沒有完全解決,因為伺服器返回的響應還是要依次返回,也就是返回的請求時 FIFO - 先發先回。
  • 絕大多數 HTTP 代理伺服器不支援 pipelining。
  • 和不支援 pipelining 的老伺服器協商有問題。

正是因為有這麼多的問題,各大瀏覽器廠商要麼是根本就不支援 pipelining,要麼就是預設關掉了 pipelining 機制,而且啟用的條件十分苛刻。

SPDY

雖然 HTTP1.0 和 HTTP 1.1 存在這麼多問題,業界也是想出了各種優化手段,但是這些手段怎麼說呢,都是治標不治本,直到 2020 年 Google 提出了 SPDY 的方案,大家才開始從正面看待和解決老版本 HTTP 協議本身的問題,這也直接加速了 HTTP 2.0 的誕生。

我們先來聊一下 SPDY 是什麼,它都有哪些特點?

認識 SPDY

SPDY 的目標在於解決 HTTP 的缺陷,即延遲和安全性。我們上面一直在討論延遲,至於安全性,雖然我們上面沒有具體聊,不過 HTTP 的明文傳輸確是個問題。如果以降低延遲為目標,應用層的 HTTP 和傳輸層的 TCP 都是都有調整的空間,不過 TCP 作為更底層協議存在已達數十年之久,其實現已深植全球的網路基礎設施當中,如果要動必然傷經動骨,業界響應度必然不高,所以 SPDY 的手術刀對準的是 HTTP 。

  • 降低延遲,客戶端的單連線單請求,服務端的 FIFO 響應佇列都是延遲的大頭。
  • HTTP 最初設計都是客戶端發起請求,然後服務端進行響應,服務端無法主動傳送內容到客戶端。
  • 壓縮 HTTP header,HTTP 1.x 的 header 越來越膨脹,cookie 和 user agent 很容易讓 header 的 size 增至1kb 大小甚至更多。而且由於 HTTP 的無狀態特性,header 必須每次請求都重複攜帶,很浪費流量。

為了增加解決這些問題的可行性,聰明的 Google 一開始就避開了從傳輸層動手,而且打算利用開源社群的力量以提高擴散的力度,對於協議使用者來說,也只需要在請求的 header 裡設定 user agent,然後在服務端做好支援即可,極大的降低了部署的難度。SPDY 的設計如下

image-20210828220948867

可以看到,SPDY 位於 HTTP 之下,SSL 之上,這樣可以輕鬆的相容老版本的 HTTP 協議,SPDY 的功能分為基礎功能和高階功能兩部分,基礎功能是預設啟用的,高階功能需要手動啟用。

SPDY 基礎功能

  • 多路複用(multiplexing),多路複用通過多個請求共用一個連線的方式,降低了 TCP 連線建立和釋放的開銷,同時提高了頻寬的利用率。
  • 請求優先順序(request prioritization),多路複用帶來的一個問題是,在共享連線的基礎上會存在一些關鍵請求被阻塞,SPDY 允許給每個請求設定優先順序,這樣重要的請求就會優先得到響應。
  • header 壓縮,前面提到的 HTTP 1.x 的 header 很多時候都是重複而且多餘的。選擇合適的壓縮演算法可以減小包的大小和數量。SPDY 對 header 的壓縮素可以達到 80% 以上。

SPDY 高階功能

  • 服務端推送,HTTP 只能由客戶端傳送,伺服器只能被動傳送響應。不過在開啟服務端推送後,服務端通過 **X-Associated-Content ** header 會告知伺服器會有新的內容被推送過來,
  • 服務端暗示,和服務端推送所不同的是,服務端暗示不會推送內容,只是告訴客戶端有新的內容產生,,內容的下載還是需要客戶端主動發起請求。服務端暗示通過 X-Subresources header 來通知,一般應用場景是客戶端需要先查詢服務端狀態,然後再下載資源,可以節約一次查詢請求。

自動 SPDY 出現後,頁面載入時間相比於 HTTP 減少了 64%,而且各大瀏覽器廠商在 SPDY 誕生之後的 1 年多時間裡也都陸續支援了 SPDY。但是,SPDY 的生存時間卻沒有人們想象中的那麼長,SPDY 從 2012 年誕生到 2016 年停止維護,如果 HTTP 2.0 沒有誕生,我相信 Google 可能會收到更多的真實反饋和資料,但是 SPDY 在這段時間裡也完成了自己的使命。

初探 HTTP 2.0

HTTP 2.0 也被寫作 HTTP/2 ,它是超文字傳輸協議的 2.0 版本,因為 SPDY 的流行讓 IETF 看到了優化後的效果,以及可以通過修改協議層來優化 HTTP,所以 IETF 開始決定正式考慮制定 HTTP 2.0 的計劃,而且,SPDY的部分設計人員也被邀請參與了 HTTP 2.0 的設計。

HTTP2.0 在設計之初就與 SPDY 的設計目的和出發點不同,SPDY 更像是 Google 自家的一個產品,相當於自家的一個玩具,你怎麼玩兒都行,而 HTTP 2.0 在設計之初就是為了普適性的這個目的,所以,一開始任何的設計都會關係到以後的維護問題,如果有什麼瑕疵或者不足的地方可能會影響巨大,所以考慮的問題角度要非常嚴謹和慎重。

HTTP 2.0 在設計之初就有一些重要的前提:

  • 客戶端向伺服器傳送請求的這種基本模型不會改變。
  • 原有的協議頭不會改變,使用 http:// 和 https:// 的服務和應用不會做任何修改,不會有 http2://。
  • 使用 HTTP 1.x 的客戶端和伺服器可以平滑升級到 HTTP 2.0 上。
  • 不識別 HTTP 2.0 的代理伺服器可以將請求降級到 HTTP 1.x。

客戶端在和伺服器確定是使用 HTTP1.x 還是 HTTP 2.0 之前,需要先確定對方是否支援 HTTP 2.0,所以這裡必須要先進行協商,也就是客戶端詢問伺服器,這樣一來一回就多了一個 RTT 的延遲。我們對 HTTP 1.x 的修改就是為了降低延遲,現在又多了一個 RTT,這樣顯然是無法接受的。Google 制定 SPDY 協議的時候也遇到了這個問題,他們採取的做法是強制協商在 SSL 層完成,還因此制定了一個 TLS 的擴充,叫做 NPN(Next Protocol Negotiation)。雖然 HTTP 2.0 也採用了相同的方式,不過經過討論後,最終 HTTP 2.0 沒有強制要走 SSL 層,HTTP 2.0 沒有使用 NPN,卻制定了一個 TLS 的擴充叫做 ALPN(Application Layer Protocol Negotiation),現在,SPDY 也打算歉意到 ALPN 了。

HTTP 2.0 的主要變化

HTTP 2.0 自從設計到誕生以來,發生了很多變化,不過對於開發人員和廠商來說,影響比較大的就幾點:

二進位制格式

HTTP 1.x 的誕生使用的是明文協議,它的格式主要由三部分構成:請求行(request line) 、請求頭(header) 和報文體(body),要識別這三部分必須要做協議解析,而協議解析是基於文字的,基於文字的解析存在多樣性的缺陷,而二進位制格式只能識別 0 和 1 ,比較固定就,基於這種考量,HTTP 2.0 決定採用二進位制格式,實現方便而且健壯性強。

下面這幅圖很好的詮釋了 HTTP1.x 和 HTTP 2.0 使用的不同報文格式。

image-20210829114232831

在 HTTP 2.0 報文中,length 定義了整個 frame 的開始到結束,type 定了 frame 的型別,一種有十種,flags 定義了一些重要的引數,stream id 用作流控制,剩下的 payload 就是 request 的正文。

雖然 HTTP 2.0 報文格式看上去和 HTTP 1.x 的完全不同,但是實際上 HTTP 2.0 並沒有改變 HTTP 1.x 的語義,它只是在 HTTP 1.x 的基礎上封裝了一層,如下圖所示

image-20210829120754200

從上圖可以看到,HTTP 1.x 中的請求行、請求頭被 HTTP 2.0 封裝成為了 HEADERS Frame,而 HTTP 1.x 中的報文體被 HTTP 2.0 封裝成為了 Data Frame。除錯的時候瀏覽器甚把 HTTP 2.0 的 frame 自動還原成HTTP 1.x的格式。

連線共享

我們上面聊到,HTTP 1.x 並沒有真正意義上的解決連線複用問題,所以 HTTP 2.0 要解決的一大難題就是連線共享(MultiPlexing),連線共享意味著客戶端與伺服器之間也只需要一個連線即可,這樣即使來自很多流的資料包也能夠混合在一起通過同樣連線傳輸,再根據不同幀首部的 stream id 識別符號重新連線將不同的資料流進行組裝。

image-20210831221340797

什麼是 stream?

stream 是連線中的一個虛擬通道,可以承載雙向訊息傳輸。每個流有唯一整數識別符號。為了防止兩端 streaam id 衝突,客戶端發起的流具有奇數 id,伺服器端發起的流具有偶數 id。

我們上面提到 HTTP 1.x 沒有真正解決連線共享還有一個主要的因素就是無法對不同的請求設定優先順序,這樣會導致關鍵請求被阻塞。而 HTTP 2.0 你可以對不同的 stream 設定不同的優先順序,stream 之間也可以設定依賴,依賴和優先順序都可以動態調整,這樣就會解決關鍵請求被阻塞的問題。

頭部壓縮

上面還聊到了 HTTP1.x 中的 header 由於 cookie 和 user agent 不存在記憶性,這樣導致每次都要帶著這些頭重新傳送請求,HTTP 2.0 使用 encoder 來減少傳輸的 header 大小,通訊雙方會各自快取一份 header 欄位表,這樣能夠避免重複傳輸 header ,也能夠減小傳輸的大小。HTTP 2.0 採用的是 HPACK 壓縮演算法。

這種壓縮演算法的主要思想可以參考官方文件 https://httpwg.org/specs/rfc7541.html

服務端推送

服務端推送(Server Push) 我們上面也已經聊過,HTTP 2.0 能夠以的方式將客戶端的內容預先傳送出去,正因為沒有發起請求,建立連線等操作,所以靜態資源通過服務端推送的方式可以極大地提升速度。服務端推送還有一個更大的優勢:快取,快取也能夠在不同頁面之間共享快取資源。

需要注意下面幾個點:

1、推送遵循同源策略;

2、這種服務端的推送是基於客戶端的請求響應來確定的。

當服務端需要主動推送某個資源時,便會傳送一個 Frame Type 為 PUSH_PROMISE 的 frame ,裡面帶了 PUSH 需要新建的 stream id。意思是告訴客戶端:接下來我要用這個 id 向你傳送東西,客戶端準備好接著。客戶端解析 frame 時,發現它是一個 PUSH_PROMISE 型別,便會準備接收服務端要推送的流。

HTTP 2.0 的缺陷

HTTP 2.0 帶給我們最驚豔的莫過於多路複用了,雖然多路複用有種種好處,但是大家可以想一下,多路複用雖然好,但是它是建立在 TCP 連線的基礎上,在連線頻繁的情況下,是不是會對 TCP 連線造成壓力,這個角度來講,TCP 很容易成為效能瓶頸。

還有一點,使用 HTTP 2.0 會增加一次 TLS 握手過程,增加 RTT,這個我們上面也說到了。

在 HTTP 2.0 中,多個請求是在同一個 TCP 管道中,這樣當 HTTP 2.0 出現丟包時,整個 TCP 都要開始等待重傳,那麼就會阻塞該 TCP。連線中的所有請求。

總結

這篇文章我們主要聊了一下 HTTP從1.x 到 SPDY,再到 HTTP 2.0 的協議變遷以及 HTTP 1.0、1.1 的痛點和弊端,SPDY 的出現背景以及發現情況,然後 HTTP 2.0 的主要特徵、HTTP 2.0 相對於 HTTP 1.x 有了哪些改變,它的缺點有哪些。

原文連結: HTTP 2.0 ,有點炸 !

相關文章