HTTP/2.0 指南 HTTP/2.0 協議基本上已經開始大規模使用,本文主要介紹 HTTP/2.0 協議的一些特性,以及前端應該對應做哪些優化的調整。 HTTP/2.0 之前 最早期的 HTTP/1.0 時代,一個資源 == 一個 TCP 鏈接 == 一個 HTTP 鏈接,每個 HTTP 請求結束都會斷開 TCP 連接,新的 HTTP 請求會另外新建一個 TCP 連接,並且只有當一個完整的請求結束(TCP closed)才會開始下一個請求(阻塞)。 這種使用 TCP 的方式最嚴重的問題是阻塞。後來為了解決阻塞問題,請求允許並發,即同一個域名允許建立多個 TCP 鏈接,這樣算是解決了部分阻塞問題,但是還是有多次建立 TCP 連接的延遲(Hand shaking)以及 TCP 特有的慢啟動(Slow start)問題。 補充:TCP 慢啟動(Slow start) TCP 連接會隨著時間自我調整,起初會限制連接的最大速度,如果數據傳輸成功,會隨著時間的推移提高傳輸速度 TCP 協議本身太複雜了,細節以後再看吧。簡單來說,TCP 協議主要解決的是兩個問題 穩定傳輸:ACK 確認機制 包亂序:Sequence Number 同時,TCP 還有兩套控制機制 Congestion Control (堵塞控制):避免高速發送端癱瘓網路 Flow Control(流量控制):避免高速發送端癱瘓低速接收端 這些機制使得 TCP 協議不僅僅知道自己信道的情況,還能知道整個網路的情況慢啟動是堵塞控制的一個基本演算法,簡單說就是,先設置小一點的窗口,發送少一點的數據,然後接受 ACK,然後慢慢增加窗口大小,通過丟包率,Round Trip Time 等等來判斷網路環境,設定一個合適的窗口大小。 長鏈接 為了解決 TCP 連接利用率低的問題,所以又提出了長鏈接(1.0 開啟,1.1 默認開啟)。即一個請求完成後,不會立刻斷開連接,而是在一定的時間內保持連接,以便快速處理即將到來的 HTTP 請求,復用同一個 TCP 通道,直到客戶端心跳檢測失敗或伺服器連接超時。 server 通過 set HTTP Header Connection: keep-alive 來建立 client 通過 set HTTP Header Connection: close 來關閉 長鏈接解決了多次創建 TCP 連接的延遲,但是還是有線頭阻塞(Head of line blocking)的問題,即一個 TCP 連接一次只能發出一個請求,所以客戶端必須等待收到響應後才能發出另外一個請求,這樣耗時的請求如果在前面會 block 後面耗時的請求。 小插曲:管道 Pipelining HTTP 管道曾被提出來用以解決線頭阻塞問題,即把多個 HTTP 請求通過一個 TCP 連接傳送,在發送過程中不需要等待伺服器對前一個請求的響應,但是客戶端還是要按照發送請求的順序接收響應。這種方式並沒有根本解決線頭阻塞問題,因為響應按順序接收還是會有阻塞,所以這個功能都被瀏覽器默認關閉(或者壓根沒有)HTTP/2.0 通過分幀將數據通過幀的方式傳送,徹底解決了這個問題,並且做了一些別的優化 HTTP/2.0 協議抽象描述(下面這段話很牛逼,需要仔細研讀) 在客戶端與伺服器之間僅建立一個 TCP 連接,而且該連接在交互持續期間一直處於打開狀態。在此連接上,消息是通過邏輯流進行傳遞的。一條消息包含一個完整的幀序列。在經過整理後,這些幀表示一個響應或請求。 這裡有一些概念很重要,下面的內容都圍繞這些概念展開 Connection (連接):僅與一個對等節點建立一個 Stream(流):邏輯意義上的流,物理實體為連接,一個物理連接擁有多個邏輯流 消息 Message(請求/響應):是一組幀,通過邏輯流傳輸,重建這些幀會得到一個完整的請求或者響應 幀(Frame):通信的基本單位 幀的結構 LENGTH :幀的大小,最多可為 2 24 bit (16MB) TYPE :標識幀的用途 HEADERS:只有 header 信息 DATA:只有 messages payload PRIORITY:流的優先順序信息 RST_STREAM:報錯,reject PUSH_PROMISE,關閉連接 SETTINGS:連接設置 PUSH_PROMISE: 通知客戶端有伺服器推送的意圖(intent) PING:心跳和 round-trip time GOAWAY:對當前連接停止提供流 WINDOW_UPDATE:flow control of streams CONTINUATION:繼續一系列的 HEADER fragment R:reserved 保留位 FLAG:Boolean 值( 0/1) DATA frame 有兩個 flag:END_STREAM / PADDED HEADER frame 有四個 flag:END_STREAM / PADDED / END_HEADERS / PRIORITY PUSH_PROMISE 有兩個 flag:END_HEADERS / PADDED STREAM IDENTIFIER:用於 track the frame membership of a logical stream 其中 FLAG : END_STREAM:end of the data stream PADDED:存在填充數據 END_HEADERS:end of the headers PRIORITY:優先順序被設定 二進位分幀(Binary framing) 分幀層處理的栗子 1. 文本請求映射到幀 END_STREAM 為 true (+),表示為請求的最後一幀(沒有 DATA 幀) END_HEADERS 為 true,表示為流中最後一個包含 HEADER 信息的幀 2. 文本響應映射到幀 END_STREAM in HEADERS 為 false (-) 表示不是流的最後一幀 END_HEADER in HEADERS 為 true (+) 表示最後一個 HEADER 幀 END_STREAM in DATA 為 true (+) 表示當前流最後一幀 1. 二進位協議(Binary Protocol) HTTP/2 保留了原始 HTTP 協議的語義,但更改了在系統之間傳輸數據的方式 HTTP/2.0 文本格式跟二進位格式的主要差別在於解析 比如 TCP 就是二進位協議,每一位表示什麼都是固定的,解析起來只用判斷 0/1 就好,效率更高 HTTP/1.0 就是文本協議,因為都是字元串,解析起來要麼是正則,要麼是狀態機,效率更低 HTTP/2.0 允許保留原來的文本格式,但會經過一個"二進位分幀"的過程,將文本徹底轉化為二進位傳輸。 2. 多路復用(Multplexing) 舊的 HTTP 協議有個問題叫線頭阻塞(Head of line blocking),之前的解決方案是同時開啟多個 TCP 鏈接(Chrome 有6個),但是這樣多個 TCP 連接就很耗費網路資源了。實際上"單線程"就夠了,方式就是將消息分成更小的單位(幀),這樣就實現了完全雙向?的請求和響應消息復用。比如下圖有三個邏輯流,一個請求(深藍),兩個響應(淺藍,綠),每一塊代表一個幀 將幀分解成 HEADER 和 DATA 幀 優點: Request 和 Response 都在一個 socket 所有的響應和請求都無法相互阻塞 減少了建立連接帶來的延遲 不再需要像 HTTP/1.1 那樣將多個請求合成一個 每個伺服器(域)只是用一個鏈接,而不是每個文件一個鏈接 3. 伺服器推送(Server push) 允許伺服器預測客戶端的需要,在請求處理完成之前就可以先發一個 PUSH_PROMISE frame,然後在 push 資源。為防止發送不必要的資源,伺服器會給每一個要推送的資源發送一個 PUSH_PROMISE frame,如果資源已經有緩存,瀏覽器可以 respond 一個 RST_STREAM frame,來拒絕 PUSH (伺服器推送這個選項怎麼說呢,理論上有用,但是實際上還需要 tune,有待觀察吧) 4. 流控制(Flow control) 防止 receiver overwhelmed by the sender,允許 receiver 停止/減少發送的數據。比如視屏流媒體服務,用戶點擊暫停,client will informs the server to stop sending video data。連接一旦 open,server 和 client 便會交換 SETTINGS frame,從而構建 flow-control window 的大小。默認為 65KB,但是可以通過 WINDOW_UPDATE frame 來改變。 5. 頭部壓縮(Header compression) 頭部壓縮的原理是 --- 緩存,與其叫頭部壓縮還不如叫頭部緩存。使用 HPACK 協議,要求客戶端和伺服器各自維護一個 HEADER 欄位的列表,在多次發送的時候,只發送差異的部分,其餘的從緩存表裡面取。// 用 JavaScript 描述的話大概是下面的意思 const cachedHeaders = { method: GET, host: example.com } const newHeaders = { method: POST } const receivedHeaders = { ...cachedHeaders ...newHeaders } 舉個栗子,第一次請求後,第二次請求只發送與之前請求頭不同的部分 6. 優先順序(Priority) Messgae 通過流傳輸,每個流都會被指定一個優先順序,優先順序決定處理的順序和分配的資源,優先順序的大小會在 HEADER frame 或者是 PRIORITY frame,為 0 - 256 的數字。優先順序還可以為樹形結構,來標記依賴關係 A 先發送,B / C 同時發送,分別拿到 40% / 60% 的資源,D / E 則拿到 C 的各一半的資源。優先順序僅僅是一個參考(only a suggestion),讓客戶端告訴服務如何處理請求是不對的,伺服器應該根據自己的能力(capabilities)來決定如何處理。 使用 HTTP/2.0 時的優化 減少分片(Sharding)的使用 sharding 指的是將服務分散到多個主機上,因為 2.0 以前是通過多個 TCP 連接來達到並發的目的,而瀏覽器對每個域名可以建立的 TCP 連接是有限制的,所以以前的優化手段是將多個資源分散到多個伺服器。 但是 2.0 協議使用多個 TCP 連接反而會造成性能的下降,因為建立連接很耗費網路資源。同理原來的一些優化方式也都沒什麼用了比如 Spiriting(雪碧圖)一種將多個圖合成一個圖從而減少請求的方式 Inline(內聯)將圖片用 data url 代替,從而減少請求 Concatenation(拼接)將多個 JS 文件打包成一個 這些原來的優化方式反而會損害性能,因為各種合成和拼接都會導致緩存沒那麼容易。 Ref https://www.mnot.net/talks/h2fe/#5 https://ye11ow.gitbooks.io/http2-explained/content/ https://www.zhihu.com/question/34074946 https://www.w3ctech.com/topic/1563#tip7sharding http://www.ruanyifeng.com/blog/2016/08/http.html https://juejin.im/post/5b8909036fb9a01a0b31a7a4 https://www.ibm.com/developerworks/cn/web/wa-http2-under-the-hood/index.html https://developer.ibm.com/articles/wa-http2-under-the-hood/ 推薦閱讀: 相关文章 {{#data}} {{title}} {{/data}}