HTTP/2 協議


HTTP/2 協議

 

HTTP/2是由google的SPDY協議衍生而來的。

HTTP/2 沒有改動 HTTP 的應用語義。 HTTP 方法、狀態代碼、URI 和標頭字段等核心概念一如往常。 不過,HTTP/2 修改了數據格式化(分幀)以及在客戶端與服務器間傳輸的方式。這兩點統帥全局,通過新的分幀層向我們的應用隱藏了所有復雜性。 因此,所有現有的應用都可以不必修改而在新協議下運行。

 

 

二進制分幀

HTTP/2 采用二進制格式傳輸數據,而非 HTTP/1.x 的文本格式。 HTTP/2 中,同域名下所有通信都在單個連接上完成,該連接可以承載任意數量的雙向數據流。連接數量減少對提升 HTTPS 部署的性能來說是一項特別重要的功能:可以減少開銷較大的 TLS 連接數、提升會話重用率,以及從整體上減少所需的客戶端和服務器資源。

每個數據流都以消息的形式發送,而消息又由一個或多個幀組成。多個幀之間可以亂序發送,根據幀首部的流標識可以重新組裝。

HTTP/2 仍是對之前 HTTP 標准的擴展,而非替代。 HTTP 的應用語義不變,提供的功能不變,HTTP 方法、狀態代碼、URI 和標頭字段等這些核心概念也不變。

  

 

這里所謂的“層”,指的是位於套接字接口與應用可見的高級 HTTP API 之間一個經過優化的新編碼機制:HTTP 的語義(包括各種動詞、方法、標頭)都不受影響,不同的是傳輸期間對它們的編碼方式變了。 HTTP/1.x 協議以換行符作為純文本的分隔符,而 HTTP/2 將所有傳輸的信息分割為更小的消息和幀,並采用二進制格式對它們編碼。

新的二進制分幀機制改變了客戶端與服務器之間交換數據的方式。 為了說明這個過程,我們需要了解 HTTP/2 的三個概念:

  • 數據流 (stream):已建立的連接內的雙向字節流,可以承載一條或多條消息。
  • 消息 (message):與邏輯請求或響應消息對應的完整的一系列幀。
  • 幀 (frame):HTTP/2 通信的最小單位,每個幀都包含幀頭,至少也會標識出當前幀所屬的數據流。

這些概念的關系總結如下:

  • 所有通信都在一個 TCP 連接上完成,此連接可以承載任意數量的雙向數據流。
  • 每條stream都有一個唯一的標識符和可選的優先級信息,用於承載雙向消息。
  • 每個message都是一條邏輯 HTTP 消息(例如請求或響應),包含一個或多個幀。
  • frame是最小的通信單位,承載着特定類型的數據,例如 HTTP 標頭、消息負載等等。 來自不同數據流的幀可以交錯發送,然后再根據每個幀頭的數據流標識符重新組裝。

 

簡言之,HTTP/2 將 HTTP 協議通信分解為二進制編碼幀的交換,這些幀對應着特定數據流中的消息。所有這些都在一個 TCP 連接內復用。 

http2.0的frame格式與http1.x的消息對比:

 

  • length定義了整個frame的開始到結束,
  • type定義frame的類型(一共10種);
  • flags用bit位定義一些重要的參數;
  • stream id用作流控制,一個request對應一個stream並分配一個id,這樣一個連接上可以有多個stream,每個stream的frame可以隨機的混雜在一起,接收方可以根據stream id將frame再歸屬到各自不同的request里面;
  • payload就是request的正文了;
雖然看上去協議的格式和http1.x完全不同了,實際上http2.0並沒有改變http1.x的語義,只是把原來http1.x的header和body部分用frame重新封裝了一層而已。調試的時候瀏覽器甚至會把http2.0的frame自動還原成http1.x的格式。
 
對於http1.x來說,是通過設置tcp segment里的reset flag來通知對端關閉連接的。這種方式會直接斷開連接,下次再發請求就必須重新建立連接。http2.0引入 RST_STREAM類型的frame,可以在不斷開連接的前提下取消某個request的stream,表現更好。
 

 

多路復用

HTTP1/1.0被抱怨最多的就是 連接無法復用head of line blocking這兩個問題。理解這兩個問題有一個十分重要的前提:客戶端是依據域名來向服務器建立連接,一般PC端瀏覽器會針對單個域名的server同時建立6~8個連接,手機端的連接數則一般控制在4~6個。為了突破單域名連接數的限制,這也是為什么CDN站點通常會申請多個靜態資源域名。
 

