前言
作為一隻前端開發?,HTTP是我們知識地圖裡面必不可少的一部分,也是面試必問知識點。HTTP2號稱可以讓我們的應用更快、更簡單、更穩定,它完美解決了1.1版本的諸多問題,本文和大家一起聊聊HTTP2的改進點。
HTTP發展史
正式講HTTP2之前我們先講一下HTTP的發展史。
- HTTP/0.9 – 單行協議
HTTP於1990年問世,那時候HTTP非常簡單:只支援GET方法;沒有首部;只能獲取純文字。 - HTTP/1.0 – 搭建協議的框架
1996年,HTTP正式被作為標準公佈,版本為HTTP/1.0。1.0版本增加了首部、狀態碼、許可權、快取、長連線(預設短連線)等規範,可以說搭建了協議的基本框架。 - HTTP/1.1 – 進一步完善
1997年,1.1版本接踵而至。1.1版本的重大改進在於預設長連線;強制客戶端提供Host首部;管線化;Cache-Control、ETag等快取的相關擴充套件。
目前存在的問題
現在我們先不聊HTTP2, 看一下HTTP發展到1.1存在有哪些問題:
- 線頭阻塞:TCP連線上只能傳送一個請求,前面的請求未完成前,後續的請求都在排隊等待。
- 多個TCP連線
雖然HTTP/1.1管線化可以支援請求併發,但是瀏覽器很難實現,chrome、firefox等都禁用了管線化。所以1.1版本請求併發依賴於多個TCP連線,建立TCP連線成本很高,還會存在慢啟動的問題。 - 頭部冗餘,採用文字格式
HTTP/1.X版本是採用文字格式,首部未壓縮,而且每一個請求都會帶上cookie、user-agent等完全相同的首部。 - 客戶端需要主動請求
HTTP/2.0的時代來了
先來一個demo感受一下吊炸天的HTTP/2.0,這個demo是載入379張圖片,來對比HTTP/1.1和HTTP/2.0的效能。HTTP/1.1 與2.0 效能比較
理論上HTTP/2.0會比HTTP/1.1有一倍多的效能提升,弱網環境下,效能提升會更加明顯。下面兩張圖是我在設定網路在fast 3G 和slow 3G的效能對比。
是不是被HTTP/2.0的速度亮瞎了雙眼?2333,接下來我們正式開始聊聊2.0。看看2.0 相比與1.1的一些重大改進。
二進位制分幀層
HTTP2效能提升的核心就在於二進位制分幀層。HTTP2是二進位制協議,他採用二進位制格式傳輸資料而不是1.x的文字格式。
看圖吧!很清晰的表達了HTTP/1.1的響應和2.0的區別。1.1響應是文字格式,而2.0把響應劃分成了兩個幀,圖中的HEADERS(首部)和DATA(訊息負載) 是幀的型別。瞭解更多幀的型別也就是說一條HTTP響應,劃分成了兩個幀來傳輸,並且採用二進位制來編碼。
這裡我們來提三個概念。
- 流(Stream):已建立的TCP連線上的雙向位元組流,可以承載一個或多個訊息。
- 訊息(Message):一個完整的HTTP請求或響應,由一個或多個幀組成。特定訊息的幀在同一個流上傳送,這意味著一個HTTP請求或響應只能在一個流上傳送。
- 幀(Frame):通訊的基本單位。
一個TCP連線上可以有任意數量的流。
多路複用
上面提到HTTP/1.1的線頭阻塞和多個TCP連線的問題,HTTP2的多路複用完美解決。HTTP2讓所有的通訊都在一個TCP連線上完成,真正實現了請求的併發。我們來看一下HTTP2具體是怎麼實現的:
HTTP2建立一個TCP連線,一個連線上面可以有任意多個流(stream),訊息分割成一個或多個幀在流裡面傳輸。幀傳輸過去以後,再進行重組,形成一個完整的請求或響應。這使得所有的請求或響應都無法阻塞。我們再來回看上面的那個demo:
開啟控制檯可以看到,HTTP/1.1的方式,後面的圖片的載入時間主要耗時在stalled,stalled的意思是從TCP連線建立完成,到真正可以傳輸資料之間的時間差。這就是隊頭阻塞,前面的請求沒有處理,後面的請求都在排隊等待。
這裡例子我們能很直觀的看到就是多路複用起到的優化作用。因為HTTP2 實現了請求併發,後面的請求不用再等待,載入時長當然少了很多。截一張HTTP2的圖片載入耗時詳情來看看(要看比較靠後的請求):
咦??什麼情況?我們發現後面的很多請求依舊有在排隊哎,只是排隊的時間相對1.1少了很多。一個TCP連線可以有任意數量的流,也就是同時可以併發任意數量的請求啊,為啥還會排隊呢?原因就是請求太多時,瀏覽器或伺服器會受不了,這超出了它的處理能力。流控制幫我們解決了這個問題,流控制會管理資料的傳輸,允許接收者停止或減少傳送的資料量,免得接收方不堪重負。所以請求太多時,還是會存在排隊等待的問題,因為不管是客戶端或伺服器端,能同時處理請求或響應都是有限的。
頭部壓縮
頭部壓縮也是HTTP2的一大亮點。在1.X版本中,首部用文字格式傳輸,通常會給每個傳輸增加500-800位元組的開銷。現在開啟一個網頁上百個請求已是常態,而每個請求帶的一些首部欄位都是相同的,例如cookie、user-agent等。HTTP2為此採用HPACK壓縮格式來壓縮首部。頭部壓縮需要在瀏覽器和伺服器端之間:
- 維護一份相同的靜態字典,包含常見的頭部名稱,以及常見的頭部名稱和值的組合
- 維護一份相同的動態字典,可以動態的新增內容
- 通過靜態Huffman編碼對傳輸的首部欄位進行編碼
HTTP2的靜態字典是長這個樣子的(只擷取了部分,完整表格在這裡):
所以我們在傳輸首部欄位的時候,例如要傳輸method:GET,那我們只需要傳輸靜態字典裡面method:GET對應的索引值就可以了,一個位元組搞定。像user-agent、cookie這種靜態字典裡面只有首部名稱而沒有值的首部,第一次傳輸需要user-agent在靜態字典中的索引以及他的值,值會採用靜態Huffman編碼來減小體積。
第一次傳輸過user-agent 之後呢,瀏覽器和伺服器端就會把它新增到自己的動態字典中。後續傳輸就可以傳輸索引了,一個位元組搞定。
我們用WireShark來抓包驗證一下:
HTTP2目前都是HTTPS的請求,WireShark對HTTPS網站抓包解密請參考這裡。
- 首次傳輸user-agent和第二次傳輸user-agent
由於第一次傳輸的時候,字典裡面並沒有user-agent的值,這時候user-agent是63位元組,第二次傳輸時,他已經在動態字典裡面了,只傳索引,一個位元組搞定。
- HPACK的首部壓縮力度
Header解碼後的長度有471個位元組,而HEADERS流只有246個位元組。這只是第一個請求,後續的請求壓縮力度會更大,因為前面請求用到的首部(靜態字典中沒有的)會新增到動態字典中,使得後續請求只需要傳輸字典裡面的索引。
伺服器端推送
伺服器端推送使得伺服器可以預測客戶端需要的資源,主動推送到客戶端。
例如:客戶端請求index.html,伺服器端能夠額外推送script.js和style.css。實現原理就是客戶端發出頁面請求時,伺服器端能夠分析這個頁面所依賴的其他資源,主動推送到客戶端的快取,當客戶端收到原始網頁的請求時,它需要的資源已經位於快取。
針對每一個希望傳送的資源,伺服器會傳送一個PUSH_PROMISE幀,客戶端可以通過傳送RST_STREAM幀來拒絕推送(當資源已經位於快取)。這一步的操作先於父響應(index.html),客戶端了解到伺服器端打算推送哪些資源,就不會再為這些資源建立重複請求。當客戶端收到index.html的響應時,script.js和style.css已經位於快取。
想要搭一個HTTP2伺服器的話推薦node,很簡單。連結
參考文章
結語
簡單講了HTTP2相比1.1版本的重要改進點,感受了一下h2的強大。還有一些流優先化等特性文中未涉及,感興趣的可以在參考文章中看看。如有錯誤,懇請指正!
@Author:小夭yao愛吃糖糖糖