HTTP/2 特性概覽


楔子

下面我們來介紹一下 HTTP2 協議,不過在介紹之前我們需要先了解為什么要有 HTTP2 協議。現在的互聯網基本上都被 HTTP/1.1 統治了,但 HTTP/1.1 有兩個主要的缺點:安全不足、性能不高。雖然通過引入 SSL/TLS 在安全上達到了極致(HTTPS),但在性能的提升方面確實乏善可陳。只優化了握手加密的環節,對於整體的數據傳輸沒有提出更好的改進方案,還只能依賴於長連接這種落后的技術。所以,在 HTTPS 逐漸成熟之后,就向着性能方面開始發力,走出了另一條進化的道路。

了解一下 HTTP 的發展歷史,我們知道是 Google 率先發明了 SPDY 協議,並應用於自家的瀏覽器 Chrome,打響了 HTTP 性能優化的第一槍。隨后互聯網標准化組織(IETF) 以 SPDY 協議為基礎,綜合其他多方的意見,終於推出了 HTTP/1 的繼任者,也就是今天的主角 HTTP/2,在性能方面有了一個大的飛躍。

你一定很想知道,為什么 HTTP/2 不像之前的 1.0、1.1 那樣叫 2.0 呢?這個也是很多初次接觸 HTTP/2 的人問的最多的一個問題,對此 HTTP/2 工作組特別給出了解釋。

他們認為以前的 1.0、1.1 造成了很多的混亂和誤解,讓人在實際的使用中難以區分差異,所以就決定 HTTP 協議不再使用小版本號(minor version),只使用大版本號(major version),從今往后 HTTP 協議不會出現 HTTP/2.0、2.1,只會有 HTTP/2、HTTP/3、……。這樣就可以明確無誤地辨別出協議版本的躍進程度,讓協議在一段較長的時期內保持穩定,每當發布新版本的 HTTP 協議都會有本質的不同,絕不會有零敲碎打的小改良。

因為 HTTPS 已經在安全方面已經做的非常好了,所以 HTTP/2 的唯一目標就是改進性能。但它不僅背負着眾多的期待,同時還背負着 HTTP/1 龐大的歷史包袱,所以協議的修改必須小心謹慎,兼容性是首要考慮的目標,否則就會破壞互聯網上無數現有的資產,這方面 TLS 已經有了先例(為了兼容 TLS1.2 不得不進行偽裝)。

所以接下來我們就看看 HTTP/2 在性能提升方面究竟是怎么做的?為什么 HTTP/1.1 的性能不行,HTTP/2 對 HTTP/1.1 做了哪些方面的修改?

HTTP/1.1 發展中遇到的問題

首先我們看看 HTTP/1.1 帶來了哪些進步:

  • 從幾 KB 大小的消息到幾 MB 大小的消息
  • 每個頁面小於 10 個資源到每個頁面 100 多個資源
  • 從文本為主的內容到富媒體(如圖片、視頻、音頻)為主的內容,HTTP 被稱為超文本傳輸協議,這個超文本不僅僅可以傳輸文本
  • 對頁面內容實時性高要求的內容越來越多

我們以打開 B 站為例:

我們看到總共發送了 113 個請求,傳輸了 6.7MB 的資源,有文本、有圖片、有視頻,傳輸內容的大小、種類豐富多樣。並且對實時性要求高的應用也越來越多,比如視頻直播等等。

以上是 HTTP/1.1 所帶來的進步,但它還是有缺點的,就是性能不夠好,下面舉例說明。

隊頭阻塞

早期 HTTP/1.0 性能上的一個很大的問題,那就是每發起一個請求,都要新建一次 TCP 連接(三次握手),而且是串行請求,做了無畏的 TCP 連接建立和斷開,增加了通信開銷。為了解決上述 TCP 連接問題,HTTP/1.1 提出了長連接的通信方式,也叫持久連接,這種方式的好處在於減少了 TCP 連接的重復建立和斷開所造成的額外開銷,減輕了服務器端的負載。持久連接的特點是,只要任意一端沒有明確提出斷開連接,則保持 TCP 連接狀態。

由於 HTTP/1.1 采用了長連接的方式,這使得管道(pipeline)網絡傳輸成為了可能。在同一個 TCP 連接里面,客戶端可以發起多個請求,只要第一個請求發出去了,不必等其回來,就可以發第二個請求出去,可以減少整體的響應時間。舉例來說,客戶端需要請求兩個資源,以前的做法是,在同一個 TCP 連接里面,先發送 A 請求,然后等待服務器做出回應,收到后再發出 B 請求。這顯然比較不夠智能,而管道機制則是允許瀏覽器同時發出 A 請求和 B 請求。有人覺得這不是好事嗎?沒錯,只不過還是不夠完美,因為響應的處理順序還是和請求的順序保持一致的,先處理 A 請求的響應,然后再處理 B 請求的響應。要是 A 請求的響應特別慢,那么即使 B 請求的響應回來了,客戶端也無法處理。

