深刻研究:HTTP2 的真正性能到底如何

圖片描述

1、研究目的

HTTP2的概念提出已經有至關長一段時間了,而網上關於關於http2的文章也一搜一大把。可是從搜索的結果來看,現有的文章可能是偏向於對http2的介紹,鮮有真正從數據上具體分析的。這篇文章正是出於填補這塊空缺內容的目的,經過一系列的實驗以及數據分析,對http2的性能進行深刻研究。固然,因爲本人技術有限,實驗所使用的方法確定會有不足之處,若是各位看官有發現問題,還請向我提出,我必定會努力修改完善實驗的方法的!css

2、基礎知識

關於HTTP2的基礎知識,能夠參考下列幾篇文章,在這裏就不進行贅述了。html

經過學習相關資料,咱們已經對HTTP2有了一個大體的認識,接下來將經過設計一個模型,對HTTP2的性能進行實驗測試。web

3、實驗設計

設置實驗組:搭建一個HTTP2(SPDY)服務器,可以以HTTP2的方式響應請求。同時,響應的內容大小,響應的延遲時間都可自定義。express

設置對照組:搭建一個HTTP1.x服務器,以HTTP1.x的方式響應請求,其可自定義內容同實驗組。另外爲了減小偏差,HTTP1.x服務器使用https協議。

測試過程:客戶端經過設置響應的內容大小、請求資源的數量、延遲時間、上下行帶寬等參數,分別對實驗組服務器和對照組服務器發起請求,統計響應完成所需時間。