連接無法復用

會導致每次請求都經歷三次握手和慢啟動。三次握手在高延遲的場景下影響較明顯,TCP慢啟動則對文件類大請求影響較大。
 
http1.0協議頭里可以設置Connection:Keep-Alive。在header里設置Keep-Alive可以在一定時間內復用連接,具體復用時間的長短可以由服務器控制,一般在15s左右。
http1.1之后Connection的默認值就是Keep-Alive,如果要關閉連接復用需要顯式的設置Connection:Close。
 
實現長連接的幾個方案:
  • 基於TCP的長連接,現在很多移動端app(不止是IM類應用)都會自己建立一條自己的長連接通道(基於TCP),使信息的上報和推送可以變得更及時;
  • http long polling,可以參考https://www.cnblogs.com/chenny7/p/3954396.html
  • websocket協議,提供雙向數據傳輸通道;
  • http chunked傳輸編碼,通過在response header增加"Transfer Encoding: chunked" 來告訴客戶端后續還會有新的數據到來。但這個方式無法按照請求來做分割,客戶端收到的每塊數據都需要自己做協議解析;

 

head-of-line blocking(線頭阻塞)

會導致帶寬無法被充分利用,以及后續健康請求被阻塞。假設有5個請求同時發出,如下圖

對於http1.0的實現,在第一個請求沒有收到回復之前,后續從應用層發出的請求只能排隊,請求2,3,4,5只能等請求1的response回來之后才能逐個發出。

在http1.1中,引入了pipeline來解決head of line blocking,如下圖

請求2,3,4,5不用等請求1的response返回之后才發出,而是幾乎在同一時間把request發向了服務器。

下圖可以看到pipeline機制對延遲的改變效果:

 

pipeline提高了性能,但head of line blocking並沒有完全得到解決,server的response還是要求依次返回,遵循FIFO(first in first out)原則。也就是說如果請求1的response沒有回來,2,3,4,5的response也不會被送回來。


HTTP2 Multiplexing

直白的說就是所有的請求都是通過一個 TCP 連接並發完成。HTTP/1.x 雖然通過 pipeline 也能並發請求,但是多個請求之間的響應會被阻塞的,所以 pipeline 至今也沒有被普及應用,而 HTTP/2 做到了真正的並發請求。同時,流還支持優先級和流量控制。當流並發時,就會涉及到流的優先級和依賴。優先級高的流會被優先發送。

下圖快照捕捉了同一個連接內並行的多個數據流。 客戶端正在向服務器傳輸一個 DATA 幀(數據流 5),與此同時,服務器正向客戶端交錯發送數據流 1 和數據流 3 的一系列幀。因此,一個連接上同時有三個並行數據流。

將 HTTP 消息分解為獨立的幀,交錯發送,然后在另一端重新組裝是 HTTP 2 最重要的一項增強:

  • 並行交錯地發送多個請求,請求之間互不影響。
  • 並行交錯地發送多個響應,響應之間互不干擾。
  • 使用一個連接並行發送多個請求和響應。
  • 不必再為繞過 HTTP/1.x 限制而做很多工作(例如級聯文件、image sprites和域名分片)
  • 消除不必要的延遲和提高現有網絡容量的利用率,從而減少頁面加載時間。

HTTP/2 中的新二進制分幀層解決了 HTTP/1.x 中存在的隊首阻塞問題,也消除了並行處理和發送請求及響應時對多個連接的依賴。

 

stream priority

將 HTTP 消息分解為很多獨立的幀之后,我們就可以復用多個數據流中的幀,客戶端和服務器交錯發送和傳輸這些幀的順序就成為關鍵的性能決定因素。 為了做到這一點,HTTP/2 標准允許每個數據流都有一個關聯的權重和依賴關系:

  • 可以向每個數據流分配一個介於 1 至 256 之間的整數。
  • 每個數據流與其他數據流之間可以存在顯式依賴關系,數據流依賴關系通過將另一個數據流的唯一標識符作為父項引用進行聲明;如果忽略標識符,相應數據流將依賴於“根數據流”。

數據流依賴關系和權重的組合讓客戶端可以構建和傳遞“優先級樹”,表明它傾向於如何接收響應。 反過來,服務器可以使用此信息通過控制 CPU、內存和其他資源的分配設定數據流處理的優先級,在資源數據可用之后,帶寬分配可以確保將高優先級響應以最優方式傳輸至客戶端。

