HTTP2 規範在2015年5月正式發佈,至今大多數瀏覽器和服務器已經對此協議提供了支持:css
(2018-04-09)做爲一個對 HTTP1.x 進行了增強、補充和完善的更好的協議,值得咱們好好的去了解它,而後使用它作出更美妙的事情。html
HTTP1.1 自從1997年發佈1999年最後一次修改以來,咱們已經使用 HTTP1.x 至關長一段時間了,可是隨着近十年互聯網的爆炸式發展,當時協議規定的某些特性,已經沒法知足現代網絡的需求了。git
HTTP1.x 有如下幾點致命缺陷:(以瀏覽器至服務器爲例)github
就是以上問題嚴重影響了現代互聯網信息交互的效率和靈活性,此時更現代更高效的通信協議 HTTP2 應運而生。web
HTTP2 使用了多路複用
、HPACK頭壓縮
、流 + 二進制幀
和流優先級
等技術手段解決上述問題。算法
HTTP2 的前身是 SPDY協議(一個 Google 主導推行的應用層協議,做爲對 HTTP1 的加強),初版草稿就是基於 SPDY3 規範修改制定而來。HTTP2必須在維持原來 HTTP 的範式(不改動 HTTP/1.x 的語義、方法、狀態碼、URI 以及首部字段等等)前提下,實現突破性能限制,改進傳輸性能,實現低延遲和高吞吐量。瀏覽器
HTTP2 的特性包括:緩存
在 HTTP1.x 時代,不管是傳輸內容仍是頭信息,都是文本/ASCII編碼的,雖然這有利於直接從請求從觀察出內容,可是卻使得想要實現併發傳輸異常困難(存在空格或其餘字符,很難判斷消息的起始和結束)。使用二進制傳輸能夠避免這個問題,由於傳輸內容只有1和0,經過下面第二點的「幀」規範規定格式,便可輕易識別出不一樣類型內容。同時使用二進制有一個顯而易見的好處是:更小的傳輸體積。安全
HTTP2 在維持原有 HTTP 範式的前提下,實現突破性能限制,改進傳輸性能,實現低延遲和高吞吐量的其中一個關鍵是:在應用層(HTTP2)和傳輸層(TCP or UDP)之間增長了二進制分幀層
。性能優化
幀(Frame)是 HTTP2 通信中的最小傳輸單位,全部幀以固定的 9 個八位字節頭部開頭,隨後是一個可變長度的有效載荷
幀結構圖
+-----------------------------------------------+
| 長度Length (24) |
+---------------+---------------+---------------+
| 類型Type (8) | 標誌Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| 流標識符Stream Identifier (31) |
+=+=============================================================+
| 幀載荷Frame Payload (0...) ...
+---------------------------------------------------------------+
複製代碼
規範中一共定義了 10 種不一樣的幀,其中最基礎的兩種分別對應於 HTTP1.x 的 DATA 和 HEADERS。
一個真正的 HTTP2 請求相似下圖:
上一節提到的
Stream Identifier
將 HTTP2 鏈接上傳輸的每一個幀都關聯到一個「流」。流是一個獨立的,雙向的幀序列,能夠經過一個 HTTP2 的鏈接在服務端與客戶端之間不斷的交換數據。
每一個單獨的 HTTP2 鏈接均可以包含多個併發的流,這些流中交錯的包含着來自兩端的幀。流既能夠被客戶端/服務器端單方面的創建和使用,也能夠被雙方共享,或者被任意一邊關閉。在流裏面,每一幀發送的順序很是關鍵。接收方會按照收到幀的順序來進行處理。
上面是《HTTP2 講解》對流的解釋,下面接着是一個小火車的例子,可是我的以爲這個例子有必定的誤差,而且並不能讓人直觀的理解 幀-流-鏈接
之間的關係,如下是我的理解:
A "stream" is an independent, bidirectional sequence of frames exchanged between the client and server within an HTTP/2 connection. --rfc7540 StreamsLayer
「流」是一個邏輯上的概念(沒有真正傳輸流這麼個東西),是 HTTP2 鏈接中在客戶端和服務器之間交換的獨立雙向幀序列,這就是爲何在規範中的 stream 也是用雙引號括起來的緣由。從上一節咱們能夠知道,HTTP2 的傳輸單位是幀,流其實就是一個幀的分組集合的概念,爲何須要這個邏輯集合呢?答案就在多路複用
。
多路複用是解決 HTTP1.x 缺陷第一點(併發問題)和第二點(HOLB線頭問題)的核心技術點。這裏須要舉個🌰來講明:
假設已經創建了 TCP 鏈接,如今須要客戶端發起了兩個請求,從流的角度看是這樣的:
可是實際 TCP 鏈接只有一個,兩個幀是不可能真的「同時」到達服務器的,多路複用更像是 CPU 處理任務概念中的 併發
,而不是並行,從規範中使用術語 Stream Concurrency 而不是 Stream Parallelism
也可得出此結論,因此實際傳輸時是下圖:
上圖須要注意三點:
正是因爲上述第一點特性,解釋了爲何須要「流」這個邏輯集合。同時,經過這種 幀-流-鏈接
的組合,解決了請求併發(一次鏈接多個請求)和HOLB線頭問題(併發發送,異步響應)。
進入 HTTP/2: the Future of the Internet 由 Akamai 公司創建的官方 Demo,能夠看出 HTTP2 相比於以前的 HTTP1.1 在性能上的大幅度提高。
HTTP1.1
HTTP2
在過去,咱們發現 HTTP 性能優化的關鍵不在於高寬帶,而是低延遲。
從上圖可見,當帶寬到達必定的速度以後,對頁面加載速度的提高已經不多了,可是隨着延遲的減小,頁面加載時間會對應持續的減小。因爲 TCP 鏈接存在一種稱爲「調諧」的慢啓動(slow start)特性,讓本來就具備突發性和短時性的 HTTP 鏈接變的十分低效。而 HTTP2 經過讓全部數據流共用同一個鏈接,能夠更有效地使用 TCP 鏈接,讓高帶寬也能真正的服務於 HTTP 的性能提高。
以上純屬我的理解,若有不對請不吝指出共同討論,感謝!
咱們都知道 HTTP協議自己是無狀態(stateless) 的:每一個請求之間互不關聯,每一個請求都須要攜帶服務器所須要的全部細節信息。好比說請求1發送給服務器信息「我是用戶A」,而後請求二發送信息「修改個人用戶名爲XX」,這時若是請求二沒有攜帶「我是用戶A」的信息,那麼服務器是不知道要修改哪一個用戶的用戶名的。
這顯然是不符合當前 web 應用系統架構的,由於通常系統都須要進行鑑權,日誌記錄,安全校驗等限制,因此須要獲取當前操做用戶的信息,出於安全和性能考慮咱們不能在消息體中明文包含這些信息,HTTP2 以前的解決方案通常是使用 Cookies 頭、服務器session 等方式模擬出「狀態」。而使用 Cookies 頭的缺點就是每一個請求都須要攜帶龐大的重複的信息而且沒法壓縮,假設一個請求的 header 是2kb,那麼一百個請求就是重複的 200Kb 信息,這是一個巨大的帶寬浪費。
HTTP2 增長了兩個特性解決上述問題:
這個功能一般被稱做「緩存推送(cache push)」。主要的思想是:當一個客戶端請求資源X,而服務器知道它極可能也須要資源Z的狀況下,服務器能夠在客戶端發送請求Z前,主動將資源Z推送給客戶端。這個功能幫助客戶端將Z放進緩存以備未來之需。
服務器推送須要客戶端顯式的容許服務器提供該功能。但即便如此,客戶端依然能自主選擇是否須要中斷該推送的流。若是不須要的話,客戶端能夠經過發送一個 RST_STREAM
幀來停止推送。
咱們來看一下實際場景:如今咱們訪問一個網站,第一個請求通常是獲取 Document 頁面,而後瀏覽器解析這個頁面,在遇到須要資源獲取的時候(css、js、圖片等),再去發起資源獲取請求,以下圖:
【傳統作法】
爲了加速這個過程,減小白屏時間,傳統的作法是把首頁須要的資源都內聯到 Document 中,還有合併資源好比 CSS sprites,js 壓縮合並等。以下圖:
【HTTP2】
在 HTTP2 的場景下,客戶端在請求 Document 的時候,服務器若是知道頁面須要的資源有哪些,就能夠把那些資源也一同返回了:
注意:主動推送的資源是能被瀏覽器緩存的!
那麼問題來了,若是客戶端已經緩存了資源,此時服務器每次都還推送一樣的資源給客戶端,這不是很大的浪費嗎?
答:原來確實會存在這種狀況,因此 IETF 小組正在擬定一個名爲 cache-digest的技術規範,用於幫助客戶端主動告訴服務端哪些資源已經緩存了,不須要重複發送。
關於服務端推送對網頁性能的影響,和對於 CDN 的使用的比較,能夠參考下面兩篇文章:
結論:使用 HTTP2 的多路複用和服務器推送功能,並不意味着能夠減小甚至拋棄使用 CDN,由於 CDN 帶來的現實地理位置上延遲減小是 HTTP2 所不能解決的,反而咱們應該思考的是如何把 HTTP2 和 CDN 結合起來,進一步提高網絡服務的效率和穩定性,減小延遲。
每一個流都包含一個優先級(也就是「權重」),它被用來告訴對端哪一個流更重要。當資源有限的時候,服務器會根據優先級來選擇應該先發送哪些流。
藉助於PRIORITY幀,客戶端一樣能夠告知服務器當前的流依賴於其餘哪一個流。該功能讓客戶端能創建一個優先級「樹」,全部「子流」會依賴於「父流」的傳輸完成狀況。
優先級和依賴關係能夠在傳輸過程當中被動態的改變。這樣當用戶滾動一個全是圖片的頁面的時候,瀏覽器就可以指定哪一個圖片擁有更高的優先級。或者是在你切換標籤頁的時候,瀏覽器能夠提高新切換到頁面所包含流的優先級。
HTTP1.x 的有一個缺點是:當一個含有確切值的 Content-Length
的 HTTP 消息被送出以後,你就很難中斷它了。固然,一般你能夠斷開整個 TCP 連接(但也不老是能夠這樣),但這樣致使的代價就是須要經過三次握手來從新創建一個新的TCP鏈接。
一個更好的方案是隻終止當前傳輸的消息並從新發送一個新的。在http2裏面,咱們能夠經過發送 RST_STREAM 幀來實現這種需求,從而避免浪費帶寬和中斷已有的鏈接。
每一個http2流都擁有本身的公示的流量窗口,它能夠限制另外一端發送數據。若是你正好知道SSH的工做原理的話,這二者很是類似。
對於每一個流來講,兩端都必須告訴對方本身還有足夠的空間來處理新的數據,而在該窗口被擴大前,另外一端只被容許發送這麼多數據。
只有數據幀會受到流量控制。
咱們來概括一下使用 HTTP2 能帶來的好處:
本文主要是學習筆記和我的理解,首發於 xlaoyu.info, 部分圖片和文字引用網絡,侵刪。
參考文章: