1. 程式人生 > >HTTP 2.0 原理詳細分析

HTTP 2.0 原理詳細分析

target all src coo rime 導致 -a charset afa

HTTP 2.0是在SPDY(An experimental protocol for a faster web, The Chromium Projects)基礎上形成的下一代互聯網通信協議。HTTP/2 的目的是通過支持請求與響應的多路復用來較少延遲,通過壓縮HTTPS首部字段將協議開銷降低,同時增加請求優先級和服務器端推送的支持。
本文目的是學習HTTP 2.0的原理並研究其通信的詳細細節。大部分知識點源於《Web性能權威指南》。

  • 1. 二進制分幀層
    • 1.1 幀(frame)
    • 1.2 消息(message)
    • 1.3 流(stream)
  • 2. 多路復用共享連接
  • 3. 請求優先級
  • 4. 服務端推送
  • 5. 首部壓縮
  • 6. 一個完整的HTTP 2.0通信過程
    • 6.1 基於ALPN的協商過程
    • 6.2 基於HTTP的協商過程
    • 6.3 完整通信過程
  • 7. HTTP 2.0性能瓶頸
  • 參考文獻

1. 二進制分幀層

二進制分幀層,是HTTP 2.0性能增強的核心。
HTTP 1.x在應用層以純文本的形式進行通信,而HTTP 2.0將所有的傳輸信息分割為更小的消息和幀,並對它們采用二進制格式編碼。這樣,客戶端和服務端都需要引入新的二進制編碼和解碼的機制。
如下圖所示,HTTP 2.0並沒有改變HTTP 1.x的語義,只是在應用層使用二進制分幀方式傳輸。
技術分享圖片
因此,也引入了新的通信單位:

1.1 幀(frame)

HTTP 2.0通信的最小單位,包括幀首部、流標識符、優先值和幀凈荷等。
技術分享圖片

其中,幀類型又可以分為:

  • DATA:用於傳輸HTTP消息體;
  • HEADERS:用於傳輸首部字段;
  • SETTINGS:用於約定客戶端和服務端的配置數據。比如設置初識的雙向流量控制窗口大小;
  • WINDOW_UPDATE:用於調整個別流或個別連接的流量
  • PRIORITY: 用於指定或重新指定引用資源的優先級。
  • RST_STREAM: 用於通知流的非正常終止。
  • PUSH_ PROMISE: 服務端推送許可。
  • PING: 用於計算往返時間,執行“ 活性” 檢活。
  • GOAWAY: 用於通知對端停止在當前連接中創建流。

標誌位用於不同的幀類型定義特定的消息標誌。比如DATA幀就可以使用End Stream: true表示該條消息通信完畢。流標識位表示幀所屬的流ID。優先值用於HEADERS幀,表示請求優先級。R表示保留位。
下面是Wireshark抓包的一個DATA幀:
技術分享圖片

1.2 消息(message)

消息是指邏輯上的HTTP消息(請求/響應)。一系列數據幀組成了一個完整的消息。比如一系列DATA幀和一個HEADERS幀組成了請求消息。

1.3 流(stream)

流是連接中的一個虛擬信道,可以承載雙向消息傳輸。每個流有唯一整數標識符。為了防止兩端流ID沖突,客戶端發起的流具有奇數ID,服務器端發起的流具有偶數ID。
所有HTTP 2. 0 通信都在一個TCP連接上完成, 這個連接可以承載任意數量的雙向數據流Stream。 相應地, 每個數據流以 消息的形式發送, 而消息由一 或多個幀組成, 這些幀可以亂序發送, 然後根據每個幀首部的流標識符重新組裝。
技術分享圖片
二進制分幀層保留了HTTP的語義不受影響,包括首部、方法等,在應用層來看,和HTTP 1.x沒有差別。同時,所有同主機的通信能夠在一個TCP連接上完成。

2. 多路復用共享連接

