前言:該篇說明:請見 說明 —— 瀏覽器工作原理與實踐 目錄
上一篇文章聊了 HTTP/1.1 的發展史,雖然 HTTP/1.1 已經做了大量的優化,但是依然存在很多性能瓶頸,依然不能滿足我們日益變化的新需求,所以就有了今天要聊的 HTTP/2。
本文依然從需求的層面來談,先分析 HTTP/1.1 存在哪些問題,然后再來分析 HTTP/2 是如何解決這些問題的。
我們知道 HTTP/1.1 為網絡效率做了大量的優化,最核心的有如下三種方式:
- 增加了持久連接;
- 瀏覽器為每個域名最多同時維護 6 個 TPC 持久連接;
- 使用 CDN 的實現域名分片機制。 注:若不理解 域名分片機制,可以參考下圖。
注: 使用cdn 的實現域名分片機制,文中有學員標注【將一個頁面的資源利用多個域名下載,提高 tcp 並發數量。http2 中已不需要】。標注在這做為一個理解參考。
通過這些方式就大大提高了頁面的下載速度,你可以通過下圖來直觀感受下:
HTTP/1.1 的資源下載方式
在該圖中,引入了 CDN,並同時為每個域名維護 6 個連接,這樣就大大減輕了整個資源的下載時間。這里可以簡單計算下:如果使用單個 TCP 的持久連接,下載 100 個資源所花費的時間為 100 * n * RTT;若通過上面的技術,就可以把整個時間縮短為 100 * n * RTT/(6 * CDN 個數)。從這個計算結果來看,頁面加載速度變快了不少。
HTTP/1.1 的主要問題
雖然 HTTP/1.1 采取了很多優化資源加載速度的策略,也取得了一定的效果,但是 HTTP/1.1對帶寬的利用率卻並不理想,這也是 HTTP/1.1 的一個核心問題。
帶寬是指每秒最大能發送或者接收的字節數。我們把每秒能發送的最大字節數稱為上行帶寬,每秒能夠接收的最大字節數稱為下行帶寬。
之所以說 HTTP/1.1 對帶寬的利用率不理想,是因為 HTTP/1.1 很難將帶寬用滿。比如我們常說的 100M 帶寬,實際的下載速度能達到 12.5M/S,而采用 HTTP/1.1 時,也許在加載頁面資源時最大只能使用到 2.5M/S,很難將 12.5M 全部用滿。之所以會出現這個問題,主要是由以下三個原因導致的。
第一個原因,TCP 的慢啟動。
注:由於 TCP 協議向應用層提供不定長的字節流發送方式,使得 TCP 協議先天性就有意願占滿整個帶寬,當許多 TCP 同時去占滿就有可能出現惡性擁塞事件。
為了防止網絡的擁塞現象,TCP 提出了一系列的擁塞控制機制。最初由 V. Jacobson 在1988年的論文中提出的 TCP 的擁塞控制由“慢啟動(Slow start)”和“擁塞避免(Congestion avoidance)”組成,后來 TCP Reno 版本中又針對性的加入了“快速重傳(Fast retransmit)”、“快速恢復(Fast Recovery)”算法,再后來在 TCP NewReno 中又對“快速恢復”算法進行了改進,近些年又出現了選擇性應答(selective acknowledgement,SACK)算法,還有其他方面的大大小小的改進,成為網絡研究的一個熱點。 慢啟動是 TCP 與其他算法結合使用的擁塞控制策略的一部分,以避免發送超過網絡承載能力的數據,避免造成網絡擁塞。
一旦一個 TCP 連接建立后,就進入了發送數據狀態,剛開始 TCP 協議會采用一個非常慢的速度去發送數據,然后慢慢加快發送數據的速度,直到發送數據的速度達到一個理想狀態,我們把這個過程成為慢啟動。
你可以把每個 TCP 發送數據的過程看成是一輛車的啟動過程,當剛進入公路時,會有從 0 到一個穩定速度的提速過程,TCP 的慢啟動就類似於該過程。
慢啟動是 TCP 為了減少網絡擁塞的一種策略,我們是沒有辦法改變的。
而之所以說慢啟動會帶來性能問題,是因為頁面中常用的一些關鍵資源文件本來就不大,如 HTML 文件、CSS 文件和 JS文件,通常這些文件在 TCP 連接建立好之后就要發起請求的,但這個過程是慢啟動,所以耗費的時間比正常的時間要多很多,這樣就推遲了寶貴的首次渲染頁面的時長了。
第二個原因,同時開啟了多條 TCP 連接,那么這些連接會競爭固定的帶寬。
你可以想象下,系統同時建立了多條 TCP 連接,當帶寬充足時,每條連接發送或接受速度會慢慢向上增加;而一旦帶寬不足時,這些 TCP 連接又會減慢發送或接收的速度。比如一個頁面有 200 個文件,使用了 3 個 CDN,那么加載該網頁的時候就需要建立 6*3,也就是 18 個 TCP連接來下載資源;在下載過程中,當發現帶寬不足的時候,各個 TCP 連接就需要動態減慢接收數據的速度。
這樣就會出現一個問題,因為有的 TCP 連接下載的是一些關鍵資源,如 CSS 文件、JS 文件等,而有的 TCP 連接下載的是圖片、視頻等普通的資源文件,但多條 TCP 連接之間又不能協商讓哪些關鍵資源優先下載,這樣就有可能影響那些關鍵資源的下載速度了。 ——資源競爭時無法區分優先級。
第三個原因,HTTP/1.1 對頭阻塞的問題。
通過上一篇文章知道在 HTTP/1.1 中使用持久連接時,雖然能公用一個 TCP 管道,但是在一個管道中同一時刻只能處理一個請求,在當前的請求沒有結束之前,其他的請求只能處於阻塞狀態。這意味着不能隨意在一個管道中發送請求和接收內容。
這是一個很嚴重的問題,因為阻塞請求的因素有很多,並且都是一些不確定性的因素,假如有的請求被阻塞了 5 秒,那么后續排隊的請求都要延遲等待 5 秒,在這個等待的過程中,帶寬、CPU 都被白白浪費了。
在瀏覽器處理生成頁面的過程中,是非常希望能提前接收到數據的,這樣就可以對這些數據做預處理操作,比如提前接收到了圖片,那么就可以提前進行編解碼操作,等到需要使用該圖片的時候,就可以直接給出處理后的數據了,這樣能讓用戶感受到整體速度的提升。
但隊頭阻塞使得這些數據不能並行請求,所以隊頭阻塞是很不利於瀏覽器優化的。
HTTP/2 的多路復用
前面分析了 HTTP/1.1 所存在的一些主要問題:慢啟動和 TCP 連接之間相互競爭帶寬是由於 TCP 本身的機制導致的,而隊頭阻塞是由於 HTTP/1.1 的機制導致的。
那么該如何去解決這些問題呢?
雖然 TCP 有問題,但是我們依然沒有換掉 TCP 的能力,所以就要想辦法去規避 TCP 的慢啟動和 TCP 連接之間的競爭問題。
基於此,HTTP/2 的思路就是一個域名只使用一個 TCP 長連接來傳輸數據,這樣整個頁面資源的下載過程只需要一次慢啟動,同時也避免了多個 TCP 連接競爭帶寬所帶來的問題。另外,就是隊頭阻塞的問題,等待請求完成后才能去請求下一個資源,這種方式無疑是最慢的,所以 HTTP/2 需要實現資源的並行請求,也就是任何時候都可以將請求發送給服務器,而並不需要等待其他請求的完成,然后服務器也可以隨時返回處理好的請求資源給瀏覽器。所以,HTTP/2 的解決方案可以總結為:一個域名只使用一個 TCP 長連接和消除隊頭阻塞問題。可以參考下圖:
HTTP/2 的多路復用
該圖就是 HTTP/2 最核心、最重要且最具顛覆性的多路復用機制。從圖中你會發現每個請求都有一個對應的 ID,如 stream1 表示 index.html 的請求,stream2 表示 foo.css 的請求。這樣在瀏覽器端,就可以隨時將請求發送給服務器了。
服務器端接收到這些請求后,會根據自己的喜好來決定優先返回哪些內容,比如服務器可能早就緩存好了 index.html 和 bar.js 的響應頭信息,那么當接收到請求的時候就可以立即把 index.html 和 bar.js 的響應頭信息返回給瀏覽器,然后再將 index.html 和 bar.js 的響應體數據返回給瀏覽器。之所以可以隨意發送,是因為每份數據都有對應的 ID,瀏覽器接收到之后,會篩選出相同 ID 的內容,將其拼接為完整的 HTTP 響應數據。
HTTP/2 使用了多路復用技術,可以將請求分成一幀一幀的數據去傳輸,這樣帶來了一個額外的好處,就是當收到一個優先級高的請求時,比如接收到 JavaScript 或者 CSS 關鍵資源的請求,服務器可以暫停之前的請求來優先處理關鍵資源的請求。
多路復用的實現
現在知道為了解決 HTTP/1.1 存在的問題,HTTP/2 采用了多路復用機制,那 HTTP/2 是怎么實現多路復用的呢?先看下面這張圖:
HTTP/2 協議棧
從圖中可以看出,HTTP/2 添加了一個二進制分幀層,那就結合圖來分析下 HTTP/2 的請求和接收過程。
注:通過分幀層對相同請求的幀進行編號
- 首先,瀏覽器准備好請求數據,包括了請求行、請求頭等信息,如果是 POST 方法,那么還要有請求體。
- 這些數據經過二進制分幀層處理之后,會被轉換為一個個帶有請求 ID 編號的幀,通過協議棧將這些幀發送給服務器。
- 服務器接收到所有幀之后,會將所有相同 ID 的幀合並為一條完整的請求信息。
- 然后服務器處理該條請求,並將處理的響應行、響應頭和響應體分別發送至二進制分幀層。
- 同樣,二進制分幀層會將這些響應數據轉換為一個個帶有請求 ID 編號的幀,經過協議棧發送給瀏覽器。
- 瀏覽器接收到響應幀之后,會根據 ID 編號將幀的數據提交給對應的請求。
從上面的流程可以看出,通過引入二進制分幀層,就實現了 HTTP 的多路復用技術。
HTTP 是瀏覽器和服務器通信的語言,在這里雖然 HTTP/2 引入了二進制分幀層,不過 HTTP/2 的語義和 HTTP/1.1 依然是一樣的,也就是說它們通信的語言並沒有改變,比如開發者依然可以通過 Accept 請求頭告訴服務器希望接收到什么類型的文件,依然可以使用 Cookie 來保持登錄狀態,依然可以使用 Cache 來緩存本地文件,這些都沒有變,發生改變的只是傳輸方式。這一點對開發者來說尤為重要,這意味着不需要為 HTTP/2 去重建生態,並且 HTTP/2 推廣起來也相對更輕松了。
HTTP/2 其他特性
通過上面的分析,知道了多路復用是 HTTP/2 的最核心功能,它能實現資源的並行傳輸。多路復用技術是建立在二進制分幀層的基礎之上。其實基於二進制分幀層,HTTP/2 還附帶實現了很多其他功能,下面就來簡要了解下。
1. 可以設置請求的優先級
瀏覽器中有些數據是非常重要的,但是在發送請求時,重要的請求可能會晚於那些不怎么重要的請求,如果服務器按照請求的順序來回復數據,那么這個重要的數據就有可能推遲很久才能送達瀏覽器,這對於用戶體驗來說是非常不友好的。
為了解決這個問題,HTTP/2 提供了請求優先級,可以在發送請求時,標上該請求的優先級,這樣服務器接收到請求之后,會優先處理優先級高的請求。
2. 服務器推送
除了設置請求的優先級外,HTTP/2 還可以直接將數據提前推送到瀏覽器。你可以想象這樣一個場景,當用戶請求一個 HTML 頁面之后,服務器知道該 HTML 頁面會引用幾個重要的 JS 文件和 CSS 文件,那么在接收到 HTML 請求之后,附帶將要使用的 CSS 文件和 JavaScript 文件一並發送給瀏覽器,這樣當瀏覽器解析完 HTML 文件之后,就能直接拿到需要的 CSS 文件和 JS 文件,這對首次打開頁面的速度起到了至關重要的作用。
3. 頭部壓縮
無論是 HTTP/1.1 還是 HTTP/2,都有請求頭和響應頭,這是瀏覽器和服務器的通信語言。HTTP/2 對請求頭和響應頭進行了壓縮,你可能覺得一個 HTTP 的頭文件沒有多大,壓不壓縮可能關系不大,但你這樣想一下,在瀏覽器發送請求的時候,基本上都是發送 HTTP 請求頭,很少有請求體的發送,通常情況下頁面也有 100 個左右的資源,如果將這 100 個請求頭的數據壓縮為原來的 20%,那么傳輸效率肯定能得到大幅提升。
總結
首先分析了影響 HTTP/1.1 效率的三個主要因素:TCP 的慢啟動、多條 TCP 連接競爭帶寬和隊頭阻塞。
接下來分析了 HTTP/2 是如何采用多路復用機制來解決這些問題的。多路復用是通過在協議棧中添加二進制分幀層來實現的,有了二進制分幀層還能夠實現請求的優先級、服務器推送、頭部壓縮等特性,從而大大提升了文件傳輸效率。
HTTP/2 協議規范於 2015 年 5 月正式發布,在那之后,該協議已在互聯網和萬維網上得到了廣泛的實現和部署。從目前的情況來看,國內外一些排名靠前的站點基本都實現了 HTTP/2 的部署。使用 HTTP/2 能帶來 20%~60% 的效率提升,至於 20% 還是 60% 要看優化的程度。總之,我們也應該與時俱進,放棄 HTTP/1.1 和其性能優化方法,去“擁抱”HTTP/2。
思考時間
雖然 HTTP/2 解決了 HTTP/1.1 中的隊頭阻塞問題,但是 HTTP/2 依然是基於 TCP 協議的,而 TCP 協議依然存在數據包級別的隊頭阻塞問題,那么你覺得 TCP 的隊頭阻塞是如何影響到 HTTP/2 性能的呢?
記錄
老師好,采用了 HTTP/2 之后,雪碧圖是不是徹底不需要了呢?而且多張圖片變成雪碧圖后,多張圖片大小加和都沒有一張雪碧圖大,那是不是雪碧圖反而讓傳輸更慢了呢?
老師回復:http/2 是沒必要用雪碧圖了。
老師,還是沒有理解 http2 怎么就能解決隊頭阻塞問題呢?http2 還是基於 tcp 連接的,經過二進制分幀層了以后不還是需要以數據包的形式通過 tcp 傳輸嗎?tcp 的數據包隊頭阻塞發生了不還是會影響后面的請求數據包的發送嗎?
老師回復:是不管 http/1 還是 http/2, 最后都需要經過tcp包的形式進行傳輸!而tcp 包也是按照順序的,一個阻塞了,會影響到其他數據包的接收。
第一:http2 解決的是 http1.1 的隊頭阻塞問題的(一個 request 發出后,直到有了response后,才能發出下一個request),http/2 發完一個request就可以馬上發第二個request,而且第二個的response 就有可能先回來。
第二:沒有解決tcp隊頭阻塞的問題(也解決不了,因為tcp在下層)。
有個疑問想請教老師,既然HTTP1.1為了並行下載資源為每個域名提供了6個TCP連接,那這6個TCP連接是並行傳輸數據的么?如果是為什么還會有隊頭阻塞的問題呢?這里沒搞明白,或者其他同學明白的幫忙回答一下唄,感謝感謝
老師回復:這六個TCP連接的請求過程是並行的,這里所提到的對頭阻塞是指發生在每個單獨的tcp連接中,因為一個tcp連接可以用來處理多個http請求。
tcp的隊頭阻塞雖然是缺點,但也從另一個方面保證了數據傳輸的可靠性,前一個沒有完成或者出錯,可以重傳。改用udp后可能會丟幀,不可靠
作者回復: http3中的quic協議也提供了udp的可靠傳輸