全面解析HTTP/2:历史、特性、调试、性能

原文地址

写在前面

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是互联网上应用最为广泛的一种网络协议。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(URI)来标识。

虽然HTTP/1.1稳定运行了十多年了,但HTTP/2来势汹汹,作为技术工程师有必要学习一下HTTP/2。今天,阿里云CDN安防技术专家金九将从历史、特性、调试、性能四个层面,来全面解析HTTP/2,希望本文可以给你带来一些启发。


一、历史

1、 HTTP/0.9
最早的原型,1991年发布,该版本极其简单,只支持 GET 方法,不支持 MIME 类型和各种 HTTP 首部等等。

2、 HTTP/1.0
1996年发布。HTTP/1.0在HTTP/0.9的基础之上添加很多方法,各种 HTTP 首部,以及对多媒体对象的处理。

除了GET命令,还引入了POST命令和HEAD命令,丰富了浏览器与服务器的互动手段。

任何格式的内容都可以发送。这使得互联网不仅可以传输文字,还能传输图像、视频、二进制文件。这为互联网的大发展奠定了基础。

HTTP请求和回应的格式也变了。除了数据部分,每次通信都必须包括头信息(HTTP header),用来描述一些元数据。

可以说,HTTP/1.0是对HTTP/0.9做了革命性的改变,但HTTP/1.0依然有一些缺点,其主要缺点是每个TCP连接只能发送一个请求,发送数据完毕后连接就关闭,如果还要请求其他资源,就得再新建一个连接。虽然有些浏览器为了解决这个问题,用了一个非标准的Connection头部,但这个不是标准头部,各个浏览器和服务器实现有可能不一致,因此不是根本解决办法。

3 、HTTP/1.1
1999年正式发布。HTTP/1.1是当前主流的 HTTP 协议。完善了之前 HTTP 设计中的结构性缺陷,明确了语义,添加和删除了一些特性,支持更加复杂的的 Web 应用。

经过了十多年将近20年的发展,这个版本的HTTP协议已经很稳定了,跟HTTP/1.0相比,它新增了很多引人注目的新特性,比如Host协议头、Range分段请求、默认持久连接、压缩、分块传输编码(chunked)、缓存处理等等,至今都大量使用,而且很多软件依赖这些特性。

虽然HTTP/1.1并不像HTTP/1.0对于HTTP/0.9那样的革命性,但是也有很多增强,目前主流浏览器均默认采用HTTP/1.1。

# 4、SPDY
SPDY(发音:speedy)协议由Google开发,主要解决 HTTP/1.1 效率不高的问题,于2009年公开,到2016年初结束使命。因为HTTP/2已经被IETF标准化了,以后各种新版浏览器都会支持HTTP/2,Google认为SPDY已经没有存在的必要了,接下来的使命由HTTP/2去完成。

5、HTTP/2
HTTP/2是最新的HTTP协议,已于2015年5月份正式发布, Chrome、 IE11、Safari以及Firefox 等主流浏览器已经支持 HTTP/2协议。

注意是HTTP/2而不是HTTP/2.0,这是因为IETF(Internet Engineering Task Force,互联网工程任务组)认为HTTP/2已经很成熟了,没有必要再发布子版本了,以后要是有重大改动就直接发布HTTP/3。

其实,HTTP/2的前身是SPDY,甚至它俩的目标、原理和基本实现都差不多。IETF组委会中有很多Google工程师,将SPDY推动成为标准也就不足为奇了。

HTTP/2不仅优化了性能而且兼容了HTTP/1.1的语义,其几大特性与SPDY差不多,与HTTP/1.1有巨大区别,比如它不是文本协议而是二进制协议,而且HTTP头部采用HPACK进行压缩,支持多路复用、服务器推送等等。


二、特性

1、二进制协议