因爲nginx切換成http2須要升級nginx版本以及取得https證書,且在服務器端的多種自定義設置所涉及的操做環節相對複雜,綜合考慮之下放棄使用nginx做爲實驗用服務器的方案,而是採用了NodeJS方案。在實驗的初始階段,使用了原生的NodeJS搭配node-http2模塊進行服務器搭建,後來改成了使用express框架搭配node-spdy模塊搭建。緣由是,原生NodeJS對於複雜請求的處理很是複雜,express框架對請求、響應等已經作了一系列的優化,能夠有效減小人爲的偏差。另外node-http2模塊沒法與express框架兼容,同時它的性能較之node-spdy模塊也更低(General performance, node-spdy vs node-http2 #98),而node-spdy模塊的功能與node-http2模塊基本一致。

一、服務器搭建

實驗組和對照組的服務器邏輯徹底一致,關鍵代碼以下:

app.get('/option/?', (req, res) => {
    allow(res)
    let size = req.query['size']
    let delay = req.query['delay']
    let buf = new Buffer(size * 1024 * 1024)
    setTimeout(() => {
        res.send(buf.toString('utf8'))
    }, delay)
})

其邏輯是,根據從客戶端傳入的參數,動態設置響應資源的大小和延遲時間。

二、客戶端搭建

客戶端可動態設置請求的次數、資源的數目、資源的大小和服務器延遲時間。同時搭配Chrome的開發者工具,能夠人爲模擬不一樣網絡環境。在資源請求響應結束後,會自動計算總耗時時間。關鍵代碼以下:

for (let i = 0; i < reqNum; i++) {
    $.get(url, function (data) {
        imageLoadTime(output, pageStart)
    })
}

客戶端經過循環對資源進行屢次請求,其數量可設置。每一次循環都會經過imageLoadTime更新時間,以實現時間統計的功能。

圖片描述

三、實驗項目

a. http2性能研究

經過研究章節二的文章內容,能夠把http2的性能影響因素歸結於「延遲」和「請求數目」。本實驗增長了「資源體積」和「網絡環境」做爲影響因素,下面將會針對這四項進行詳細的測試實驗。其中每一次實驗都會重複10次,取平均值後做記錄。

b. 服務端推送研究

http2還有一項很是特別的功能——服務端推送。服務端推送容許服務器主動向客戶端推送資源。本實驗也會針對這個功能展開研究,主要研究服務端推送的使用方法及其對性能的影響。

4、HTTP2 性能數據統計

一、延遲因素對性能的影響

條件/實驗次數 1 2 3 4 5
延遲時間(ms) 0 10 20 30 40
資源數目(個) 100 100 100 100 100
資源大小(MB) 0.1 0.1 0.1 0.1 0.1
統計時間(s)http1.x 0.38 0.51 0.62 0.78 0.94
統計時間(s)http2 0.48 0.51 0.49 0.48 0.50

延遲因素對性能的影響

二、請求數目對性能的影響

經過上一個實驗,能夠知道在延遲爲10ms的時候,http1.x和http2的時間統計相近,故本次實驗延遲時間設置爲10ms。

條件/實驗次數 1 2 3 4 5
延遲時間(ms) 10 10 10 10 10
資源數目(個) 6 30 150 750 3750
資源大小(MB) 0.1 0.1 0.1 0.1 0.1
統計時間(s)http1.x 0.04 0.16 0.63 3.03 20.72
統計時間(s)http2 0.04 0.16 0.71 3.28 19.34

請求數目對性能的影響

增長延遲時間,重複實驗:

條件/實驗次數 6 7 8 9 10
延遲時間(ms) 30 30 30 30 30
資源數目(個) 6 30 150 750 3750
資源大小(MB) 0.1 0.1 0.1 0.1 0.1
統計時間(s)http1.x 0.07 0.24 1.32 5.63 28.82
統計時間(s)http2 0.07 0.17 0.78 3.81 18.78

請求數目對性能的影響

三、資源體積對性能的影響

經過上兩個實驗,能夠知道在延遲爲10ms,資源數目爲30個的時候,http1.x和http2的時間統計相近,故本次實驗延遲時間設置爲10ms,資源數目30個。

條件/實驗次數 1 2 3 4 5
延遲時間(ms) 10 10 10 10 10
資源數目(個) 30 30 30 30 30
資源大小(MB) 0.2 0.4 0.6 0.8 1.0
統計時間(s)http1.x 0.21 0.37 0.59 0.68 0.68
統計時間(s)http2 0.25 0.45 0.61 0.83 0.73
條件/實驗次數 6 7 8 9 10
延遲時間(ms) 10 10 10 10 10
資源數目(個) 30 30 30 30 30
資源大小(MB) 1.2 1.4 1.6 1.8 2.0
統計時間(s)http1.x 0.78 0.94 1.02 1.07 1.13
統計時間(s)http2 0.92 0.86 1.08 1.26 1.33

圖片描述

四、網絡環境對性能的影響

經過上兩個實驗,能夠知道在延遲爲10ms,資源數目爲30個的時候,http1.x和http2的時間統計相近,故本次實驗延遲時間設置爲10ms,資源數目30個。

條件/網絡條件 Regular 2G Good 2G Regular 3G Good 3G Regular 4G Wifi
延遲時間(ms) 10 10 10 10 10 10
資源數目(個) 30 30 30 30 30 30
資源大小(MB) 0.1 0.1 0.1 0.1 0.1 0.1
統計時間(s)http1.x 222.66 116.64 67.37 32.82 11.89 0.87
統計時間(s)http2 138.06 71.02 40.77 20.82 7.70 0.94

圖片描述

5、HTTP2 服務端推送實驗

本實驗主要針對網絡環境對服務端推送速度的影響進行研究。在本實驗中,所請求/推送的資源都是一個體積爲290Kb的JS文件。每個網絡環境下都會重複十次實驗,取平均值後填入表格。

條件/網絡條件 Regular 2G Good 2G Regular 3G Good 3G Regular 4G Wifi
客戶端請求總耗時(s) 9.59 5.30 3.21 1.57 0.63 0.12
服務端推送總耗時(s) 18.83 10.46 6.31 3.09 1.19 0.20
資源加載速度-客戶端請求(s) 9.24 5.13 3.08 1.50 0.56 0.08
資源加載速度-服務端推送(s) 9.28 5.16 3.09 1.51 0.57 0.08
條件/網絡條件 No Throttling
客戶端請求總耗時(ms) 56
服務端推送總耗時(ms) 18
資源加載速度-客戶端請求(s) 15.03
資源加載速度-服務端推送(s) 2.80

從上述表格能夠發現一個很是奇怪的現象,在開啓了網絡節流之後(包括Wifi選項),服務端推送的速度都遠遠比不上普通的客戶端請求,可是在關閉了網絡節流後,服務端推送的速度優點很是明顯。在網絡節流的Wifi選項中,下載速度爲30M/s,上傳速度爲15M/s。而測試所用網絡的實際下載速度卻只有542K/s,上傳速度只有142K/s,遠遠達不到網絡節流Wifi選項的速度。爲了分析這個緣由,咱們須要理解「服務端推送」的原理,以及推送過來的資源的存放位置在哪裏。

普通的客戶端請求過程以下圖:

圖片描述

服務端推送的過程以下圖:

圖片描述

從上述原理圖能夠知道,服務端推送能把客戶端所須要的資源伴隨着index.html一塊兒發送到客戶端,省去了客戶端重複請求的步驟。正由於沒有發起請求,創建鏈接等操做,因此靜態資源經過服務端推送的方式能夠極大地提高速度。可是這裏又有一個問題,這些被推送的資源又是存放在哪裏呢?參考了這篇文章Issue 5: HTTP/2 Push之後,終於找到了緣由。咱們能夠把服務端推送過程的原理圖深刻一下:

圖片描述

服務端推送過來的資源,會統一放在一個網絡與http緩存之間的一個地方,在這裏能夠理解爲「本地」。當客戶端把index.html解析完之後,會向本地請求這個資源。因爲資源已經本地化,因此這個請求的速度很是快,這也是服務端推送性能優點的體現之一。固然,這個已經本地化的資源會返回200狀態碼,而非相似localStorage的304或者200 (from cache)狀態碼。Chrome的網絡節流工具,會在任何「網絡請求」之間加入節流,因爲服務端推送活來的靜態資源也是返回200狀態碼,因此Chrome會把它看成網絡請求來處理,因而致使了上述實驗所看到的問題。

6、研究結論

經過上述一系列的實驗,咱們能夠知道http2的性能優點集中體如今「多路複用」和「服務端推送」上。對於請求數目較少(約小於30個)的狀況下,http1.x和http2的性能差別不大,在請求數目較多且延遲大於30ms的狀況下,才能體現http2的性能優點。對於網絡情況較差的環境,http2的性能也高於http1.x。與此同時,若是把靜態資源都經過服務端推送的方式來處理,加載速度會獲得更加巨大的提高。

在實際的應用中,因爲http2多路複用的優點,前端應用團隊無須採起把多個文件合併成一個,生成雪碧圖之類的方法減小網絡請求。除此以外,http2對於前端開發的影響並不大。

服務端升級http2,若是是使用NodeJS方案,只須要把node-http模塊升級爲node-spdy模塊,並加入證書便可。nginx方案的話能夠參考這篇文章:Open Source NGINX 1.9.5 Released with HTTP/2 Support

若要使用服務端推送,則在服務端須要對響應的邏輯進行擴展,這個須要視狀況具體分析實施。

7、後記

紙上得來終覺淺,絕知此事要躬行。若是不是真正的設計實驗、進行實驗,我可能根本不會知道原來http2也有坑,原來使用Chrome作調試的時候也有須要注意的地方。

但願這篇文章可以對研究http2的同窗有些許幫助吧,如文章開頭所說,若是你發現個人實驗設計有任何問題,或者你想到了更好的實驗方式,也歡迎向我提出,我必定會認真研讀你的建議的!


下面附送實驗所需源碼:
一、客戶端頁面

<!-- http1_vs_http2.html -->

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>http1 vs http2</title>
   <script src="//cdn.bootcss.com/jquery/1.9.1/jquery.min.js"></script>
   <style>
       .box {
           float: left;
           width: 200px;
           margin-right: 100px;
           margin-bottom: 50px;
           padding: 20px;
           border: 4px solid pink;
           font-family: Microsoft Yahei;
       }
       .box h2 {
           margin: 5px 0;
       }
       .box .done {
           color: pink;
           font-weight: bold;
           font-size: 18px;
       }
       .box button {
           padding: 10px;
           display: block;
           margin: 10px 0;
       }
   </style>
</head>
<body>
   <div class="box">
       <h2>Http1.x</h2>
       <p>Time: <span id="output-http1"></span></p>
       <p class="done done-1">× Unfinished...</p>
       <button class="btn-1">Get Response</button>
   </div>

   <div class="box">
       <h2>Http2</h2>
       <p>Time: <span id="output-http2"></span></p>
       <p class="done done-1">× Unfinished...</p>
       <button class="btn-2">Get Response</button>
   </div>

   <div class="box">
       <h2>Options</h2>
       <p>Request Num: <input type="text" id="req-num"></p>
       <p>Request Size (Mb): <input type="text" id="req-size"></p>
       <p>Request Delay (ms): <input type="text" id="req-delay"></p>
   </div>

   <script>
       function imageLoadTime(id, pageStart) {
         let lapsed = Date.now() - pageStart;
         document.getElementById(id).innerHTML = ((lapsed) / 1000).toFixed(2) + 's'
       }
       
       let boxes = document.querySelectorAll('.box')
       let doneTip = document.querySelectorAll('.done')
       let reqNumInput = document.querySelector('#req-num')
       let reqSizeInput = document.querySelector('#req-size')
       let reqDelayInput = document.querySelector('#req-delay')

       let reqNum = 100
       let reqSize = 0.1
       let reqDelay = 300

       reqNumInput.value = reqNum
       reqSizeInput.value = reqSize
       reqDelayInput.value = reqDelay

       reqNumInput.onblur = function () {
           reqNum = reqNumInput.value
       }

       reqSizeInput.onblur = function () {
           reqSize = reqSizeInput.value
       }

       reqDelayInput.onblur = function () {
           reqDelay = reqDelayInput.value
       }

       function clickEvents(index, url, output, server) {
           doneTip[index].innerHTML = '× Unfinished...'
           doneTip[index].style.color = 'pink'
           boxes[index].style.borderColor = 'pink'
           let pageStart = Date.now()
           for (let i = 0; i < reqNum; i++) {
               $.get(url, function (data) {
                   console.log(server + ' data')
                   imageLoadTime(output, pageStart)
                   if (i === reqNum - 1) {
                       doneTip[index].innerHTML = '√ Finished!'
                       doneTip[index].style.color = 'lightgreen'
                       boxes[index].style.borderColor = 'lightgreen'
                   }
               })
           }
       }

       document.querySelector('.btn-1').onclick = function () {
           clickEvents(0, 'https://localhost:1001/option?size=' + reqSize + '&delay=' + reqDelay, 'output-http1', 'http1.x')
       }

       document.querySelector('.btn-2').onclick = function () {
           clickEvents(1, 'https://localhost:1002/option?size=' + reqSize + '&delay=' + reqDelay, 'output-http2', 'http2')
       }
   </script>
</body>
</html>

二、服務端代碼(http1.x與http2僅有一處不一樣)

const http = require('https') // 若爲http2則把'https'模塊改成'spdy'模塊
const url = require('url')
const fs = require('fs')
const express = require('express')
const path = require('path')

const app = express()

const options = {
  key: fs.readFileSync(`${__dirname}/server.key`),
  cert: fs.readFileSync(`${__dirname}/server.crt`)
}

const allow = (res) => {
  res.header("Access-Control-Allow-Origin", "*")
  res.header("Access-Control-Allow-Headers", "X-Requested-With")
  res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS")
}

app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')
app.use(express.static(path.join(__dirname, 'static')))

app.get('/option/?', (req, res) => {
    allow(res)
    let size = req.query['size']
    let delay = req.query['delay']
    let buf = new Buffer(size * 1024 * 1024)
    setTimeout(() => {
        res.send(buf.toString('utf8'))
    }, delay)
})

http.createServer(options, app).listen(1001, (err) => { // http2服務器端口爲1002
    if (err) throw new Error(err)
    console.log('Http1.x server listening on port 1001.')
})
相關文章
相關標籤/搜索