基於二進制分幀層,HTTP 2.0可以在共享TCP連接的基礎上,同時發送請求和響應。HTTP消息被分解為獨立的幀,而不破壞消息本身的語義,交錯發送出去,最後在另一端根據流ID和首部將它們重新組合起來。
我們來對比下HTTP 1.x和HTTP 2.0,假設不考慮1.x的pipeline機制,雙方四層都是一個TCP連接。客戶端向服務度發起三個圖片請求/image1.jpg,/image2.jpg,/image3.jpg。
HTTP 1.x發起請求是串行的,image1返回後才能再發起image2,image2返回後才能再發起image3。
技術分享圖片
HTTP 2.0建立一條TCP連接後,並行傳輸著3個數據流,客戶端向服務端亂序發送stream1~3的一系列的DATA幀,與此同時,服務端已經在返回stream 1的DATA幀
技術分享圖片
性能對比,高下立見。HTTP 2.0成功解決了HTTP 1.x的隊首阻塞問題(TCP層的阻塞仍無法解決),同時,也不需要通過pipeline機制多條TCP連接來實現並行請求與響應。減少了TCP連接數對服務器性能也有很大的提升。

3. 請求優先級

流可以帶有一個31bit的優先級:

  • 0:表示最高優先級
  • 231-1:表示最低優先級

客戶端明確指定優先級,服務端可以根據這個優先級作為依據交互數據,比如客戶端優先級設置為.css>.js>.jpg(具體可參見《高性能網站建設指南》), 服務端按優先級返回結果有利於高效利用底層連接,提高用戶體驗。
然而,也不能過分迷信請求優先級,仍然要註意以下問題:

  • 服務端是否支持請求優先級
  • 會否引起隊首阻塞問題,比如高優先級的慢響應請求會阻塞其他資源的交互。

4. 服務端推送

HTTP 2.0增加了服務端推送功能,服務端可以根據客戶端的請求,提前返回多個響應,推送額外的資源給客戶端。如下圖所示,客戶端請求stream 1,/page.html。服務端在返回stream 1消息的同時推送了stream 2(/script.js)和stream 4(/style.css)。
技術分享圖片
PUSH_PROMISE幀是服務端向客戶端有意推送資源的信號。

  • 如果客戶端不需要服務端Push,可在SETTINGS幀中設定服務端流的值為0,禁用此功能
  • PUSH_PROMISE幀中只包含預推送資源的首部。如果客戶端對PUSH_PROMISE幀沒有意見,服務端在PUSH_PROMISE幀後發送響應的DATA幀開始推送資源。如果客戶端已經緩存該資源,不需要再推送,可以選擇拒絕PUSH_PROMISE幀。
  • PUSH_PROMISE必須遵循請求-響應原則,只能借著對請求的響應推送資源。
    目前,Apache的mod_http2能夠開啟 H2Push on服務端推送Push。Nginx的ngx_http_v2_module還不支持服務端Push。
 
1 Apache mod_headers example
2 <Location /index.html>
3     Header add Link "</css/site.css>;rel=preload"
4     Header add Link "</images/logo.jpg>;rel=preload"
5 </Location>

5. 首部壓縮

HTTP 1.x每一次通信(請求/響應)都會攜帶首部信息用於描述資源屬性。HTTP 2.0在客戶端和服務端之間使用“首部表”來跟蹤和存儲之前發送的鍵-值對。首部表在連接過程中始終存在,新增的鍵-值對會更新到表尾,因此,不需要每次通信都需要再攜帶首部。
技術分享圖片
另外,HTTP 2.0使用了首部壓縮技術,壓縮算法使用HPACK。可讓報頭更緊湊,更快速傳輸,有利於移動網絡環境。
需要註意的是,HTTP 2.0關註的是首部壓縮,而我們常用的gzip等是報文內容(body)的壓縮。二者不僅不沖突,且能夠一起達到更好的壓縮效果。

6. 一個完整的HTTP 2.0通信過程

