合併HTTP請求 vs 並行HTTP請求,到底誰更快?

本文討論的場景基於HTTP 1.1, 不涉及HTTP 2。css

面試時,常常會問候選人一個問題:如何提升網頁性能?html

有些基礎的人都會提到這麼一條:減小/合併HTTP請求。前端

繼續問:瀏覽器不是能夠並行下載資源嗎?將多個資源合併成一個資源,只使用一個HTTP請求下載,難道要比用多個HTTP請求並行下載沒有合併過的多個資源速度更快?git

候選人:……(至今,尚未遇到讓我滿意的回答)github

減小HTTP請求,是雅虎前端性能優化35條軍規的第1條,2006年雅虎提出了這35條軍規,從那之後,就深深地影響到了一批又一批的前端開發者,即便在12年後的今天,影響力依舊不減…..面試

可是,雅虎軍規中還有1條是:拆分資源以最大化利用瀏覽器並行下載的能力。如今問題就來了,減小HTTP請求,但網頁所需的資源並不能減小(不然網頁就再也不是以前的網頁了),因此減小HTTP請求,主要是經過合併資源來實現的,一邊是建議合併資源,一邊是建議拆分資源,顯然是有衝突的地方,那麼到底該怎麼作呢?網上有些文章也討論過這個問題,但大可能是停留在想固然的理論分析上,並且忽略了TCP傳輸機制的影響。今天,老幹部就帶你們一塊兒用實驗+理論,仔細探討下這個問題。瀏覽器

HTTP請求過程

一個HTTP請求的主要過程是:緩存

DNS解析(T1) -> 創建TCP鏈接(T2) -> 發送請求(T3) -> 等待服務器返回首字節(TTFB)(T4) -> 接收數據(T5)。性能優化

以下圖所示,是Chrome Devtools中顯示的一個HTTP請求,顯示了HTTP請求的主要階段,注意,Queueing階段是請求在瀏覽器隊列中的排隊時間,並不計入HTTP請求時間bash

圖1

從這個過程當中,能夠看出若是合併N個HTTP請求爲1個,能夠節省(N-1)* (T1+T2+T3+T4) 的時間。

但實際場景並無這麼理想,上面的分析存在幾個漏洞:

  1. 瀏覽器會緩存DNS信息,所以不是每次請求都須要DNS解析。
  2. HTTP 1.1 keep-alive的特性,使HTTP請求能夠複用已有TCP鏈接,因此並非每一個HTTP請求都須要創建新的TCP鏈接。
  3. 瀏覽器能夠並行發送多個HTTP請求,一樣可能影響到資源的下載時間,而上面的分析顯然只是基於同一時刻只有1個HTTP請求的場景。

實驗論證

咱們來作4組實驗,對比一個HTTP請求加載合併後的資源所需時間,和多個HTTP請求並行加載拆分的資源所需時間。每組實驗所用資源的體積大小有顯著差別。

實驗環境:

服務器:阿里雲ECS 1核 2GB內存 帶寬1M

Web服務器:Nginx (未啓用Gzip)

Chrome v66 隱身模式,禁用緩存

Client 網絡:wifi 帶寬20M

實驗代碼地址

實驗 1

測試文件:large1.css、large2.css … large6.css,每一個文件141K;large-6in1.css,由前面6個css文件合併而成,大小爲846K。parallel-large.html引用large1.css、large2.css … large6.css, combined-large.html引用large-6in1.css,代碼以下:

// parallel-large.html
<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <title>Parallel Large</title>
    <link rel="stylesheet" type="text/css" media="screen" href="large1.css" />
    <link rel="stylesheet" type="text/css" media="screen" href="large2.css" />
    <link rel="stylesheet" type="text/css" media="screen" href="large3.css" />
    <link rel="stylesheet" type="text/css" media="screen" href="large4.css" />
    <link rel="stylesheet" type="text/css" media="screen" href="large5.css" />
    <link rel="stylesheet" type="text/css" media="screen" href="large6.css" />
  </head>

  <body>
    Hello, world!
  </body>

</html>
複製代碼
// combined-large.html
<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <title>Combined Large</title>
    <link rel="stylesheet" type="text/css" media="screen" href="large-6in1.css" />
  </head>

  <body>
    Hello, world!
  </body>

</html>
複製代碼

分別刷新2個頁面各10次,利用Devtools 的Network計算CSS資源加載的平均時間。