HTTP/2 采用二进制格式传输数据,而非HTTP/1.x的文本格式。消息头和消息体均采用二进制格式,并称之为”帧“(Frame)。Frame二进制基本格式如下(摘自rfc7540#section-4.1):

+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+

之所以说是基本格式,是因为所有HTTP/2 Frame都是由该基本格式来封装,类似于TCP头,目前有10个Frame,由Type字段来区分,各个Frame都有自己的二进制格式,都封装Frame Payload中。

其中有两个重要的Frame:Headers Frame(Type=0x1)和Data Frame(Type=0x0),分别对应HTTP/1.1中的消息头(Header)和消息体(Body),由此可见语义并没有太大变化,而是文本格式变成二进制的Frame。二者的转换和关系如下图(摘自 《High Performance Browser Networking》):

此外,HTTP/2中还有流(Stream)和消息(Message)的概念,通过Stream Identifier(即流ID)字段来标识,流ID一样的是同一个流,流中包含消息,这个消息对应HTTP/1.x的请求消息(Request Message)或者响应消息(Response Message),消息是通过帧(Frame)来传输的,响应消息比较大,可能由多个Data Frame来传输。HTTP/2中流、消息和帧的对应关系如下图(摘自 《High Performance Browser Networking》):

2、头部压缩

HTTP/1.x 每次请求和响应,都会携带大量冗余消息头信息,比如Cookie和User Agent,基本一样的内容,每次请求浏览器都会默认携带,这会浪费很多带宽资源,也影响了速度。这是因为HTTP是无状态协议,每次请求都必须附上所有信息,从而导致了每次请求都带上大量重复的消息头。

为此,HTTP/2做了优化,对消息头采用HPACK格式进行压缩传输,并对消息头建立索引表,相同的消息头只发送索引号,从而提高效率和速度。但付出的代价是客户端和服务器均维护一个索引表,在如今内存不值钱的时代,这点空间换取时间还是非常值得的。

关于HPACK请参考RFC7541

3、多路复用

多路复用是指在一个TCP连接里,客户端和服务器都可以同时发送多个请求或者响应,对HTTP/1.x来说各个请求和响应都是有严格的次序要求,而在HTTP/2中,不用按照次序一一对应,而且并发的多个请求或者响应中任何一个请求阻塞了不会影响其他的请求或者响应,这样就避免了“队头堵塞”。如下图(摘自 《High Performance Browser Networking》):

4、服务器推送

服务器推送(Server Push)是指在HTTP/2中服务器未经请求可以主动给客户端推送资源。例如服务端可以主动把 图片、JS 和 CSS 文件推送给浏览器,而不需要浏览器解析HTML后再发送这些请求。当浏览器解析HTML后这些需要的资源都已经在浏览器里了,大大提高了网页加载的速度。如下图(摘自 《High Performance Browser Networking》):

浏览器发起请求page.html这个页面,这个页面中引用了script.js和style.css,服务器在响应page.html后顺便推送了script.js和style.css这两个文件,这样浏览器解析完page.html后发现引用的script.js和style.css已经在本地了,不需要再发送请求了,这样就节省了两次请求和这两次请求所花的网络时间,大大提高了网络性能和用户体验。

5、安全

HTTP的安全是由SSL/TLS来保障,也就是HTTPS,其实HTTP/2并不强制要求依赖SSL/TLS,但是,当前主流浏览器均只支持基于SSL/TLS的HTTP/2,况且在网络劫持日益猖獗的互联网环境下,HTTPS将是未来的趋势,HTTP/2基于HTTPS也是未来的趋势,而各大主流浏览器在实现HTTP/2之初均只支持SSL/TLS的HTTP/2,可见安全也是HTTP/2的重要特性之一。


三、调试

从原理和目标上看HTTP/2和SPDY差不多,从Nginx官方代码上看HTTP/2和SPDY的实现也差不多。Nginx官方代码中已经删除了spdy模块的代码,取而代之的是http2模块(ngx_http_v2_module)。

1、启用

1.1、在编译参数中加入http2模块(默认已经有ssl模块了):

# git clone https://github.com/alibaba/tengine.git
# cd tengine
# ./configure --prefix=/opt/tengine --with-http_v2_module
# make
# make install

1.2、生成测试证书和私钥

# cd /etc/pki/CA/
# touch index.txt serial
# echo 01 > serial
# openssl genrsa -out private/cakey.pem 2048
# openssl req -new -x509 -key private/cakey.pem -out cacert.pem
# cd /opt/tengine/conf
# openssl genrsa -out tengine.key 2048
# openssl req -new -key tengine.key -out tengine.csr
...
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:ZJ
Locality Name (eg, city) []:HZ
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Aliyun
Organizational Unit Name (eg, section) []:CDN
Common Name (e.g. server FQDN or YOUR name) []:www.tengine.com
Email Address []:

Please enter the following ‘extra‘ attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
...
# openssl x509 -req -in tengine.csr -CA /etc/pki/CA/cacert.pem -CAkey /etc/pki/CA/private/cakey.pem -CAcreateserial -out tengine.crt

1.3、配置http2

server {
        listen       443 ssl http2;
        server_name  www.tengine.com;
        default_type  text/plain;
        ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
        ssl_certificate     tengine.crt;
        ssl_certificate_key tengine.key;
        ssl_ciphers         EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:EECDH+AES256:EECDH+3DES:RSA+3DESi:RC4-SHA:ALL:!MD5:!aNULL:!EXP:!LOW:!SSLV2:!NULL:!ECDHE-RSA-AES128-GCM-SHA256;
        ssl_prefer_server_ciphers  on;

        location / {
            return 200 "http2 is ok";
        }
    }

1.4、启动tengine即可:

# /opt/tengine/sbin/nginx -c /opt/tengine/conf/nginx.conf
1.5、测试
先绑定/etc/hosts:
127.0.0.1 www.tengine.com
用nghttp工具测试:
[email protected] ~/work/pcap$ nghttp ‘https://www.tengine.com/‘ -v
[  0.019] Connected
[  0.043][NPN] server offers:
          * h2
          * http/1.1
The negotiated protocol: h2
[  0.064] recv SETTINGS frame <length=18, flags=0x00, stream_id=0>
          (niv=3)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):128]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):2147483647]
          [SETTINGS_MAX_FRAME_SIZE(0x05):16777215]