數據流依賴關系和權重的組合明確表達了資源優先級:

  • 應盡可能先向父數據流分配資源,然后再向其依賴項分配資源;
  • 共享相同父項的數據流(即,同級數據流)應按其權重比例分配資源。

我們來看一下上圖中的其他幾個操作示例。 從左到右依次為:

  1. 數據流 A 和數據流 B 都沒有指定父依賴項,依賴於顯式“根數據流”;A 的權重為 12,B 的權重為 4。因此,根據比例權重:數據流 B 獲得的資源是 A 所獲資源的三分之一。
  2. 數據流 D 依賴於根數據流;C 依賴於 D。 因此,D 應先於 C 獲得完整資源分配。 權重不重要,因為 C 的依賴關系擁有更高的優先級。
  3. 數據流 D 應先於 C 獲得完整資源分配;C 應先於 A 和 B 獲得完整資源分配;數據流 B 獲得的資源是 A 所獲資源的三分之一。
  4. 數據流 D 應先於 E 和 C 獲得完整資源分配;E 和 C 應先於 A 和 B 獲得相同的資源分配;A 和 B 應基於其權重獲得比例分配。

注:數據流依賴關系和權重表示傳輸優先級,而不是要求,因此不能保證特定的處理或傳輸順序。 即,客戶端無法強制服務器通過數據流優先級以特定順序處理數據流。 盡管這看起來違反直覺,但卻是一種必要行為。 我們不希望在優先級較高的資源受到阻止時,還阻止服務器處理優先級較低的資源。

 

flow control

流控制是一種阻止發送方向接收方發送大量數據的機制,以免超出后者的需求或處理能力。 例如,客戶端可能請求了一個具有較高優先級的大型視頻流,但是用戶已經暫停視頻,客戶端現在希望暫停或限制從服務器的傳輸,以免提取和緩沖不必要的數據。 

  • 流控制具有方向性。 每個接收方都可以根據自身需要選擇為每個數據流和整個連接設置任意的窗口大小。
  • 流控制基於信用。 每個接收方都可以公布其初始連接和數據流流控制窗口(以字節為單位),每當發送方發出 DATA 幀時都會減小,在接收方發出 WINDOW_UPDATE 幀時增大。
  • 流控制無法停用。 建立 HTTP/2 連接后,客戶端將與服務器交換 SETTINGS 幀,這會在兩個方向上設置流控制窗口。 流控制窗口的默認值設為 65,535 字節,但是接收方可以設置一個較大的最大窗口大小(2^31-1 字節),並在接收到任意數據時通過發送 WINDOW_UPDATE 幀來維持這一大小。
  • 流控制為逐躍點控制,而非端到端控制。 即,可信中介可以使用它來控制資源使用,以及基於自身條件和啟發式算法實現資源分配機制。

 

 

Server Push

服務端能夠更快的把資源推送給客戶端。例如服務端可以主動把 JS 和 CSS 文件推送給客戶端,而不需要客戶端解析 HTML 再發送這些請求。當客戶端需要的時候,它已經在客戶端了。

 

HTTP/2 打破了嚴格的請求-響應語義,支持一對多和服務器發起的推送工作流。

所有服務器推送數據流都由 PUSH_PROMISE 幀發起,表明了服務器向客戶端推送所述資源的意圖,並且需要先於請求推送資源的響應數據傳輸。 這種傳輸順序非常重要:客戶端需要了解服務器打算推送哪些資源,以免為這些資源創建重復請求。 滿足此要求的最簡單策略是先於父響應(即,DATA 幀)發送所有 PUSH_PROMISE 幀,其中包含所承諾資源的 HTTP 標頭。

假設服務端接收到客戶端對 HTML 文件的請求,決定用 server push 推送一個css文件。那么,服務端會構造一個請求,包括請求方法和請求頭,填充到一個 PUSH_PROMISE 幀里發送給客戶端,來告知客戶端它已經代勞發了這個請求。當客戶端收到這個 PUSH_PROMISE 幀的時候,它就知道服務端將要推送一個CSS文件回來。如果此時客戶端需要請求這個文件,即便服務端還沒推完,它也不會往服務端發送對CSS文件的請求。在這個例子中,必須先發送 PUSH_PROMISE,再發送 HTML 的內容。這是因為 HTML 中存在對CSS的引用,一旦客戶端發現了這個引用卻還沒收到 PUSH_PROMISE,它就會發起獲取CSS文件請求。

 