注意事項:

  1. large1.css、large2.css … large6.css的加載時間,計算方式爲從第一個資源的HTTP請求發送開始,到6個文件都下載完成的時間,如圖2紅色框內的時間。
  2. 兩個html頁面不能同時加載,不然帶寬爲兩個頁面所共享,會影響測試結果。須要等待一個頁面加載完畢後,再手動刷新加載另一個頁面。
  3. 頁面兩次刷新時間間隔在1分鐘以上 ,以免HTTP 1.1 鏈接複用對實驗的影響。

圖 2

​ 實驗結果以下:

large-6in1.css large1.css、large2.css … large6.css
平均時間(s) 5.52 5.3

咱們再把large1.css、large2.css … large6.css合併爲3個資源large-2in1a.css、large-2in1b.css、large-2in1c.css,每一個資源282K,在combined-large-1.html中引用這3個資源:

// combined-large-1.html
<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <title>Parallel Large 1</title>
    <link rel="stylesheet" type="text/css" media="screen" href="large-2in1a.css" />
    <link rel="stylesheet" type="text/css" media="screen" href="large-2in1b.css" />
    <link rel="stylesheet" type="text/css" media="screen" href="large-2in1c.css" />
  </head>

  <body>
    Hello, world!
  </body>

</html>
複製代碼

測試10次,平均加載時間爲5.20s。

彙總實驗結果以下:

large-6in1.css large1.css、large2.css … large6.css large-2in1a.css、... large-2in1c.css
平均時間(s) 5.52 5.30 5.20

從實驗1結果能夠看出,合併資源和拆分資源對於資源的總加載時間沒有顯著影響。實驗中耗時最少的是拆分紅3個資源的狀況(5.2s),耗時最多的是合併成一個資源的狀況(5.52s),但二者也只不過相差6%。考慮到實驗環境具備必定隨機性,以及實驗重複次數只有10次,這個時間差並不能表徵3種場景有明顯的時間差別性。

實驗 2

繼續增長css文件大小。

測試文件:xlarge1.css、xlarge2.css 、xlarge3.css,每一個文件1.7M;xlarge-3in1.css,由前面3個css文件合併而成,大小爲5.1M。parallel-xlarge.html引用xlarge1.css、xlarge2.css 、xlarge3.css, combined-xlarge.html引用xlarge-3in1.css。

測試過程同上,實驗結果以下:

xlarge-3in1.css xlarge1.css、xlarge2.css、xlarge3.css
平均時間(s) 37.72 36.88

這組實驗的時間差只有2%,更小了,因此更沒法說明合併資源和拆分資源的總加載時間有明顯差別性。

實際上,理想狀況下,隨着資源體積變大,兩種資源加載方式所需時間將趨於相同。

從理論上解釋,由於HTTP的傳輸通道是基於TCP鏈接的,而TCP鏈接具備慢啓動的特性,剛開始時並無充分利用網絡帶寬,通過慢啓動過程後,逐漸佔滿可利用的帶寬。對於大資源而言,帶寬老是會被充分利用的,因此帶寬是瓶頸,即便使用更多的TCP鏈接,也不能帶來速度的提高。資源越大,慢啓動所佔總的下載時間的比例就越小,絕大部分時間,帶寬都是被充分利用的,總數據量相同(拆分資源致使的額外Header在這種狀況下徹底能夠忽略不計),帶寬相同,傳輸時間固然也相同。

實驗 3

減少css文件大小。

測試文件:medium1.css、medium2.css … medium6.css,每一個文件9.4K;medium-6in1.css,由前面6個css文件合併而成,大小爲56.4K。parallel-medium.html引用medium1.css、medium2.css … medium6.css, combined-medium.html 引用 medium-6in1.css。

實驗結果以下:

medium-6in1.css medium1.css、medium2.css … medium6.css
平均時間(ms) 34.87 46.24

注意單位變成ms

實驗3的時間差是33%,雖然數值上只差12ms。先很少分析,繼續看實驗4。

實驗 4

繼續減少css文件大小,至幾十字節級別。

測試文件:small1.css、small2.css … small6.css,每一個文件28B;small-6in1.css,由前面6個css文件合併而成,大小爲173B。parallel-medium.html引用small1.css、small2.css … small6.css, combined-medium.html 引用 small-6in1.css。

實驗結果以下:

small-6in1.css small1.css、small2.css … small6.css
平均時間(ms) 20.33 35

實驗4的時間差是72%。

