HTTP2的概念提出已经有至关长一段时间了,而网上关于关于http2的文章也一搜一大把。可是从搜索的结果来看,现有的文章可能是偏向于对http2的介绍,鲜有真正从数据上具体分析的。这篇文章正是出于填补这块空缺内容的目的,经过一系列的实验以及数据分析,对http2的性能进行深刻研究。固然,因为本人技术有限,实验所使用的方法确定会有不足之处,若是各位看官有发现问题,还请向我提出,我必定会努力修改完善实验的方法的!css
关于HTTP2的基础知识,能够参考下列几篇文章,在这里就不进行赘述了。html
HTTP 2.0的那些事node
HTTP2.0的奇妙平常jquery
一分钟预览 HTTP2 特性和抓包分析nginx
经过学习相关资料,咱们已经对HTTP2有了一个大体的认识,接下来将经过设计一个模型,对HTTP2的性能进行实验测试。web
设置实验组:搭建一个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
更新时间,以实现时间统计的功能。
经过研究章节二的文章内容,能够把http2的性能影响因素归结于“延迟”和“请求数目”。本实验增长了“资源体积”和“网络环境”做为影响因素,下面将会针对这四项进行详细的测试实验。其中每一次实验都会重复10次,取平均值后做记录。
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 |
本实验主要针对网络环境对服务端推送速度的影响进行研究。在本实验中,所请求/推送的资源都是一个体积为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会把它看成网络请求来处理,因而致使了上述实验所看到的问题。
经过上述一系列的实验,咱们能够知道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
若要使用服务端推送,则在服务端须要对响应的逻辑进行扩展,这个须要视状况具体分析实施。
纸上得来终觉浅,绝知此事要躬行。若是不是真正的设计实验、进行实验,我可能根本不会知道原来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.') })