考慮一個問題,客戶端如何知道服務端是否支持HTTP 2.0?是否支持對二進制分幀層的編碼和解碼?所以,在兩端使用HTTP 2.0通信之前,必然存在協議協商的過程。

6.1 基於ALPN的協商過程

支持HTTP 2.0的瀏覽器可以在TLS會話層自發完成和服務端的協議協商以確定是否使用HTTP 2.0通信。其原理是TLS 1.2中引入了擴展字段,以允許協議的擴展,其中ALPN協議(Application Layer Protocol Negotiation, 應用層協議協商, 前身是NPN)用於客戶端和服務端的協議協商過程。
服務端使用ALPN,監聽443端口默認提高HTTP 1.1,並允許協商其他協議,比如SPDY和HTTP 2.0。
比如,客戶端在TLS握手Client Hello階段表明自身支持HTTP 2.0
技術分享圖片
服務端收到後,響應Server Hello,表示自己也支持HTTP 2.0。雙方開始HTTP 2.0通信。
技術分享圖片

6.2 基於HTTP的協商過程

然而,HTTP 2.0一定是HTTPS(TLS 1.2)的特權嗎?
當然不是,客戶端使用HTTP也可以開啟HTTP 2.0通信。只不過因為HTTP 1. 0和HTTP 2. 0都使用同一個 端口(80), 又沒有服務器是否支持HTTP 2. 0的其他任何 信息,此時 客戶端只能使用HTTP Upgrade機制(OkHttp, nghttp2等組件均可實現,也可以自己編碼完成)通過協調確定適當的協議:

 1 HTTP Upgrade request
 2 GET / HTTP/1.1
 3 host: nghttp2.org
 4 connection: Upgrade, HTTP2-Settings
 5 upgrade: h2c        /*發起帶有HTTP2.0 Upgrade頭部的請求*/       
 6 http2-settings: AAMAAABkAAQAAP__   /*客戶端SETTINGS凈荷*/
 7 user-agent: nghttp2/1.9.0-DEV
 8 
 9 HTTP Upgrade response    
10 HTTP/1.1 101 Switching Protocols   /*服務端同意升級到HTTP 2.0*/
11 Connection: Upgrade
12 Upgrade: h2c
13 
14 HTTP Upgrade success               /*協商完成*/

6.3 完整通信過程

TCP連接建立:
技術分享圖片
TLS握手和HTTP 2.0通信過程:
技術分享圖片
另外,在chrome中通過chrome://net-internals/#http2命令也能捕獲HTTP 2.0通信過程:

42072: HTTP2_SESSION
 1 42072: HTTP2_SESSION
 2 textlink.simba.taobao.com:443 (PROXY 10.19.110.55:8080)
 3 Start Time: 2017-04-05 11:39:11.459
 4 
 5 t=370225 [st=    0] +HTTP2_SESSION  [dt=32475+]
 6                      --> host = "textlink.simba.taobao.com:443"
 7                      --> proxy = "PROXY 10.19.110.55:8080"
 8 t=370225 [st=    0]    HTTP2_SESSION_INITIALIZED
 9                        --> protocol = "h2"