[  0.064] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=2147418112)
[  0.064] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
          (niv=2)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[  0.064] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.064] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
          (dep_stream_id=0, weight=201, exclusive=0)
[  0.064] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
          (dep_stream_id=0, weight=101, exclusive=0)
[  0.077] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
          (dep_stream_id=0, weight=1, exclusive=0)
[  0.077] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
          (dep_stream_id=7, weight=1, exclusive=0)
[  0.077] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
          (dep_stream_id=3, weight=1, exclusive=0)
[  0.077] send HEADERS frame <length=39, flags=0x25, stream_id=13>
          ; END_STREAM | END_HEADERS | PRIORITY
          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)
          ; Open new stream
          :method: GET
          :path: /
          :scheme: https
          :authority: www.tengine.com
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: nghttp2/1.9.2
[  0.087] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.087] recv (stream_id=13) :status: 200
[  0.087] recv (stream_id=13) server: Tengine/2.2.0
[  0.087] recv (stream_id=13) date: Mon, 26 Sep 2016 03:00:01 GMT
[  0.087] recv (stream_id=13) content-type: text/plain
[  0.087] recv (stream_id=13) content-length: 11
[  0.087] recv HEADERS frame <length=63, flags=0x04, stream_id=13>
          ; END_HEADERS
          (padlen=0)
          ; First response header
http2 is ok[  0.087] recv DATA frame <length=11, flags=0x01, stream_id=13>
          ; END_STREAM
[  0.087] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])

用chrome浏览器测试:

