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

标签: 研究 http2 性能 | 发表时间:2016-10-26 08:27 | 作者:English0523
分享到:
出处:http://blog.csdn.net

一、研究目的

HTTP2的概念提出已经有相当长一段时间了,而网上关于关于http2的文章也一搜一大把。但是从搜索的结果来看,现有的文章多是偏向于对http2的介绍,鲜有真正从数据上具体分析的。这篇文章正是出于填补这块空缺内容的目的,通过一系列的实验以及数据分析,对http2的性能进行深入研究。当然,由于本人技术有限,实验所使用的方法肯定会有不足之处,如果各位看官有发现问题,还请向我提出,我一定会努力修改完善实验的方法的!

二、基础知识

关于HTTP2的基础知识,可以参考下列几篇文章,在这里就不进行赘述了。

通过学习相关资料,我们已经对HTTP2有了一个大致的认识,接下来将通过设计一个模型,对HTTP2的性能进行实验测试。

三、实验设计

设置实验组:搭建一个HTTP2(SPDY)服务器,能够以HTTP2的方式响应请求。同时,响应的内容大小,响应的延迟时间均可自定义。

设置对照组:搭建一个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模块基本一致。

1、服务器搭建

实验组和对照组的服务器逻辑完全一致,关键代码如下:

  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)
})

其逻辑是,根据从客户端传入的参数,动态设置响应资源的大小和延迟时间。

2、客户端搭建

客户端可动态设置请求的次数、资源的数目、资源的大小和服务器延迟时间。同时搭配Chrome的开发者工具,可以人为模拟不同网络环境。在资源请求响应结束后,会自动计算总耗时时间。关键代码如下:

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

客户端通过循环对资源进行多次请求,其数量可设置。每一次循环都会通过 imageLoadTime更新时间,以实现时间统计的功能。

3、实验项目

a. http2性能研究

通过研究章节二的文章内容,可以把http2的性能影响因素归结于“延迟”和“请求数目”。本实验增加了“资源体积”和“网络环境”作为影响因素,下面将会针对这四项进行详细的测试实验。其中每一次实验都会重复10次,取平均值后作记录。

b. 服务端推送研究

http2还有一项非常特别的功能——服务端推送。服务端推送允许服务器主动向客户端推送资源。本实验也会针对这个功能展开研究,主要研究服务端推送的使用方法及其对性能的影响。

四、HTTP2 性能数据统计

1、延迟因素对性能的影响

条件/实验次数 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

2、请求数目对性能的影响

通过上一个实验,可以知道在延迟为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

3、资源体积对性能的影响

通过上两个实验,可以知道在延迟为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

4、网络环境对性能的影响

通过上两个实验,可以知道在延迟为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

五、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会把它当作网络请求来处理,于是导致了上述实验所看到的问题。

六、研究结论

通过上述一系列的实验,我们可以知道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的同学有些许帮助吧,如文章开头所说,如果你发现我的实验设计有任何问题,或者你想到了更好的实验方式,也欢迎向我提出,我一定会认真研读你的建议的!


下面附送实验所需源码:1、客户端页面

  <!-- 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>

2、服务端代码(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.')
})
作者:English0523 发表于2016/10/26 8:27:06 原文链接
阅读:64 评论:1 查看评论

相关 [研究 http2 性能] 推荐:

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

- - CSDN博客综合推荐文章
HTTP2的概念提出已经有相当长一段时间了,而网上关于关于http2的文章也一搜一大把. 但是从搜索的结果来看,现有的文章多是偏向于对http2的介绍,鲜有真正从数据上具体分析的. 这篇文章正是出于填补这块空缺内容的目的,通过一系列的实验以及数据分析,对http2的性能进行深入研究. 当然,由于本人技术有限,实验所使用的方法肯定会有不足之处,如果各位看官有发现问题,还请向我提出,我一定会努力修改完善实验的方法的.

http2协议

- - 企业架构 - ITeye博客
http2协议的草案已经出来了,阅读了一下网上的中文版,http2尽可能的兼容http1.1. 改进了http1.1协议的不足. http1.0和http1.1的缺点:. 1.http1.0只允许在一个连接上建立当前未完成的请求. 2.http1.1管道只部分处理了请求并发和包头堵塞问题,客户端多建立TCP连接,减少延迟.