10                        --> source_dependency = 42027 (PROXY_CLIENT_SOCKET_WRAPPER)
11 t=370225 [st=    0]    HTTP2_SESSION_SEND_SETTINGS
12                        --> settings = ["[id:3 flags:0 value:1000]","[id:4 flags:0 value:6291456]","[id:1 flags:0 value:65536]"]
13 t=370225 [st=    0]    HTTP2_STREAM_UPDATE_RECV_WINDOW
14                        --> delta = 15663105
15                        --> window_size = 15728640
16 t=370225 [st=    0]    HTTP2_SESSION_SENT_WINDOW_UPDATE_FRAME
17                        --> delta = 15663105
18                        --> stream_id = 0
19 t=370225 [st=    0]    HTTP2_SESSION_SEND_HEADERS
20                        --> exclusive = true
21                        --> fin = true
22                        --> has_priority = true
23                        --> :method: GET
24                            :authority: textlink.simba.taobao.com
25                            :scheme: https
26                            :path: /?name=tbhs&cna=IAj9EOy3fngCAXBQ5kJ9yusH&nn=&count=13&pid=430266_1006&_ksTS=1491363551394_94&callback=jsonp95
27                            user-agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
28                            accept: */*
29                            referer: https://www.taobao.com/
30                            accept-encoding: gzip, deflate, sdch, br
31                            accept-language: zh-CN,zh;q=0.8
32                            cookie: [382 bytes were stripped]
33                        --> parent_stream_id = 0
34                        --> stream_id = 1
35                        --> weight = 147
36 t=370256 [st=   31]    HTTP2_SESSION_RECV_SETTINGS
37                        --> host = "textlink.simba.taobao.com:443"
38 t=370256 [st=   31]    HTTP2_SESSION_RECV_SETTING
39                        --> flags = 0
40                        --> id = 3
41                        --> value = 128
42 t=370256 [st=   31]    HTTP2_SESSION_UPDATE_STREAMS_SEND_WINDOW_SIZE
43                        --> delta_window_size = 2147418112
44 t=370256 [st=   31]    HTTP2_SESSION_RECV_SETTING
45                        --> flags = 0
46                        --> id = 4
47                        --> value = 2147483647
48 t=370256 [st=   31]    HTTP2_SESSION_RECV_SETTING
49                        --> flags = 0
50                        --> id = 5
51                        --> value = 16777215
52 t=370256 [st=   31]    HTTP2_SESSION_RECEIVED_WINDOW_UPDATE_FRAME
53                        --> delta = 2147418112
54                        --> stream_id = 0
55 t=370256 [st=   31]    HTTP2_SESSION_UPDATE_SEND_WINDOW
56                        --> delta = 2147418112
57                        --> window_size = 2147483647
58 t=370261 [st=   36]    HTTP2_SESSION_RECV_HEADERS
59                        --> fin = false
60                        --> :status: 200
61                            date: Wed, 05 Apr 2017 03:39:11 GMT
62                            content-type: text/html; charset=ISO-8859-1
63                            vary: Accept-Encoding
64                            server: Tengine
65                            expires: Wed, 05 Apr 2017 03:39:11 GMT
66                            cache-control: max-age=0
67                            strict-transport-security: max-age=0
68                            timing-allow-origin: *
69                            content-encoding: gzip
70                        --> stream_id = 1
71 t=370261 [st=   36]    HTTP2_SESSION_RECV_DATA
72                        --> fin = false
73                        --> size = 58
74                        --> stream_id = 1
75 t=370261 [st=   36]    HTTP2_SESSION_UPDATE_RECV_WINDOW
76                        --> delta = -58
77                        --> window_size = 15728582
78 t=370261 [st=   36]    HTTP2_SESSION_RECV_DATA
79                        --> fin = true
80                        --> size = 0
81                        --> stream_id = 1
82 t=370295 [st=   70]    HTTP2_STREAM_UPDATE_RECV_WINDOW
83                        --> delta = 58
84                        --> window_size = 15728640
85 t=402700 [st=32475] 

7. HTTP 2.0性能瓶頸

是不是啟用HTTP 2.0後性能必然提升了?任何事情都不是絕對的,雖然總體而言性能肯定是能提升的。
我想HTTP 2.0會帶來新的性能瓶頸。因為現在所有的壓力集中在底層一個TCP連接之上,TCP很可能就是下一個性能瓶頸,比如TCP分組的隊首阻塞問題,單個TCP packet丟失導致整個連接阻塞,無法逃避,此時所有消息都會受到影響。未來,服務器端針對HTTP 2.0下的TCP配置優化至關重要,有機會我們再跟進詳述。

參考文獻

《Web性能權威指南》
《使用 nghttp2 調試 HTTP/2 流量》 https://imququ.com/post/intro-to-nghttp2.html

HTTP 2.0 原理詳細分析