2、抓包分析

从抓包来学习HTTP/2格式是最好的办法,但HTTP/2又是基于https的,也就是加密的,直接抓包看到的是密文,没有意义,还好Wireshark提供解密https流量的办法可以比较方便地调试HTTP/2。

2.1、先导出系统变量$SSLKEYLOGFILE,以OSX系统为例

#bash
echo "\nexport SSLKEYLOGFILE=~/ssl_debug/ssl_pms.log" >> ~/.bash_profile && . ~/.bash_profile

2.2、打开Chrome或者Firefox

#chrome
open /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome
#firefox
open /Applications/Firefox.app/Contents/MacOS/firefox

用打开的Chrome或者Firefox浏览器打开https网站,比如:https://www.taobao.com
然后看看文件~/ssl_debug/ssl_pms.log有没有内容,有内容就可以用Wireshark解密https数据了。

2.3、Wireshark设置

Wireshark->Perferences...->Protocols->SSL

启动抓包:

可见已经能得到HTTP/2的明文数据了。篇幅所限,在此不展开具体细节了,更多HTTP/2二进制协议细节请参考RFC7540


四、性能

测试机器配置:cache1.cn1, 115.238.23.13, 16核Intel(R) Xeon(R) CPU L5630 @ 2.13GHz,48G内存,万兆网卡
测试工具:h2load
测试结果:


结论

1、无论是否keepalive,HTTP/2与SPDY/3.1性能相当,HTTP/2略优。
2、在size为1k、2k、4k测试结果中保持较低RT情况下QPS也较高,CPU没有达到瓶颈,加大压测客户端数量后QPS有所提高,但RT变大,5xx也变多(这部分数据没有给出,是测试时记录的现象)。
在size为16k、32k、64k、128k、256k的测试结果中CPU达到瓶颈,随着size变大,QPS降低,RT变高,CPU性能消耗较多的函数是gcm_ghash_clmul。
在size为512k时网卡达到瓶颈,CPU没有达到瓶颈。
3、在开启keepalive的情况下,HTTP/1.1的性能与HTTP/2的性能差距不是很大。但关闭keepalive时HTTP/2的性能比HTTP/1.1更好。

原文地址

时间: 2024-10-09 13:01:38

全面解析HTTP/2:历史、特性、调试、性能的相关文章

数据库深度解析 | 从NoSQL历史看未来

数据库深度解析 | 从NoSQL历史看未来 http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=209753217&idx=1&sn=d3a021a7bd959cbf92ffc658336b2387&scene=1&srcid=fWEZMjyaJKjZo5wrpSiB&from=singlemessage&isappinstalled=0#rd 本文根据王晶昱(花名沈询)老师在“高可用架构”微信群

Angular2 VS Angular4 深度对比:特性、性能

在Web应用开发领域,Angular被认为是最好的开源JavaScript框架之一. Google的Angular团队已于3月23日发布了Angular4,而期待已久的Angular2版本则是之前版本的完全重构. 对于成熟的开发人员来说,有以上两种选择是一件很棒的事情:但是,对于处于学习阶段的新晋开发人员来说,可能有点不知如何选择. 那么,本文将会对Angular2和Angular4进行深度对比,以便帮助大家更好的了解这两个版本. Angular2 Angular2是在2015年底发布的.接下来

C++ 语言特性的性能分析

转载:http://www.cnblogs.com/rollenholt/archive/2012/05/07/2487244.html      大多数开发人员通常都有这个观点,即汇编语言和 C 语言适合用来编写对性能要求非常高的程序.而 C++ 语言的主要应用范围是编写复杂度非常高的程序,但是对性能要求不是那么严格的程序.但是事实往往并非如此,很多时候,一个程序的速度在框架设计完成时大致已经确定了,而并非是因为采用了C++语言才使其速度没有达到预期的目标.因此当一个程序的性能需要提高时,首先