根據實驗3和實驗4,發現當資源體積很小時,合併資源和拆分資源的加載時間有了比較明顯的差別。圖3和圖4是實驗4中的某次測試結果的截圖,當資源體積很小時,數據的下載時間(圖中水平柱的藍色部分所示)佔總時間的比例就很小了,這時候影響資源加載時間的關鍵就是DNS解析(T1) 、 TCP鏈接創建(T2) 、發送請求(T3) 和等待服務器返回首字節(TTFB)(T4) 。但同時創建多個HTTP鏈接自己就存在額外的資源消耗,每一個HTTP的DNS查詢時間、TCP鏈接的創建時間等也存在必定的隨機性,這就致使併發請求資源時,出現某個HTTP耗時明顯增長的可能性變大。如圖3所示,small1.css加載時間最短(16ms),small5.css加載時間最長(32ms),二者相差了1倍,但計算時間是以全部資源都加載完成爲準,這種狀況下,同時使用多個HTTP請求就會致使更大的時間不均勻性和不肯定性,表現結果就是每每要比使用一個HTTP請求加載合併後的資源慢。

圖3

圖4

更復雜的狀況

對於小文件必定是合併資源更快嗎?

其實未必,在一些狀況下,合併小文件反而有可能明顯增長資源加載時間。

再說些理論的東西。爲了提升傳輸效率,TCP通道上,並非發送方每發送一個數據包,都要等到收到接收方的確認應答(ACK)後,再發送下一個報文。TCP引入了」窗口「的概念,窗口大小指無需等待確認應答而能夠繼續發送數據的最大值,例如窗口大小是4個MSS(Maximum Segment Size,TCP數據包每次可以傳輸的最大數據分段),表示當前能夠連續發送4個報文段,而不須要等待接收方的確認信號,也就是說,在1次網絡往返(round-trip)中完成了4個報文段的傳輸。以下圖所示(MSS爲1,窗口大小爲4個MSS),1 - 4000 數據是連續發送的,並無等待確認應答,一樣的,4001 - 8000也是連續發送的。請注意,這只是理想狀況下的示意圖,實際狀況要比這裏更復雜。

圖5

在慢啓動階段,TCP維護一個擁塞窗口變量,這個階段窗口的大小就等於擁塞窗口,慢啓動階段,隨着每次網絡往返,擁塞窗口的大小就會翻一倍,例如,假設擁塞窗口的初始大小爲1,擁塞窗口的大小變化爲:1,2,4,8……。以下圖所示。

圖6

實際網絡中,擁塞窗口的初始值通常是10,因此擁塞窗口的大小變化爲:10,20,40 ... ,MSS的值取決於網絡拓撲結構和硬件設備,以太網中MSS值通常是1460字節,按每一個報文段傳輸的數據大小都等於MSS計算(實際狀況能夠小於MSS值),通過第1次網絡往返後,傳輸的最大數據爲14.6K,第2次後,爲(10+20) * 1.46 = 43.8K, 第3次後,爲(10+20+40) * 1.46 = 102.2K。

根據上面的理論介紹,實驗4中,不論是合併資源,仍是拆分資源,都是在1次網絡往返中傳輸完成。但實驗3,拆分後的資源大小爲9.4K,能夠在1次網絡往返中傳輸完成,而合併後的資源大小爲56.4K,須要3次網絡往返才能傳輸完成,若是網絡延時很大(例如1s),帶寬又不是瓶頸,多了兩次網絡往返將致使耗時增長1s,這時候合併資源就可能得不償失了。實驗3並無產生這個結果的緣由是,實驗中網絡延時是10ms左右,因爲數值過小而沒有對結果產生明顯影響。

總結

對於大資源,是否合併對於加載時間沒有明顯影響,但拆分資源能夠更好的利用瀏覽器緩存,不會由於某個資源的更新致使全部資源緩存失效,而資源合併後,任一資源的更新都會致使總體資源的緩存失效。另外還能夠利用域名分片技術,將資源拆分部署到不一樣域名下,既能夠分散服務器的壓力,又能夠下降網絡抖動帶來的影響。

對於小資源,合併資源每每具備更快的加載速度,但在網絡帶寬情況良好的狀況下,由於提高的時間單位以ms計量,收益能夠忽略。若是網絡延遲很大,服務器響應速度又慢,則能夠帶來必定收益,但在高延遲的網絡場景下,又要注意合併資源後可能帶來網絡往返次數的增長,進而影響到加載時間。

其實,看到這裏,是合是分已經不重要了,重要的是咱們要知道合分背後的原理是什麼,和業務場景是怎樣的。


個人新書《React進階之路》已上市,對React感興趣的同窗不妨去了解下。 購買地址: 噹噹 京東


歡迎關注個人公衆號:老幹部的大前端

相關文章
相關標籤/搜索