序言

目前HTTP/2.0(简称h2)已经在广泛使用(截止2018年8月根据Alexa流行度排名的头部1千万网站中,h2占比约29%,https://w3techs.com/technologies/details/ce-http2/all/all)。写此文章的目的是:h2作为较新的技术,并逐渐占有率广泛,虽然目前有更新的QUIC,但其实现思路类似于h2。颠覆以往的HTTP/1.x,H2的创造性的技术值得我们细细品味。此篇文章根据笔者在h2开发经验和思考,向你介绍全面的h2知识以及是非功过。本篇更注重于帮助读者理解h2的设计思路、亦可作为一篇RFC导读或者总结。

第一话、追踪溯源

图1、HTTP年鉴图

早在1991年,伴随WWW诞生之初,HTTP/0.9协议已经提出。HTTP0.9是简单且应用受限的协议。支持去网络主机获取对应路径的资源。但是没有扩展属性。其协议之简单甚至只用下面一个访问谷歌主机的例子概括了HTTP/0.9的全部。如下所示,协议只支持GET,没有http头;响应只能是超文本。

telnet google.com 80

Connected to x.x.x.x

GET /about

(Hyper text)

(Conection closed)

随着人们对富媒体信息的渴望以及浏览器的普及,HTTP/1.0在1996年被提出来。HTTP/1.0的很多特性目前还被广泛使用,但是仍然像HTTP/0.9一样一次请求需要创建一次的tcp连接。随即短短几年时间内,HTTP/1.1以RFC标准形式再次展现在人们眼前。此时的HTTP协议1.1版本已经重新设计了长连接、options请求方法、cache头、upgrade头、range头、transfer-encoding头, 以及pieline(in order)等概念。

而我们另一个所熟知的HTTPS的SSL/TLS技术各个版本差不多在后来的十年间逐渐被提出。出于安全考虑,互联网通信间的防火墙路由交换机等设备,这些设备一般仅会开发有限的端口(如80和443)。各种版本的通信协议只能复用这些端口。HTTP1.1的80端口设计了upgrade请求头升级到更高级的协议,而443端口为了避免多消耗个网络RTT,在tls握手过程中使用了NPN/ALPN技术直接在通信之前保持CS两端的协议一致。NPN/ALPN是是TLS协议扩展,其中NPN是Google为实现spdy提出的。由服务端提供可支持的协议,供客户端选择。ALPN则是更接近于HTTP交互的方式,由客户端先发出使用某种协议的请求,由服务端确认是否支持协议。ALPN为了HTTP2诞生做铺垫。

另外一个不得不提的是spdy协议。Spdy旨在解决HTTP1.1的线头阻塞问题(后面章节有详细讨论)于2009被google提出。同时分别于2012提出了spdy3.0实现了流控制,2013-2014期间提出了流优先级,server push等概念。Spdy的存在意义更像是http2.0的体验服。它为探索HTTP继续演进道路做了铺垫。

第二话、人机交互

汇编是效率最高的语言之一,但是又是最晦涩难懂的语言之一。而人脑易懂的编程语言往往牺牲性能作为折衷。简单说,HTTP/1.x协议就是为了人类语言习惯所设计的协议,但是转换成机器执行协议并不是高效的。让我们在回顾下计算机执行解析HTTP1.x的流程。

GET / HTTP/1.1<crlf>

Host: xxx.aa.com<crlf>

<crlf>

对应的解析伪代码是

loop

while(! CRLF)

read bytes

end while

if line 1:

parse line as Request-Line

else if empty line:

Break out and We have done

else If start with non-whitespace

parse header

else if space

continue with last heade

end if

end loop

在伪代码解析流程可以看到,肉眼看起来简洁的协议解析起来是这么的费劲。而且在HTTP服务器中还要考虑这种问题:字节行的长度是未知的,也不知道预先分配多大内存。

HTTP/2.0使用了计算机易懂的二进制编码信息,而且得向上兼容HTTP的涵义。具体我们来看下他是如何做到的。像大多数通信协议一样,桢是传输最小单位。桢分为数据帧和控制桢。数据帧作为数据的载体,控制桢控制信道的信令。h2桢的通用格式为首部9字节+额外的字符。正如你能想到的那样,桢的第一个部分是描述长度,第二个部分描述了桢的类型,第三个部分描述了标志Flag,第四个部分是唯一序列号。这是所有桢的通用头。通用头紧接的是桢的实体。图4展示了桢的结构。

图2、通用桢的格式

这样设计有什么好处呢。再来看一下桢的解析流程,你就会发现对计算机来说更简洁。

loop

read 9 byte

payload_Length=first 3 bytes

read payload

swith type:

Take action

end loop

HTTP2.0使用header桢表达HTTP header+request line,data桢表达Body。header桢和data桢使用相同的stream id组成一个完整的HTTP请求/响应包。这里的stream描述了一次请求和响应,相当于完成了一次HTTP/1.x的短连接请求和响应。

第三话、并行不悖

上节讲到我们用h2桢完整表达了HTTP/1.x。但是h2协议抱负远不止于此。它的真正目的是解决之前HTTP1.x的线头阻塞问题、改善网络延迟和页面加载时间。

我们知道一个完整的网页包含了主页请求和数次或数十次的子请求。HTTP/1.1已经可以并行发出所有请求.但是HTTP本身是无状态的协议,它依赖于时间的顺序来识别请求和响应直接的对应关系。先来的请求必须先给响应。那么如果后面的响应资源对浏览器构建DOM或者CSSOM更重要。那它必须阻塞等待前者完成。当然这也难不倒我们,我们可以多开几条tcp连接(浏览器规定一个origin(协议+host+port)最多6个)或者合并资源来减少不必要的阻塞。这是有代价的。首先tcp建连的开销,其实合并资源带来一小块子资源过期导致整个合并资源的缓存过期。对此,h2有一揽子的解决方案,接下来一一道来。

h2在一个tcp连接创建多个流。每个流可以有从属关系,比如说根据浏览器加载的优先级顺序(主请求>CSS>能改变DOM结构的JS文件>图片和字体资源文件)建立一条依赖关系链。处于同一等级的依赖关系中可以设置权重。权重用于分配传输信道资源多少。

图6例子说明了有一次主页请求index.html、一次main.css,一次jq.js以及一些image文件和字体文件qq.tff

图3、h2请求的依赖树

HTML的优先级最高,在HTML传输完成之前,其他文件不会被传输。HTML传输完成后,JS和CSS根据其分配的权重占比分配信息传输资源。如果CSS传输完成后,TFF和PNG如果是相同权重,那么他们将占有1/4的信道资源。

这里抛出3个问题和答案。

  • 如果CSS被阻塞了,那么 JS 得到本属于CSS的通信资源
  • 如果CSS传输完成但没有被移依赖树, TFF和PNG继承CSS的通信份额 (假设TFF和PNG权重一样,那么各分得1/4通信资源).
  • 如果CSS在依赖数被移除,JS, TFF, PNG平分通信资源(假设3个权重一样,那么三者各分得1/3通信资源)

第四话、众星捧月

HTTP2还设计了一系列方案来改善网络的性能、包括流量控制,HPack压缩,Server Push。

什么你说TCP已经有流量控制了,HTTP不是多此一举吗?没错,但是在单条TCP内部,各个流可是没有流量控制。流量控制使用了Update Frame不断告知发送方更新发送的窗口大小(上限)。流量控制一个现实用途是阻塞不重要的请求,以腾出更大的通信资源给重要的请求使用。流量控制是不可以被关闭的,流量大小可以设置2的31次方-1(2GB)。不同的中间网络设备有不一样的吞吐能力。流控的另一个用途在于同步所有的中间设备交换机最小的上限。流量窗口初始大小为65535(2的16次方-1)。

就像世界上的大多数财富聚集在少数人身上一样。在一份对48,452,989个请求的统计中,以下11个头占据了99%的数量,依名次递减分别是user-agent、accetp-encoding、accept-language、accept、referer、host、connection、cookie、origin、upgrade-inseure-request、content-type。http header的值也有很大相似性,比如说”/index.html“, “gzip, deflate”。Cookie也携带冗余的信息。

这些都组成了http header大量可以压缩的内容。

而在一份GET请求和一个304响应或者content-length很少的响应中,这些头占据了很大比例的通信资源。2016年发布的一份HTTP报告中,请求头大约在460bytes,对一个通常的网页,平均会有140个请求对象。这些头总共需要63KB。这些量很有可能会是首屏和页面加载时间优化的瓶颈。

可能你会说用gzip等压缩算法这些请求头,不就完了吗?的确spdy就这样干过,直到2013年BREACH攻击暴露了gzip压缩在https应用的安全性,这种攻击让攻击者很容易获得session cookie等数据。于是才有了HPACK。

HPACK简单来说就是索引表,包括静态表和动态表。静态表由RFC定义,从不改变,静态表预留了62个表项。每个连接的通信双方维护着动态表。

H2协议使用索引号代表http中的name、value或者name-value。假设被索引的是name,value没有索引,那么value还可以用霍夫曼编码压缩。

  • 在预定的头字段静态映射表 中已经有预定义的 Header Name 和 Header Value值,这时候的二进制数据格式如图4, 第一位固定为1, 后面7位为映射的索引值。图8的83就是这样的,83的二进制字节标示1000 0011,抹掉首位就是 3 , 对应的静态映射表中的method:POST。

图4、index索引name和value

图5、抓包示意

  • 预定的头字段静态映射表中有 name,需要设置新值。图6所示例子,一个指定 path的Header,首字符 为 44 ,对应的二进制位0100 0100。前两个字符为 01 ,Index 为 4 ,即对应静态映射表中的 path 头。第二个字符为 95对应的二进制位 1001 0101,排除首字符对应的 Value Length 为 十进制的21。即 算上 44,一共23个字符来记录这个信息。

图6、index索引name和自定义value

图7、抓包示意

  • 预定的头字段静态映射表中没有 name,需要设置新name和新值。40 的二进制是0100 0000,02 的二进制是0000 0010,后七位的十进制值是 2,86 的二进制是1000 0110,后7位的十进制值是6。

图8、index索引自定义name和自定义value

图9、抓包示意

  • 明确要求该请求头不做hpack的index

HTTP2.0还有个大杀器是Server Push,Server Push利用闲置的带宽资源可以向浏览器预推送页面展示的关键资源,Server push有效得降低了页面加载时间。具体详情参考笔者的另一篇文章https://cloud.tencent.com/developer/article/1159626。

第五话、庖丁解牛

HTTP2相比HTTP1更适合计算机执行。但是其二进制特性不易于人脑理解。这一话我们专门来讲讲关于http2的调试工具。

  • Chrome(qq浏览器)可以按住F12查看h2协议。图13所示为浏览器网络时序图,列出了具体协议名称。chrome还可以在地址栏敲入chrome://net-internals/#http2查看到h2协议细节,如图11所示。点击相应的host就可以看到h2协商过程,如图12所示。

图10、浏览器的网络时序图

chrome://net-internals/#http2

图11、chrome调试h2

图12、h2协商过程

  • wireshark(需和chrome或firefox搭配使用)。设置环境变量SSLKEYLOGFILE=c:\temp\sslkeylog.log。然后在Wireshark->Preferences->Protocols->SSL配置key所在路径。

图13、wireshark配置

图14、wireshark抓h2包

  • Nghttp2是一个完整的http2协议实现的组件。作者也参与过spdy实现。目前nghttp2库被很多知名软件作为h2协议实现库使用。另外nghttp2也自带了h2协议的分析工具。图18展示了在明文状态使用upgrade头升级到h2c。图19展示了在https基础上升级到h2。

图15、明文状态使用upgrade头升级到h2c

图16、展示了在https基础上升级到h2

Curl的—http2选项(需要和nghttp2一起编译)

图17、支持h2的curl客户端调试

  • Github还有些实用的http2工具组件,诸如chrome-http2-log-parser、http2-push-manifest等组件,笔者后续会专门开篇文章介绍这些工具。对于移动端的调试,ios可以用charles proxy做代理,android需要开发者模式使用移动端的chrome,笔者在移动端使用较少,这里就不做展开。

第六话、雕栏玉砌

H2怎么部署呢,目前主流服务端像nginx、apache都已经支持http2,主流的客户端curl和各种浏览器(包括移动端safari和chrome-android)基本也支持http2。代理服务器如ATS、Varnish,Akamai、腾讯云等CDN服务也支持http2。那么怎么把一套网站部署到h2。或者说部署h2网站和之前h1网站有什么不一样?

如果是自己的源站,那么请确保服务器支持TLS1.2已经RFC7540所要求的加密套件,h2需要保证支持alpn。你可以使用ssllabs等网站检查。对于h2服务器的要求是h2必须了解如何设置流的优先级,h2服务器需要支持server push。h2客户端需要尽量多的发送请求。

如果你的网站是从http1.x迁移过来的,那么之前对于http1.x所做的优化可能无任何帮助甚至更差。合并小文件不在需要,因为额外的小文件请求在h2看来只是开销很少。并且如果大文件的局部更改使得整个大文件缓存失效。在http1.0时代使用多个域名来并发http连接,在http2也毫无必要,因为http2天生就是并发的。http1.x做的优化比如说图片资源文件不使用cookie来减少请求大小,http2的header压缩功能也减少了这种影响。即使不做这种优化也亦可。像合并css、小图片带来的增益在http2.0也是可忽略的。

如果网页使用第三方网站组件,那么请尽可能减少使用第三方网站组件。第三方网站不能保证支持h2,所以它可能成为木桶理论的最大短板。

谨慎使用2.0-1.x的部署方案,h2流转化成h1请求。因为这样无法发挥h2性能。

图18、2.0-1.x的部署方案

CDN代理服务器的h2支持,可以屏蔽h2强制走tls的代理服务器。如图19,代理可以在与各种协议客户端的网络环境下,切断和客户端的tls连接,和服务器新建连接。也可以作为load balancer,相当于HTTP2.0用户和HTTP2.x服务器直接通信。

图19、带tls客户端功能的代理

图20列举如果绕过proxy到达h2服务器。此时的proxy相当于tcp转发的load balance功能的设备。如果该proxy支持tls的alpn协议,那么它也可以选择HTTP代理功能,和h2服务器可以建立加密连接。如果即不支持alpn,也不支持tcp转发。那么proxy只能用upgrade升级成h2协议。

图20、经过代理服务器的H2部署方案

第七话、十全九美

HTTP2.0是建立在TCP之上,所以TCP的所有缺点他都有,所以H2能发挥最大性能得益于调优的tcp协议栈。TCP的慢启动特性,决定h2一开始的并发流量不会太大,TCP以及SSL的握手连接也会拖慢h2的首包网络耗时。QUIC则完全地抛弃TCP,在UDP基础上实现了HTTP2的一系列特性。同时做了应用层的如TCP的可靠性保障。同时这些TLS1.3传输更快更简洁。这些都为HTTP2.0进化到HTTP3.0提供了一些思路。

总结

具体可以继续参考RFC7540和RFC7541协议。

转自:https://www.cnblogs.com/qcloud1001/p/9591172.html

【Http2.0】Http2.0的更多相关文章

  1. 【CUDA 基础】6.0 流和并发

    title: [CUDA 基础]6.0 流和并发 categories: - CUDA - Freshman tags: - 流 - 事件 - 网格级并行 - 同步机制 - NVVP toc: tru ...

  2. 【CUDA 基础】5.0 共享内存和常量内存

    title: [CUDA 基础]5.0 共享内存和常量内存 categories: - CUDA - Freshman tags: - 共享内存 - 常量内存 toc: true date: 2018 ...

  3. 【CUDA 基础】4.0 全局内存

    title: [CUDA 基础]4.0 全局内存 categories: - CUDA - Freshman tags: - 全局内存 - CUDA内存模型 - CUDA内存管理 - 全局内存编程 - ...

  4. 【Yii系列】Yii2.0基础框架

    缘起 因为一个月的短暂停留,我在给朋友搞事情,所以Yii系列的文章耽搁了很长时间,现在又重拾当时的知识,给大伙好好撸下这一系列的博客 提起Yii,虽然是国外的开发者搞的,但是它的作者是华人,这才是让我 ...

  5. 【Yii系列】Yii2.0的安装与调试

    接上一节的话,我们最终选择了Yii框架作为我们的主要开发框架,今天,我就和大伙来聊聊如何安装与调试Yii2.0,以及后续会和大伙聊聊如何在Yii2.0上快速撸代码. Yii2.0的安装 好的,Comp ...

  6. 【重点突破】—— Vue2.0 transition 动画Demo实践填坑

    前言:vue1.0版本和2.0版本的过渡系统改变是很大的,具体请详看文档介绍.本文转载自郭锦荣的博客,一共列举了四种transition的使用实践,分别是css过渡.css动画.javascript钩 ...

  7. 【记录一下】从0到1 我的python开发之路

    请设计实现一个商城系统,商城主要提供两个功能:商品管理.会员管理. 商品管理: - 查看商品列表 - 根据关键字搜索指定商品 - 录入商品 会员管理:[无需开发,如选择则提示此功能不可用,正在开发中, ...

  8. 【CNMP系列】CentOS7.0下安装PHP5.6.30服务

    上一节我们讲过了如何在CentOS7.0下安装MySql服务,如果没有看到欢迎页面的朋友,可以加我的个人微信详聊:litao514148204 附上一节地址:http://www.cnblogs.co ...

  9. 【CNMP系列】CentOS7.0下安装MySql5.6服务

    接上一回的话,CentOS7.0下安装好了Nginx服务,对于我们的CNMP,我们可以开始我们的M啦,就是传统意义上的MySql服务 MySql简介 MySQL是一个关系型数据库管理系统,由瑞典MyS ...

随机推荐

  1. django缓存优化(一)

    在配置之前,先介绍一个实用的工具: 当我们进入虚拟环境,在shell中进行操作的时候,往往要导入django的各种配置文件: from django.x import xxxx 这时我们可以借助dja ...

  2. Latex数学公式中的矩阵

    目录 矩阵的括号形式 array环境 上三角矩阵 分块矩阵 行内矩阵 矩阵的括号形式 使用matrix.pmatrix.bmatrix.Bmatrix.vmatrix或者Vmatrix环境: $$ \ ...

  3. mariadb(一)基础

    一.数据库介绍 1.什么是数据库? 简单的说,数据库就是一个存放数据的仓库,这个仓库是按照一定的数据结构(数据结构是指数据的组织形式或数据之间的联系)来组织,存储的,我们可以通过数据库提供的多种方法来 ...

  4. DataFrame API应用案例

    DataFrame API 1.collect与collectAsList . collect返回一个数组,包含DataFrame中的全部Rows collectAsList返回一个Java List ...

  5. 【SD系列】SAP SD模块-销售收入科目的配置

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[SD系列]SAP SD模块-销售收入科目的配置 ...

  6. Win7 VS2019安装后创建C++工程失败解决

    VS2019正式上手,第1个问题创建不了工程,看起来非常类似之前VS2017更新 解决办法 https://github.com/Microsoft/msbuild/issues/4286 和上次的问 ...

  7. ArrayBlockingQueue使用

    阻塞与非阻塞: 阻塞: 阻塞调用是没有获得资源则挂起进程,被挂起的进程进入休眠状态,调用的函数只有在得到结果之后才返回,进程继续. 对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但并不是一一 ...

  8. each of which 用法

    each of which 在以下為 同位語,非關代. 1. An urn contains two balls, each of which is known to be either white ...

  9. php上传文件如何保证上传文件不被改变或者乱码

    很多网站上传文件都截取文件后缀,前面用时间错加后缀组成,然而一下下载的网站并不需要这样,需要保持原来的文件名,这里讲述一下 //上传操作 function uploadify(){ //var_dum ...

  10. Metasploit自动攻击模块

    Metasploit自动攻击模块 Usage: db_autopwn [options] -h Display this help text -t Show all matching exploit ...