HTTP/2 简介
支撑现有 Web 服务的 HTTP 协议距离其发布时的 1997 年已经有些年月了,随后的 HTTP/1.1 版本发布自 1999 年。随着技术的进步和需求的进化,对于数据快速高效地传输,HTTP/1.1 显得捉襟见肘,大部分优化是在应用层来做的,因为协议是改不了的嘛,比如雪碧图减少请求数,域名共享(Domain sharing)增加同时建立的请求数。 SPDY 与 HTTP/2早在 2009 年,Google 便尝试推出了一个叫 SPDY 的实验性协议来对 HTTP/1.1 进行升级优化。在一个工作了这么久的协议上进行大的改进,如何做到对现有应用影响最小又收效显著,相信每个对历史代码有重构的开发者来说是能够体会的。所以 Google 在尝试对协议进行优化时,其目标是:
在最初的内部实验结果中,页面加载耗时的提升相当显著,达到了 55%。随后该协议在主流浏览器中得以实现,Chrome, Firefix 及 Opera,各大小公司也开始在其服务中部署实施。借着 SPDY 这波势头,HTTP 工作组 (HTTP Working Group/HTTP-WG)以其为基础开始了 HTTP/2 的提案,随后两者并行发展,SPDY 可看成是 HTTP/2 的一个实验分支,在这上面进行新功能开发和测试,然后将稳定的功能提交到 HTTP/2 草案中。 经过长时间的不断测试演进,2015 年 HTTP/2 通过了可发布的标准,是时,Chrome 团队给出了废弃 SPDY 的日程,开始了 HTTP/2 的时代。但其实早在此协议正式通过前,已经有好些浏览器及站点完整地实施了 HTTP/2,享受到了性能上的巨大提升。 HTTP/2 对于现有应用层的东西没有改变,比如 HTTP 动词,状态码,请求响应头。它的变动主要在数据格式及传输上,通过新增的分帧层(Frame layer)将这些变动和应用层隔离,所以对于应用层来说是透明的,现有 Web 应用可以不经任何修改就切到 HTTP/2。 关于版本为何不是 HTTP/1.2为了达到预期的性能优化,HTTP/2 中新增了二进制分帧层,这与现有 HTTP/1.1 协议的服务器及客户端是不兼容的,所以版本号来了个大升级。 除非你是服务器的开发者,需要关注这些底层的实现,否则对于应用来说,这些优化是服务器和客户端替你实现的,无须关心。 如何看站点使用的 HTTP 版本最简单的办法,通过浏览器的开发者工具中的网络面板查看。 Chrome DevTools 网络面板中查看 HTTP 版本 其中 也可使用
$ curl -I <url> 以下是 Bing 中国及百度的返回。 对 Bing 中国使用 curl 查看返回的结果$ curl -I https://cn.bing.com
+ HTTP/2 302
cache-control: private
content-length: 139
content-type: text/html; charset=utf-8
location: https://www4.bing.com/
vary: Accept-Encoding
set-cookie: ...
set-cookie: ...
set-cookie: ...
set-cookie:...
set-cookie: ...
date: Sun, 14 Apr 2019 06:29:36 GMT 对 百度使用 curl 查看返回的结果$ curl -I https://www.baidu.com
+ HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: Keep-Alive
Content-Length: 277
Content-Type: text/html
Date: Sun, 14 Apr 2019 06:30:43 GMT
Etag: "575e1f5c-115"
Last-Modified: Mon, 13 Jun 2016 02:50:04 GMT
Pragma: no-cache
Server: bfe/1.0.8.18 通过返回可以了解到,Bing 中国站点已经启用 HTTP/2,百度使用的是 HTTP/1.1。 二进制分帧层HTTP/2 所有优化中,最关键的技术点莫过于二进制分帧层(Binary framing layer)了,它负责 HTTP 消息在服务器与客户端之间封装及传输。 来自 Web Fundamentals 关于二进制分帧层的示意图 从示意图可看出新引入的分帧层位于 HTTP 应用层与套接字接口(socket interface )之间。上层 HTTP API 相关的部分比如 HTTP 动词,headers 并不受影响,所以做到了既有的应用无需做适配。但真实的 HTTP 消息在传输时,经过分帧层的处理,数据被划分成了更小单位的帧,以二进制的形式传送。所以浏览器与服务器需要处理对这种新增二进制数据的解析。 流,消息与帧进一步了解 HTTP/2 中数据传输前,先来理解一些新的概念。 流/Stream客户端与服务器建立连接后,两者之间一个双向的数据流,也可理解为管道,一次可传输一个或多个消息(Message)。每个流都有唯一标识和一个可选的优先级。 消息/Message一序列有序的帧(Frame),对应一个完整请求(Request)或响应(Response)数据。 帧/FrameHTTP/2 中数据传输的最小单位,每一帧都包含一个头部(frame header)以标识该帧属于哪个流(Stream)。同时帧里还会承载特定数据的传输,比如请求头,请求数据,响应头,响应数据。 来自 Web Fundamentals 关于 HTTP/2 中一个 TCP 链接的示意图 所有通信都通过一个 TCP 链接进行,其中可承载任意数量的流。 来自不同流的帧数据在 TCP 连接中是混合着传输的,这使得帧在传输时是非常高效的,数据到达后会通过其中的头部来重新进行组装,还原到原来所属的流中。上图中可看出可各术语的包含关系。 简单理解,HTTP/2 中将通信数据切分成一小片一小片的帧并以二进制形式传输,在单个 TCP 中多路复用(multiplexed)。这是 HTTP/2 提供的基础特性,为其他性能优化提供了技术上的保障。 请求与响应的多路复用HTTP/1.1 协议下,客户端如果想并行加载资源,需要建立多个 TCP 连接,每个请求单独一个。这样的效率是不高的,比如 header 中的 cookie 冗余,客户端能够同时发起的链接也有限,建立 TCP 连接成本很大,然而这个建立好的连接却没有被充分利用,只能返回一个响应。 HTTP/2 中通过将请求数据分帧,在同一个 TCP 链接中交错传输,在接收方再重新将分片的数据组装回来,同时数据的传输双向同时并行,提高了连接的利用效率,也避免了建立多个 TCP 连接的资源和时间消耗。 来自 Web Fundamentals 关于 HTTP/2 协议下连接的示意图 上面示例中,服务器通过 stream 1 和 stream 3 向客户端发送数据,同时客户端也在通过 stream 5 向服务器发送数据。 HTTP/2 提供的这种分帧数据传输带来了一系列建立在其之上的优化:
流处理的优先级来自不同请求的数据都在同一个连接中双向并行传输,这些帧的顺序及优先级该如何设置才能让信息处理得高效,这成了一个问题的关键。所以 HTTP/2 中为流提供了权重(weight)和依赖两个属性:
结合以上两个因素可以分析形成一个对流数据的优先级树(prioritization tree),根据它客户端可做出如何接收数据的最佳决策,服务器也可以根据它来决定如何优先处理哪些请求,进行合理的 CPU ,内存及其他资源的分配。 来自 Web Fundamentals 关于优先级树的示意图 前面介绍流的时候有提到其身上有个唯一的身分标识,通过这个标识,一个流可指定依赖于另一个流,形成父子关系。如果流没有指定依赖,默认依赖于根节点(root stream)。这个依赖关系很好理解,就是被依赖的流数据应该优先被处理及返回,想象一下页面中 JS 资源的相互依赖。 多个流数据所依赖的流相同,即拥有同一个父级,则根据他们的权重来决定处理的优先级和资源分配。 比如上图中第一个情况,权重值为 12 流 A 和权重为 4 的流 B 都没有显式指定依赖,他们隐式依赖于 root stream,在处理他们优先级时 A 为 12/(12+4),B 为 4/(12+4)。 第二个情况下,D 先于 C 拥有全部优先级,处理完成后才是 C。其他情况推而广之同样的逻辑。 更重要的是,这些优先级可随时根据情况而动态调整。比如浏览器可根据用户的交互动态调整资源的优先级,重新分配,以达到资源加载的最优效果。 单个源对应一个持久的连接基于 HTTP/2 将所有请求分片后在一个连接中传输处理的特点,对于一个域(origin),只需要建立一个长久的连接即可。大多数 HTTP 通信是瞬时的,而 TCP 是为长连接批量数据传输而设计。通过始终复用该连接,可以达到减少重复建立连接的不必要资源消耗,极大地减少网络时延,同时也会减少持有多个连接带来的内存等资源的消耗。 流的控制流的控制是这么一种机制,它防止在接收方已经无暇接收或疲于处理时,发送方仍不断发送数据。比如服务器压力过大,无法处理更多请求时;比如浏览器中请求视频资源,但用户点击了暂停,不想再继续加载后续的数据;或链路中有多层代理时,不同节点处理能力不一致,需要平衡一下数据传输量。 TCP 中是有流控制机制的,但无法满足 HTTP/2 中分帧情况下的控制需求,因此 HTTP/2 提供了一些基础设施(building blocks)让浏览器和服务器可实现自己的控制逻辑,或更加底层的控制:
服务端推送另一个 HTTP/2 带来的特性便是允许服务器向单个请求发送多次响应。典型的场景便是 Web 网页。浏览器请求网页后分析其中的资源,然后再请求相应的其他资源。这一过程完全可以省掉,转而由服务器一并发送。因为文档在服务器这边,服务器是知道该页面还需要哪些资源的。下图展示了对于客户端的请求,服务器推送了额外资源的情况。 来自 Web Fundamentals 的插图展示了服务器推送 服务器通过发送 头部信息的压缩每次 HTTP 的数据传输都包含了大量头部以描述请求的目的和传送的资源的属性,这些 header 通过普通字符串的形式发送,带上 cookie 时其大小可达几 Kb 。HTTP/2 中使用 HPACK 格式来对这些头信息进行压缩优化:
来自 Web Fundamentals 的插图展示 header 压缩的工作流程 从对上图的理解可以看出,新发送的头部只需要包含变更的部分,而剩余的部分可通过本地索引找出,无须再次发送。 一个本地示例是时候 show me the code 了。下面的示例来自 Node.js 中 HTTP/2 的文档。 服务端代码: server.js const http2 = require('http2');
const fs = require('fs');
因为浏览器不支持未加密的 HTTP/2,所以需要创建本地证书来启用 HTTPS。 通过如下命令创建本地证书。 $ openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \
-keyout localhost-privkey.pem -out localhost-cert.pem 然后启动服务器就可以在浏览器中访问查看了。 $ node node.js 打开浏览器访问 本地 Node.js 搭建的 HTTP/2 示例 也可通过 curl 工具访问查看: $ curl -ki https://localhost:8443
这里 相关资源 |
![](https://licensebuttons.net/l/by-nc-sa/3.0/88x31.png)