Node.js V0.12 新特性之性能优化

v0.12悠长的开发周期(已经过去九个月了,并且还在继续,是有史以来最长的一次)让核心团队和贡献者们有充分的机会对性能做一些优化. 本文会介绍其中最值得注意的几个. http://www.infoq.com/cn/articles/nodejs-v012-optimize-performance?utm_source=infoq&utm_medium=related_content_link&utm_campaign=relatedContent_articles_clk Node.js和

揭秘Sql2014新特性-tempdb性能提升

一直以来,在高负载,复杂的生产环境中,tempdb的压力是成为整个实例瓶颈的重要因素之一.微软的工程师们也在各个版本中不断优化它的使用.到了Sql Server2014又有了新的特性使其性能得tempdb的性能有一定提升.这里我将通过实例给大家介绍tempdb在新版本中的实现变化. 我们都知道tempdb的日志无需提前落盘是其快的重要原因之一,日志无需落盘,那么数据又是如何操作的呢?这里先介绍一个重要的概念:主动写(Eager write) 主动写:Sql server引擎在面对最小化日志操作的

使用instruments工具调试性能

Instruments Instruments是Xcode套件中没有被充分利用的一个工具.很多iOS开发者从没用过Instruments,或者只是用Leaks工具检测循环引用.实际上有很多Instruments工具,包括为动画性能调优的东西. 你可以通过在菜单中选择Profile选项来打开Instruments(在这之前,记住要把目标设置成iOS设备,而不是模拟器).然后将会显示出图12.1(如果没有看到所有选项,你可能设置成了模拟器选项). 图12.1 Instruments工具选项窗口 就像

flash builder 4.6使用profile(概要分析)调试性能

最近用调试flex的性能,发现fb自带有性能调试工具profile,折腾好一段时间终于成功用上 环境:flash builder 4.6,myeclipse 10(fb装独立版,再以插件形式绑定到myeclipse),win8.1 64bit,ie 11,flash player 16 第一次profile,静止一段时间后弹出以下错误信息——unable to connect to the application to fetch profile data. please try profili

Node.js V0.12新特性之性能优化

v0.12悠长的开发周期(已经过去九个月了,并且还在继续,是有史以来最长的一次)让核心团队和贡献者们有充分的机会对性能做一些优化.本文会介绍其中最值得注意的几个. 支持塞住模式的可写流 现在可写流可以支持“塞住(corked)”模式,类似于你执行man tcp时见到的socket选项TCP_CORK和TCP_NOPUSH. 当被塞住时,写到流中的数据会排队直到流被重新开塞(uncorked).这样Node.js可以将比较小的写操作合并成比较大的,从而减少系统调用和TCP往返. http模块已经升

Redis基础、高级特性与性能调优

本文将从Redis的基本特性入手,通过讲述Redis的数据结构和主要命令对Redis的基本能力进行直观介绍.之后概览Redis提供的高级能力,并在部署.维护.性能调优等多个方面进行更深入的介绍和指导.本文适合使用Redis的普通开发人员,以及对Redis进行选型.架构设计和性能调优的架构设计人员. 目录 概述 Redis的数据结构和相关常用命令 数据持久化 内存管理与数据淘汰机制 Pipelining 事务与Scripting Redis性能调优 主从复制与集群分片 Redis Java客户端的

Redis 基础、高级特性与性能调优

本文将从Redis的基本特性入手,通过讲述Redis的数据结构和主要命令对Redis的基本能力进行直观介绍.之后概览Redis提供的高级能力,并在部署.维护.性能调优等多个方面进行更深入的介绍和指导. 本文适合使用Redis的普通开发人员,以及对Redis进行选型.架构设计和性能调优的架构设计人员. 目录 概述 Redis的数据结构和相关常用命令 数据持久化 内存管理与数据淘汰机制 Pipelining 事务与Scripting Redis性能调优 主从复制与集群分片 Redis Java客户端