所以「請求 - 響應」的模式加劇了 HTTP 的性能問題,因為當順序發送的請求序列中的一個請求因為某種原因被阻塞時,在后面排隊的所有請求也一同被阻塞了,會導致客戶端一直請求不到數據,這也就是「隊頭阻塞」,好比上班的路上塞車。

因此 HTTP/1.1 的性能雖然不算差,但還不能完全適應現在的互聯網,仍有很大的提升空間,后續的 HTTP/2 和 HTTP/3 都是在優化 HTTP/1.1 的性能。

高延遲問題

高延遲帶來頁面加載速度的降低,並且隨着帶寬增加,延遲並沒有顯著下降。

現在家庭的網絡帶寬越來越大,但是延遲並沒有顯著的降低,從左邊的柱狀圖可以看出。原因就是 HTTP/1.1 處理響應的順序和請求順序是一致的,只有第一個響應處理完畢之后才能處理第二個響應。就類似於打卡,第一個人因為某些原因怎么也打不上卡,但他如果打不上,后面的人也沒法打。總的來說,對於 HTTP/1.1 而言,沒有輕重緩急的優先級,只有先后入隊的順序。而右邊的柱狀圖則反應了,延遲越低,頁面加載用的耗時越少。

無狀態特性帶來的巨大 HTTP 頭部

我們知道無論是請求報文還是響應報文,都是由 Header + Body 組成,因為 HTTP/1.1 是無狀態的,所以就要求 Header 攜帶很多的頭字段,有時多達幾百字節甚至上千字節,但 Body 卻經常只有幾十字節、甚至幾字節(比如 GET 請求、204/301/304 響應),等於說變成了一個不折不扣的"大頭兒子"。更要命的是,成千上萬的請求響應報文里有很多字段值都是重復的,非常浪費,長尾效應導致大量帶寬消耗在了這些冗余度極高的數據上。

以上就是 HTTP/1.1 面臨的一些問題, 盡管也通過一些額外的手段來曲線救國,但仍然不能很好的解決問題。所以業界就開始「改革」了,發明了 HTTP/2 協議,這個協議很好地解決了 HTTP/1.1 所面臨的問題。

HTTP/2 特性概述

首先 HTTP/2 它是安全的,和 HTTPS 一樣,也是基於 SSL/TLS 之上,我們來看一張圖。

圖中的 HTTP/3 我們之后再說,我們看到 HTTP/2 在結構上和 HTTPS 是類似的,因為要保持向上兼容。而 HTTP/2 的做法是將 HTTP 分解成了「語義」和「語法」兩個部分,語義層不做改動,與 HTTP/1 完全一致(即 RFC7231)。比如請求方法、URI、狀態碼、頭字段等概念都保留不變,這樣就消除了再學習的成本,基於 HTTP 的上層應用也不需要做任何修改,可以無縫轉換到 HTTP/2,如果代理服務器不支持 HTTP/2,那么會自動降級到 HTTP/1.1(HTTPS)。特別要說的是,與 HTTPS 不同,HTTP/2 沒有在 URI 里引入新的協議名,仍然用 http 表示明文協議,用 https 表示加密協議。這是一個非常了不起的決定,可以讓瀏覽器或者服務器去自動升級或降級協議,免去了選擇的麻煩,讓用戶在上網的時候都意識不到協議的切換,實現平滑過渡。

在語義保持穩定之后,HTTP/2 在語法層做了天翻地覆的改造,完全變更了 HTTP 報文的傳輸格式。

頭部壓縮

首先,HTTP/2 對報文的頭部做了一個大手術,我們知道 HTTP/1 里可以用頭字段 Content-Encoding 指定 Body 的編碼方式,比如用 gzip 壓縮來節約帶寬,但報文的另一個組成部分「Header」卻被無視了,沒有針對它的優化手段。

所以,HTTP/2 把頭部壓縮作為性能改進的一個重點,優化的方式你也肯定能想到,還是壓縮。不過 HTTP/2 並沒有使用傳統的壓縮算法,而是開發了專門的 HPACK 算法,在客戶端和服務器兩端建立字典,用索引號表示重復的字符串,還釆用哈夫曼編碼來壓縮整數和字符串,可以達到 50%~90% 的高壓縮率。

二進制格式

你可能已經很習慣於 HTTP/1 里純文本形式的報文了,它的優點是一目了然,用最簡單的工具就可以開發調試,非常方便。但 HTTP/2 在這方面沒有妥協,決定改變延續了十多年的現狀,不再使用肉眼可見的 ASCII 碼,而是向下層的 TCP/IP 協議靠攏,全面采用二進制格式。這樣雖然對人不友好,但卻大大方便了計算機的解析。原來使用純文本的時候容易出現多義性,比如大小寫、空白字符、回車換行、多字少字等等,程序在處理時必須用復雜的狀態機,效率低,還麻煩。

而二進制里只有 0 和 1,可以嚴格規定字段大小、順序、標志位等格式,對就是對,錯就是錯,解析起來沒有歧義,實現簡單,而且體積小、速度快,做到內部提效。因此以二進制格式為基礎,HTTP/2 就開始了大刀闊斧的改革。它把 TCP 協議的部分特性挪到了應用層,把原來的 Header+Body 的消息打散為數個小片的二進制幀(Frame),其中 HEADERS 幀存放頭數據、DATA 幀存放實體數據。