in 和 exists性能研究

- zhengyun - CSDN博客推荐文章
从sql编程角度来说,in直观,exists不直观多一个select;in可以用于各种子查询,而exists好像只适宜于关联子查询. in 是把外表和内表作hash join,而exists是对外表作loop,每次loop再对内表进行查询. 一直以来认为exists比in效率高的说法是不准确的. 如果查询的两个表大小相当,那么用in和exists差别不大.

in 和 exists性能研究

- - SQL - 编程语言 - ITeye博客
从sql编程角度来说,in直观,exists不直观多一个select;in可以用于各种子查询,而exists好像只适宜于关联子查询. in 是把外表和内表作hash join,而exists是对外表作loop,每次loop再对内表进行查询. 一直以来认为exists比in效率高的说法是不准确的. 如果查询的两个表大小相当,那么用in和exists差别不大.

Google研究称C++是性能最优越的语言

- ashuai - Solidot
Google发表了一篇研究论文(PDF),声称C++是目前市场上性能最好的程序语言. 搜索巨人分别用C++、Java、Scala和其发明的Go语言实现一种压缩算法,然后测试其性能. 结果显示,C++远胜其它三种语言. 虽然C++性能最优越,但Google同时指出它实现起来太复杂,超出了普通程序员的水平,而Java则“最容易实现”.

研究人员找到方法提升WiFix性能40倍到70倍

- - Solidot
一个WiFi热点的访问人数越多,网速越慢. 当有大量用户向一个信道递交数据请求,热点难以及时返回请求的数据,这种情况还会因为反复的请求而恶化. 现在,北卡州立大学的研究人员开发出WiFox,能监视WiFi信道上的网络流量,当探测到积压数据,它会授权热点根据优先次序发送数据. 研究人员在实验室WiFi网络测试了WiFox程序,发现当联网用户达到约25人时网络数据吞吐量提升40倍;当联网用户达到约45人时性能提升达到70倍.

用户研究

- - 技术改变世界 创新驱动中国 - 《程序员》官网
介绍自己的设计流程时,设计师通常都说它是“以人为中心”或是“以用户为中心”的. 笼统地讲,这表示设计师经常要考虑所设计产品的潜在用户,尽力为这些人创造出最好的产品. 这个问题看似简单,实际上却不好回答. 好的设计通常都是从用户研究着手的. 我们如何才能发现人们想要实现的目标. 虽然这样做有时会得到一些有用的信息,但一定要小心地评估人们给出的答案.

JVM研究

- - 开源软件 - ITeye博客
每天接客户的电话都是战战兢兢的,生怕再出什么幺蛾子了. 我想Java做的久一点的都有这样的经历,那这些问题的最终根结是在哪呢. JVM全称是Java Virtual Machine,Java虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM你是看不到的,它存在内存中.

BigPipe学习研究

- maxiyun - 搜索技术博客-淘宝
技术背景 FaceBook页面加载技术. 试想这样一个场景,一个经常访问的网站,每次打开它的页面都要要花费6 秒;同时另外一个网站提供了相似的服务,但响应时间只需3 秒,那么你会如何选择呢. 数据表明,如果用户打开一个网站,等待3~4 秒还没有任何反应,他们会变得急躁,焦虑,抱怨,甚至关闭网页并且不再访问,这是非常糟糕的情况.

Mysql缓存研究

- - CSDN博客推荐文章
缓存机制简单的说就是缓存sql文本及查询结果,如果运行相同的sql,服务器直接从缓存中取到结果,而不需要再去解析和执行sql. 如果表更改了,那么使用这个表的所有缓存查询将不再有效,查询缓存值的相关条目被清空. 更改指的是表中任何数据或是结构的改变,包括INSERT、UPDATE、DELETE、TRUNCATE、ALTER TABLE、DROP TABLE或DROP DATABASE等,也包括那些映射到改变了的表的使用MERGE表的查询.