HTTP/2介绍
维基百科:HTTP/2(超文本传输协议第2版,最初命名为HTTP 2.0),简称为h2(基于TLS/1.2或以上版本的加密连接)或h2c(非加密连接),是HTTP协议的的第二个主要版本,使用于万维网。
HTTP/2是HTTP协议自1999年HTTP 1.1发布后的首个更新,主要基于SPDY协议。它由互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis(httpbis)工作小组进行开发。该组织于2014年12月将HTTP/2标准提议递交至IESG进行讨论,于2015年2月17日被批准。HTTP/2标准于2015年5月以RFC 7540正式发表。
HTTP/1.x存在的问题
在看为什么要使用HTTP/2之前,我们先来了解之前的HTTP/1.x存在的问题。
线头阻塞
:TCP连接上只能发送一个请求,前面的请求未完成前,后续的请求都在排队等待。多个TCP连接
:虽然HTTP/1.1管线化可以支持请求并发,但是浏览器很难实现,chrome、firefox等都禁用了管线化。所以1.1版本请求并发依赖于多个TCP连接,建立TCP连接成本很高,还会存在慢启动的问题。头部冗余,采用文本格式
:HTTP/1.X版本是采用文本格式,首部未压缩,而且每一个请求都会带上cookie、user-agent等完全相同的首部。客户端需要主动请求
HTTP/2的具体变化
二进制分帧层
先来理解几个概念:
帧(Frame)
:HTTP/2数据通信的最小单位消息:指 HTTP/2 中逻辑上的 HTTP 消息。例如请求和响应等,消息由一个或多个帧组成。
流(Stream)
:存在于连接中的一个虚拟通道。流可以承载双向消息,每个流都有一个唯一的整数ID。
消息(Message)
:一个完整的HTTP请求或响应,由一个或多个帧组成。特定消息的帧在同一个流上发送,这意味着一个HTTP请求或响应只能在一个流上发送。
HTTP/2 性能提升的核心就在于二进制分帧层。HTTP2是二进制协议,他采用二进制格式传输数据而不是1.x的文本格式,二进制协议解析起来更高效。 HTTP / 1 的请求和响应报文,都是由起始行,首部和实体正文(可选)组成,各部分之间以文本换行符分隔。HTTP/2 将请求和响应数据分割为更小的帧,并且它们采用二进制编码。
HTTP/2 中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装。
多路复用
上面提到HTTP/1.x的线头阻塞和多个TCP连接的问题,HTTP2的多路复用完美解决。HTTP/2让所有的通信都在一个TCP连接上完成,真正实现了请求的并发。
在一个 TCP 连接上,HTTP/2可以向服务器不断发送帧,每帧的 stream identifier 的标明这一帧属于哪个流,然后在接收时,根据 stream identifier 拼接每个流的所有帧组成一整块数据。把 HTTP/1.x 每个请求都当作一个流,那么多个请求变成多个流,请求响应数据分成多个帧,不同流中的帧交错地发送给对方,这就是 HTTP/2 中的多路复用。
流的概念实现了单连接上多请求 - 响应并行,解决了线头阻塞的问题,减少了 TCP 连接数量和 TCP 连接慢启动造成的问题
所以 HTTP/2 对于同一域名只需要创建一个连接,而不是像 HTTP/1.x 那样创建 6~8 个连接。
头部压缩
在HTTP/1.x版本中,首部用文本格式传输,通常会给每个传输增加500-800字节的开销。当一个网站请求非常多时,而每个请求带的一些首部字段都是相同的,例如cookie、user-agent等,浪费了很多带宽资源。HTTP/2为此对消息头采用HPACK(专为HTTP/2头部设计的压缩格式)进行压缩传输,能够节省消息头占用的网络的流量。头部压缩需要在浏览器和服务器端之间:
- 维护一份相同的静态字典,包含常见的头部名称,以及常见的头部名称和值的组合
- 维护一份相同的动态字典,可以动态的添加内容
- 通过静态Huffman编码对传输的首部字段进行编码
HTTP/2的静态字典可以查看这里
所以我们在传输首部字段的时候,例如要传输method:GET,那我们只需要传输静态字典里面method:GET对应的索引值就可以了,一个字节搞定。像user-agent、cookie这种静态字典里面只有首部名称而没有值的首部,第一次传输需要user-agent在静态字典中的索引以及他的值,值会采用静态Huffman编码来减小体积。
第一次传输过user-agent 之后呢,浏览器和服务器端就会把它添加到自己的动态字典中。后续传输就可以传输索引了,一个字节搞定。
服务器推送
浏览器发送一个请求,服务器主动向浏览器推送与这个请求相关的资源,这样浏览器就不用发起后续请求。
Server-Push 主要是针对资源内联做出的优化,相较于 HTTP/1.x 资源内联的优势:
- 客户端可以缓存推送的资源
- 客户端可以拒收推送过来的资源
- 推送资源可以由不同页面共享
- 服务器可以按照优先级推送资源
重置
HTTP/1.1的有一个缺点是:当一个含有确切值的Content-Length的HTTP消息被送出之后,你就很难中断它了。当然,通常你可以断开整个TCP链接(但也不总是可以这样),但这样导致的代价就是需要通过三次握手来重新建立一个新的TCP连接。
一个更好的方案是只终止当前传输的消息并重新发送一个新的。在HTTP/2里面,我们可以通过发送RST_STREAM帧来实现这种需求,从而避免浪费带宽和中断已有的连接。
扩展
HTTP/2升级并不完全是没有副作用的,先说结论,HTTP/1.x全升HTTP/2性能不一定能提升,还是需要做一些特殊的优化。
需要把针对HTTP/1.x的优化点摘出来改成对h2友好的,不然会影响性能,比如雪碧图,css,js行内引入,域名打散这些都是针对h1的优化,如果不针对h2做修改,收益可能是负的。
保证你的页面没有那种古老的合并资源请求的优化,比如通过xhr请求多个图片js,html片段再在客户端解析的骚操作。
h2特性在h1上不支持,所以你需要在不支持的h1浏览器里访问站点,来做性能测试,需要成本。
h2对单请求的优化有限,如果做流服务器,可能收益也不大,视频,大图片下载,多路复用也体现不出什么优势。
开启h2之后对ssl的配置可能会更复杂一些,如果不是nginx层代理开启,而是在前端机上比如nodejs服务上开启h2,服务端的改造也比较麻烦,不像静态资源那么开关方便。