這種做法有點像是 Chunked 分塊編碼的方式,也是化整為零的思路,但 HTTP/2 數據分幀后 Header+Body 的報文結構就完全消失了,協議看到的只是一個個的碎片。

虛擬的流

在連接的層面上看,多個消息就是一堆亂序收發的幀,比如可以先接收消息 1 的幀、再接收消息 2 的幀,然后再接收消息 1 的幀,不要求順序(比如先將消息 1 的幀全部接收完畢之后才能接收消息 2 的幀),否則和 HTTP/1.1 就沒有區別了。

那么問題來了,這些消息碎片(二進制幀)到達目的地后應該怎么組裝起來呢?HTTP/2 為此定義了一個流(Stream)的概念,它是虛擬的,可以想象成二進制幀的雙向傳輸序列。而隸屬同一個消息的所有幀都有一個相同的流 ID,不同消息的流 ID 則不同。后續在對幀進行組裝的時候,根據這個 ID 來將屬於同一個消息的幀組裝在一起,得到類似 HTTP/1.1 中的報文,也就是傳輸時無序,接收時組裝。所以,在 HTTP/2 中的多個請求 / 響應之間沒有了順序關系,不需要排隊等待,也就不會再出現隊頭阻塞問題,降低了延遲,大幅度提高了連接的利用率。

說白了在 HTTP/2 中就是將二進制的報文數據切分成多個幀進行傳輸,而不同消息的幀可以混在一起發送(傳輸時無序),而在接收時再根據流 ID 將屬於同一個消息的幀組裝在一起(接收時組裝)。

因為流是虛擬的,實際上並不存在,所以 HTTP/2 就可以在一個 TCP 連接上同時發送多個碎片化的消息,這就是常說的多路復用( Multiplexing),多個往返通信都復用一個連接來處理。

但需要注意的是,我們說傳輸時無序指的是多個消息之間的幀可以無序,但同一個消息的幀則必須是有序的,比如每條消息必須先傳輸其 HEADER 幀、再傳輸 DATA 幀,否則消息不就亂掉了嗎?然后組裝的時候,直接按照順序進行組裝即可。

另外為了更好地利用連接,加大吞吐量,HTTP/2 還添加了一些控制幀來管理虛擬的流,實現了優先級和流量控制,這些特性也和 TCP 協議非常相似。HTTP/2 還在一定程度上改變了傳統的 請求 - 響應 工作模式,服務器不再是完全被動地響應請求,也可以新建流主動向客戶端發送消息。比如,在瀏覽器剛請求 HTML 的時候就提前把可能會用到的 JS、CSS 文件發給客戶端,減少等待的延遲,這被稱為服務器推送(Server Push,也叫 Cache Push)。

強化安全

出於兼容的考慮,HTTP/2 延續了 HTTP/1 的明文特點,可以像以前一樣使用明文傳輸數據,不強制使用加密通信,只不過傳輸內容變成了二進制(不需要解密)。並且為了區分加密和明文這兩個不同的版本,HTTP/2 協議定義了兩個字符串標識符:h2 表示加密的 HTTP/2,也就是在 TCP 三次握手之后,還要進行 TLS 握手,保證后續傳輸的數據是加密的;h2c 表示明文的 HTTP/2,在 TCP 三次握手之后直接就進行數據傳輸了,雖然傳輸內容是二進制,但沒有加密的過程,而 h2c 中多出的那個字母 c 的意思就是 clear text。

但由於 HTTPS 已經是大勢所趨,而且主流的瀏覽器 Chrome、Firefox 等都公開宣布只支持加密的 HTTP/2,所以事實上的 HTTP/2 是加密的。也就是說,互聯網上通常所能見到的 HTTP/2 都是使用 https 協議名,跑在 TLS 上面。

另外,由於在 HTTP/2 標准制定的時候(2015 年)已經發現了很多 SSL/TLS 的弱點,而新的 TLS1.3 還未發布,所以加密版本的 HTTP/2 在安全方面做了強化,要求下層的通信協議必須是 TLS1.2 以上,還要支持前向安全和 SNI,並且把幾百個弱密碼套件列入了黑名單,比如 DES、RC4、CBC、SHA-1 都不能在 HTTP/2 里使用,相當於底層用的是 TLS1.25。

所以 HTTP/2 是建立在 HPack、Stream、TLS1.2 基礎之上的,比 HTTP/1、HTTPS 復雜了一些。但它的語義還是簡單的 HTTP/1.1,所以之前學習的知識不會過時,仍然可以排上用場。

補充

HTTP/2 的前身 SPDY 在壓縮頭部時使用了 gzip,但發現會受到 CRIME 攻擊,所以開發了專用的壓縮算法 HPACK。

HTTP/2 的流可以實現 HTTP/1.1 里的管道功能,而且綜合性能更好,所以管道在 HTTP/2 里就被廢棄了。

如果你寫過 Linux 程序,用過 epoll,那么應該知道 epoll 也是一種多路復用,只不過它是 I/O Multiplexing。


免責聲明!

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



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