在客戶端接收到 PUSH_PROMISE 幀后,它可以根據自身情況選擇拒絕數據流(通過 RST_STREAM 幀)。 (例如,如果資源已經位於緩存中,便可能會發生這種情況。) 這是一個相對於 HTTP/1.x 的重要提升。 相比之下,使用資源內聯(一種受歡迎的 HTTP/1.x“優化”)等同於“強制推送”:客戶端無法選擇拒絕、取消或單獨處理內聯的資源。

使用 HTTP/2,客戶端仍然完全掌控服務器推送的使用方式。 客戶端可以限制並行推送的數據流數量;調整初始的流控制窗口以控制在數據流首次打開時推送的數據量;或完全停用服務器推送。 這些優先級在 HTTP/2 連接開始時通過 SETTINGS 幀傳輸,可能隨時更新。

推送的每個資源都是一個數據流,與內嵌資源不同,客戶端可以對推送的資源逐一復用、設定優先級和處理。 瀏覽器強制執行的唯一安全限制是,推送的資源必須符合原點相同這一政策:服務器對所提供內容必須具有權威性。

 

 

HPACK頭部壓縮

在 HTTP/1.x 中,此元數據始終以純文本形式,通常會給每個傳輸增加 500–800 字節的開銷。如果使用 HTTP Cookie,增加的開銷有時會達到上千字節。

為了減少此開銷和提升性能,HTTP/2 使用 HPACK 壓縮格式壓縮請求和響應標頭元數據,這種格式采用兩種簡單但是強大的技術:

  1. 這種格式支持通過靜態霍夫曼代碼對傳輸的標頭字段進行編碼,從而減小了各個傳輸的大小。
  2. 這種格式要求客戶端和服務器同時維護和更新一個包含之前見過的標頭字段的索引列表(換句話說,它可以建立一個共享的壓縮上下文),此列表隨后會用作參考,對之前傳輸的值進行有效編碼。

利用霍夫曼編碼,可以在傳輸時對各個值進行壓縮,而利用之前傳輸值的索引列表,我們可以通過傳輸索引值的方式對重復值進行編碼,索引值可用於有效查詢和重構完整的標頭鍵值對。

 

HPACK 壓縮上下文包含一個靜態表和一個動態表:靜態表在規范中定義,並提供了一個包含所有連接都可能使用的常用 HTTP 標頭字段(例如,有效標頭名稱)的列表;動態表最初為空,將根據在特定連接內交換的值進行更新。 因此,為之前未見過的值采用靜態 Huffman 編碼,並替換每一側靜態表或動態表中已存在值的索引,可以減小每個請求的大小。

注:在 HTTP/2 中,請求和響應標頭字段的定義保持不變,僅有一些微小的差異:所有標頭字段名稱均為小寫,請求行現在拆分成各個 :method:scheme:authority 和 :path 偽標頭字段。

 

 

  

更安全的SSL

因為客戶端和server之間在確立使用http1.x還是http2.0之前,必須要要確認對方是否支持http2.0,所以這里必須要有個協商的過程。最簡單的協商也要有一問一答,客戶端問server答,即使這種最簡單的方式也多了一個RTT的延遲,我們之所以要修改http1.x就是為了降低延遲,顯然這個RTT我們是無法接受的。

google制定SPDY的時候也遇到了這個問題,他們的辦法是強制SPDY走https,在SSL層完成這個協商過程。ssl層的協商在http協議通信之前,所以是最適合的載體。google為此做了一個tls的拓展,叫NPN(Next Protocol Negotiation),從名字上也可以看出,這個拓展主要目的就是為了協商下一個要使用的協議。

HTTP2雖然沒有強制要走ssl層,但大部分瀏覽器廠商(除了IE)卻只實現了基於https的2.0協議。HTTP2.0沒有使用NPN,而是另一個tls的拓展叫ALPN(Application Layer Protocol Negotiation)。此外,HTTP2.0對tls的安全性做了近一步加強,通過黑名單機制禁用了幾百種不再安全的加密算法,一些加密算法可能還在被繼續使用。如果在ssl協商過程當中,客戶端和server的cipher suite沒有交集,直接就會導致協商失敗,從而請求失敗。

tcp協議優化的一個經典場景是:Nagle算法和Berkeley的delayed ack算法的對立。http2.0並沒有對tcp層做任何修改,所以這種對立導致的高延遲問題依然存在。要么通過TCP_NODELAY禁用Nagle算法,要么通過TCP_QUICKACK禁用delayed ack算法。貌似http2.0官方建議是設置TCP_NODELAY。


 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM