HTTP/2 in Action
Barry Pollard
1.1 万维网的原理
IoT(Internet of Things,物联网)
因特网是使用 IP(Internet Protocol,因特网协议)连接在一起实现消息传递的计算机构成的网络。因特网上有很多服务,包括万维网,以及电子邮件、文件共享、因特网电话等。
Tim Berners-Lee 当初发明万维网时,一共创造了三项核心技术,除了传输数据的 HTTP,还有用于标识唯一资源的 URL(Uniform Resource Locator,统一资源定位符)和用于构造网页的 HTML(Hypertext Markup Language,超文本标记语言)。
而 IoT 设备的原理简单来说,就是通过 HTTP 调用暴露服务,从而让其他设备(计算机、手机应用,乃至其他 IoT 设备)使用
IP 用于直接通过因特网传输数据(这也是 Internet Protocol 这个名字的含义),而 TCP 增加了稳定性与重传机制以确保连接可靠
标准端口(80 用于 HTTP,443 用于 HTTPS
1.2 什么是 HTTP
OSI 模型(Open System Interconnection,开放式系统互联通信参考模型)是经常用来描述网络分层的概念模型。
从本质上来讲,HTTP 是一个请求-响应协议
为什么在 HTTP/0.9 请求中还需要声明 GET,不显得多此一举吗?后来的 HTTP 版本中引入了其他方法,所以请给 HTTP 的发明者们送上掌声,他们预见到了会有更多的请求方法。
1.3 HTTP 的语法和历史
另一个区别是 GET 请求是幂等的,而 POST 请求不是。这意味着对于同一个 URL 的多个 GET 请求,应始终返回相同的结果;而对于同一 URL 请求的多个 POST 请求,则可能不会返回相同的结果
HTTP 首部使用首部名称、冒号和首部内容表示。根据规范,首部名称(而不是内容)不区分大小写。
状态码和描述是 HTTP/1.0 中的新概念。在 HTTP/0.9 中,没有响应码这样的概念,错误只能在返回的 HTML 本身中给出。
从 0.9 到 1.0 是一个较大的变化,增加了 HTTP 首部。HTTP/1.1 做了进一步的改进,以便充分利用 HTTP 协议(例如,持久连接、强制响应首部、更好的缓存选项和分块编码)。
强制首部和持久连接是 HTTP/1.0 语法的两个显著变化。
强制添加 Host 首部
将 Host 作为必选项是 HTTP/1.1 的重要改进,这使得服务器能够充分利用虚拟主机托管技术,
持久连接(也就是 KEEP-ALIVE)
在此基础上,HTTP/1.1 增加了管道的概念,因此应该可以通过同一个持久连接发送多个请求并按顺序获取响应。
但大多数 HTTP/1.1 的实现仍然是遵循请求响应再请求再响应的模式的。当一个请求被处理时,HTTP 连接被阻塞,不能用于其他请求。
除了在 HTTP/1.0 中定义的 GET、POST 和 HEAD 方法之外,HTTP/1.1 又定义了新的方法,如 PUT、OPTIONS 和比较少见的 CONNECT、TRACE 及 DELETE。
在 HTTP/1.1 中引入的 Cache-Control HTTP 首部比 HTTP/1.0 中的 Expires 首部的选项更多。- HTTP cookies,允许 HTTP 维护状态。- 引入字符集(如本章的一些例子所示),在 HTTP 响应中新增语言选项。- 支持代理。- 支持权限验证。- 新的状态码。- 尾随首部(在 4.3.3 节中讨论)。
曾经有一个惯例,是在这些首部中包含一个 X-来表明它们没有被正式标准化(X-Content-Type、X-Frame-Options 或 X-XSS-Protection),但这个约定已经不推荐使用了[21],这就导致新的实验性首部很难与 HTTP/1.1 规范中的首部区分开来。
1.4 HTTPS 简介
HTTPS 是 HTTP 的安全版本,它使用 TLS(Transport Layer Security,传输层加密)协议对传输中的消息进行加密,TLS 的前身是我们熟知的 SSL(Secure Sockets Layer,安全套接字层),如
加密——传输过程中第三方无法读取消息。• 完整性校验——消息在传输过程中未被更改,因为整个加密消息已经过数字签名,并且该签名在解密之前已通过加密验证。• 身份验证——服务器不是伪装的。
HTTPS 使用公钥加密,服务器在用户首次连接时以数字证书的形式提供公钥。你的浏览器使用此公钥加密消息,只有服务器可以解密,因为只有它拥有配对的私钥。
数字证书由浏览器信任的各种 CA(Certificate Authorities,证书颁发机构)发布并进行数字签名,这就是为什么可以验证公钥是否适用于你要连接的服务器。HTTPS 的一个重大问题是,它只保证你正在连接到该服务器,而不能保证服务器值得信任。使用相似的域名(如 exmplebank.com、examplebank.com),即使使用 HTTPS,也可以轻松设置虚假钓鱼站点。HTTPS 站点通常在 Web 浏览器中显示为绿色挂锁,许多用户认为这意味着安全,但其实它仅仅意味着加密。
HTTPS 是基于 HTTP 构建的,几乎可以与 HTTP 协议无缝衔接。它默认在不同的端口上服务(使用 443 端口,而 HTTP 的默认端口是 80)
公钥加密很慢,因此公钥仅用于协商共享密钥。为了更好的性能,可用共享密钥加密后续的消息。)第 4 章(4.
第 2 章 通向 HTTP/2 之路
速度是相对的,1ms300 公里意味着从江西到上海只要 2ms,这非常快。但在网络世界中,优化的就是秒级,毫秒级别的时间。
延迟是影响网络浏览的关键因素,延迟从根本上受到光速的限制
延迟是影响网络浏览的关键因素,延迟从根本上受到光速的限制
2.1 HTTP/1.1 和当前的万维网
HTTP 协议在设计之初并未考虑到资源的这种巨量增长,协议的简单性设计不可避免地遇到了一些根本上的性能问题。
超过 80%的时间用于等待消息在网络上传送。在此期间,Web 浏览器和 Web 服务器都没有做太多事情,这段时间被浪费了,这是 HTTP 协议的一个主要问题
现代互联网最大的问题之一是延迟而不是带宽。延迟是将单个消息发送到服务器所需的时间,而带宽是指用户可以在这些消息中下载多少内容。
HTTP/1.1 尝试引入管道化,从而在收到响应之前并发发出请求,实现并行发送请求。初始的 HTML 仍然需要单独请求,但是当浏览器知道它需要两个图像时,它可以先后连续发出两个请求。
2.2 解决 HTTP/1.1 性能问题的方案
HTTP/1.1 不是一种高效的协议,因为它为等待响应会阻塞发送。导致在当前请求完成之前,无法发送另一个请求。
已经有各种突破 HTTP/1.1 的性能限制的技术,这些技术分为以下两类:• 使用多个 HTTP 连接。• 合并 HTTP 请求。
为了进一步突破 6 个连接的限制,许多网站从子域(例如,static.example.com)提供静态资源,如图像、CSS 和 JavaScript,Web 浏览器从而可以为每个新域名打开另外 6 个连接。这种技术称为域名分片
当开启多个 HTTP 连接时,客户端和服务器都有额外的开销:打开 TCP 连接需要时间,维护连接需要更多的内存和 CPU 资源。
开启多个 HTTP 连接的主要问题是,没有充分利用底层的 TCP 协议。
需要三次网络消息传递(或者 1.5 次往返),发生在发送 HTTP 请求之前。
TCP 在开启连接时比较小心,在确认网络不拥堵之前只会发送比较少的数据包。CWND(Congestion Window,拥塞窗口)随着时间的推移逐渐增加,只要连接没发现丢包,就可以处理更大的流量。TCP 拥塞窗口的大小受 TCP 慢启动算法控制。
所以,在 CWND 比较小时,可能需要多个 TCP ACK 消息才能发出一个完整的 HTTP 请求。因为 HTTP 响应常常比请求大很多,所以它同样也会受到拥塞窗口的影响。由于 TCP 连接的应用更加广泛,所以人们提升了拥塞窗口的大小以使它更高效,但是在创建它时是给它限流了的
就算没有 TCP 建立连接的开销和慢启动的问题,使用多个独立的连接也可能导致带宽问题。例如,如果所有带宽都用掉了,就会导致 TCP 超时,和其他的连接上的重传。在这些独立的连接之间,没有优先级的概念,这就无法更高效地利用带宽。
所以,在 TCP 和 HTTPS 的层面,开启多个连接并不高效,尽管在 HTTP 的层面这么做是一种很棒的优化。这个解决 HTTP/1.1 延迟问题的方案需要更多额外的请求和响应,所以这个方案反而可能会导致延迟问题,这本是它应该解决的问题。
开启多个 TCP 连接并不是解决 HTTP/1 问题的满意方案,尽管在没有更好的解决方案时,它确实可以提升性能。另外,这也说明了为什么浏览器限制每个域名开启的连接数为 6。尽管可以增大这个数字(一些浏览器允许这么做),但考虑到每个连接的额外开销,收益会比较低。
对于图片来说,这种打包技术叫作精灵图。
图片可以包含在 CSS 中,通过行内 SVG,或者转换为 Base64 编码,也能减少 HTTP 请求数。
这个方案的主要问题是它引入的复杂度
另外一个问题是,合并会导致文件的浪费
无论是从网络的方面(特别在开始的时候,TCP 启动慢)还是从浏览器执行的方面(浏览器需要处理它不需要的代码)来说,这种技术都不够高效。
2.3 HTTP/1.1 的其他问题
HTTP 使用文本格式带来的另外一个问题是,HTTP 消息较大,这是因为不能高效编码数据(比如使用数字来表达 Date 首部,而不是使用人类可读的完整文本),而且首部内容也有重复
例如,就算只有主页需要 cookie,每个发向服务器的 HTTP 请求中都会包含 cookie。
同样 HTTP 响应的首部也变得庞大了许多,像 Content-Security-Policy 这种关于安全的首部,会导致 HTTP 首部非常大,从而使效率低下的问题越来越突出。很多网站需要加载上百个资源,庞大的 HTTP 首部可能会带来几十甚至上百 KB 的数据传输。
除此之外,它还存在纯文本协议的安全和隐私问题(HTTPS 加密很好地解决了这个问题),以及缺少状态的问题(cookie 在一定程度上解决了这个问题)。
2.4 实际案例
浏览器不需要等整个 HTML 页面下载处理完才开始下载其他资源,它只要发现域名引用就开启新的资源请求(尽管由于连接创建延迟,新的资源在 HTML 下载完成时还没有进入下载流程)。
。由于 HTTP/1.1 在一个连接上同一时间只允许执行一个请求,因此浏览器创建了另外一个连接。这次省去了 DNS 查询时间(通过请求 2,已经知道域名的 IP 地址了),但是在请求 CSS 之前,还需要进行耗时的 TCP/IP 连接创建和 HTTPS 协商。
2.5 从 HTTP/1.1 到 HTTP/2
所以 SPDY 变成了一个二进制协议
2012 年 11 月,基于 SPDY 发布了 HTTP/2 初稿[
在 2014 年底,HTTP/2 规范作为互联网的标准被提出,而在 2015 年 5 月,被正式通过,这就是 RFC 7450[22]。
2.6 HTTP/2 对 Web 性能的影响
在 HTTP/1 下对多连接的使用,自然就形成了请求排队机制,因为同时只有 6 个请求。HTTP/2 以流的形式,只使用一个连接,在理论上没有同时只能发送 6 个请求的限制,但具体的实现不受约束,可以添加其他限制
从理论上讲,只要支持 HTTP/2,网站可以轻易获得性能提升,不需要再使用域名分片、CSS 合并、JavaScript 合并、精灵图等技术。
4.1 为什么是 HTTP/2 而不是 HTTP/1.2
- 二进制协议
- 多路复用
- 流量控制功能
- 数据流优先级
- 首部压缩
- 服务端推送
不向前兼容
HTTP/2 和 HTTPS 有很多相似点,它们都在发送前将标准 HTTP 消息用特殊的格式封装,在收到响应时再解开。
HTTP/1 和 HTTP/2 的主要区别之一是,HTTP/2 是一个二进制的、基于数据包的协议,而 HTTP/1 是完全基于文本的。
使用基于文本的协议,要先发完请求,并接收完响应之后,才能开始下一个请求
分块编码和管道化都有队头阻塞(HOL)的问题——在队列首部的消息会阻塞后面消息的发送,更不用说,管道化在实际应用中并没有得到很好的支持。
HTTP/2 变成了一个完全的二进制协议,HTTP 消息被分成清晰定义的数据帧发送。所有的 HTTP/2 消息都使用分块的编码技术
HTTP/1 是一种同步的、独占的请求-响应协议。客户端发送 HTTP/1 消息,然后服务器返回 HTTP/1 响应。
HTTP/1 的主要解决方法是打开多个连接,并且使用资源合并以减少请求数
HTTP/2 允许在单个连接上同时执行多个请求,每个 HTTP 请求或响应使用不同的流。通过使用二进制分帧层,给每个帧分配一个流标识符,以支持同时发出多个独立请求。当接收到该流的所有帧时,接收方可以将帧组合成完整消息。
HTTP/2 连接在请求发出后不需要阻塞到响应返回(如第 2 章中所述,HTTP/1.1 会阻塞)
服务器发送响应的顺序完全取决于服务器,但客户端可以指定优先级。如果可以发送多个响应,则服务器可以进行优先级排序,先发送重要资源(例如 CSS 和 JavaScript),然后是不太重要的资源(例如图像)。
每个请求都有一个新的、自增的流 ID(如图 4.2 中所示的流 5、7 和 9)。返回响应时使用相同的流 ID,也就是说和 HTTP 连接一样,流是往返的。响应完成后,流将被关闭。
为了防止流 ID 冲突,客户端发起的请求使用奇数流 ID(这就是为什么我们在前面的图中使用的流 ID 为 5、7 和 9,在此连接上已经使用过流 1 和流 3),服务器发起的请求使用偶数流 ID。
HTTP/2 使用多个二进制帧发送 HTTP 请求和响应,使用单个 TCP 连接,以流的方式多路复用。
HTTP/2 与 HTTP/1 的不同主要在消息发送的层面上,在更上层,HTTP 的核心概念不变。
流的优先级控制是通过这种方式实现的:当数据帧在排队时,服务器会给高优先级的请求发送更多的帧。
使用优先级决定给每个响应分配多少资源。
在连接层,TCP 支持限流,但 HTTP/2 要在流的层面实现流量控制
HTTP/2 引入了首部压缩的概念,但是它使用了和正文压缩不同的技术。该技术支持跨请求压缩首部,这可以避免正文压缩所使用算法的安全问题。
HTTP/2 添加了服务端推送的概念,它允许服务端给一个请求返回多个响应
4.2 如何创建一个 HTTP/2 连接
HTTP/2 规范文档[3]提供了三种建立 HTTP/2 连接的方法(但是目前已经有了第 4 种方法,见 4.2.4
使用 HTTPS 协商。• 使用 HTTP Upgrade 首部。• 和之前的连接保持一致。
公钥私钥加密被称为非对称加密,因为它加密和解密消息时使用不同的密钥
消息通过服务端才有的私钥加密,使用证书中的公钥解密消息,通过这种方式来确认服务器身份。
通过发送 Upgrade 首部,客户端可以请求将现有的 HTTP/1.1 连接升级为 HTTP/2。这个首部应该只用于未加密的 HTTP 连接(h2c)。基于加密的 HTTPS 连接的 HTTP/2(h2)不得使用此方法进行 HTTP/2 协商,它必须使用 ALPN。
4.3 HTTP/2 帧
HTTP/2 消息由数据帧组成,通过在一个连接上的多路复用的流发送。
HTTP/2 帧与可变长的 HTTP/1 文本消息不同,对于后者,必须通过扫描换行符和空格来解析 —— 这个过程效率低且容易出错。HTTP/2 帧格式更严格,定义更清晰。
5.1 什么是 HTTP/2 服务端推送
HTTP/2 服务端推送(以下称为 HTTP/2 推送)允许服务器发回客户端未请求的额外资源。
HTTP/2 推送打破了 HTTP“一个请求=一个响应”的惯例
HTTP/2 中不是真正的双向流,一切都仍然是从客户端请求发起的。推送资源是为了响应初始请求而做出的额外响应。完成该初始请求后,流会被关闭,除非客户端发起其他请求,否则不能推送其他资源
5.3 HTTP/2 推送在浏览器中如何运作
资源不是被直接推到网页中,而是被推到缓存中。跟平常一样处理网页。当页面知道它需要什么资源时,它先查看缓存,如果发现缓存中有,就直接从缓存中加载,而不需要向服务端请求。
7.1 流状态
实际上,HTTP/2 连接比 HTTP/1 连接开销更高,因为在它上额外添加了 HTTP/2“魔法”前奏消息,并且在发送请求之前还至少要发送一个 SETTINGS 帧。但 HTTP/2 的流开销更低。
7.2 流量控制
在 HTTP/2 下,使用由多个流组成的多路复用的连接,所以只使用连接层的流量控制就不够了。不仅需要连接层的控制,还需要流层级的控制
HTTP/2 中的流量控制和 TCP 方法类似。在连接开始时(使用 SETTINGS 帧),确定流量控制窗口大小(如果不指定,默认为 65 535 个 8 位字节)。然后每次都会从总量中减去发送的数据的大小,而后再将接收到的响应数据(通过 WINDOW_UPDATE 帧)大小加回去
具体什么时候发送 WINDOW_UPDATE 帧(在每个 DATA 帧被消费完?接近限制的时候?还是周期性的?)取决于客户端。
7.3 流优先级
HTTP/2 定义了两种不同的方法来设置优先级:
- 流依赖
- 流权重
一个流可以依赖于另外一个流,只有当所依赖的流不需要使用连接来发送数据时,这个流才可以开始发送资源
使用流优先级的目的是尽量高效利用连接,而不是作为一种阻塞机制。
另外一个有助于定义流优先级的概念是流权重,其用于给两个依赖同一个父资源的请求设定优先级。
8.1 为什么需要首部压缩
压缩和解压缩数据需要时间和处理器性能,但相比于所需要的网络传输时间,计算所需要的时间相当短,所以在网络上发送数据之前进行压缩几乎总是有用的。同时,HTTPS 也要求加密,这比压缩更需要计算资源。压缩之后再加密小的数据更好一些。
8.2 压缩的运作方式
• 查表法• 更高效的编码技术• lookback(反查)压缩
Huffman 编码是更进一步的可变长度编码。它的工作原理是根据每个值的使用频率为其分配一个唯一代码,并且保证没有一个代码是其他代码的前缀。
8.4 HTTP/2 的 HPACK 首部压缩
HTTP 工作组制定了一个新的规范,叫作 HPACK(不是简写),它基于查询表和 Huffman 编码,但(关键)不是基于反查的压缩方法。
9.1 TCP 的低效率因素,以及 HTTP
TCP 给每个 TCP 数据包分配一个序列号,如果数据包在到达时顺序不对,则进行重排;如果丢失了部分数据包,则根据序列号来重新请求。TCP 通过这种方式来确保数据完整性。TCP 基于 CWND(拥塞窗口,这也构成了 HTTP/2 流量控制工作原理的基础,见第 7 章)传输数据,因此能够发送的最大数据量取决于拥塞窗口的大小,发出数据减小窗口大小,接收到数据再把窗口大小加大。开始时窗口比较小,只要网络能够处理那些增加的负载,它就随着时间增长。如果客户端的处理速度跟不上,窗口就会减小。这个流程运行得相当好,这也是为什么 TCP/IP 成为了互联网的基石的原因。然而,TCP 运行的基本方式会导致 5 个主要的问题,它们至少影响到了 HTTP:
- 有一个连接创建的延迟。要在连接开始时协商发送方和接收方可以使用的序列号。
- TCP 慢启动算法限制了 TCP 的性能,它小心翼翼地处理发送的数据量,以尽可能防止重传。
- 不充分使用连接会导致限流阈值降低。如果连接未被充分使用,TCP 会将拥塞窗口的大小减小,因为它不确定在上个最优的拥塞窗口之后网络参数有没有发生变化。
- 丢包也会导致 TCP 的限流阈值降低。TCP 认为所有的丢包都是由窗口拥堵造成的,但其实并不是。
- 数据包可能被排队。乱序接收到的数据包会被排队,以保证数据是有序的。这些问题在 HTTP/2 下依然存在,其中一些问题正是为什么在 HTTP/2 下使用单个 TCP 连接更好的原因。然而,在某些丢包的情况下,最后两个问题会导致 HTTP/2 比 HTTP/1.1 更慢。
TCP 慢启动机制可以感知 TCP 在网络上的最佳吞吐量,以免过大冲垮或者危害到网络。
HTTP/2 在 HTTP 层解决了队头阻塞(HOL)的问题,因为有多路复用,单个响应的延迟不会影响其他资源使用当前的 HTTP 连接。但是,在 TCP 层队头阻塞依然存在。一个流的丢包会直接影响到其他所有的流,尽管它们可能不需要排队。
当网络持续有 2%的丢包时,HTTP/2 的表现总比 HTTP/1.1 要差。当然,持续 2%的丢包说明网络环境非常差,大多数时候是偶尔丢包,而不是这样持续的丢包。但这个实验说明,HTTP/2 不是能解决所有场景下问题的银弹。
感谢多路复用,HTTP 队首阻塞在 HTTP/2 中已经不是一个问题了,但是 TCP 的队头阻塞却成了问题,特别是在容易丢包的环境下。
虽然不推荐更改设置,操作系统内核发布时,会将 TCP 设置调整到最佳的状态。
- 提高初始拥塞窗口大小
- 支持窗口缩放
- 使用 SACK
- TCP 可以使用 SACK(Selective Acknowledgment,选择性响应)响应乱序的数据包,以避免丢包时重传。
- 禁止重启慢启动
- 使用 TFO TFO(TCP Fast Open,TCP 快速打开)允许使用 TCP 三次握手的初始 SYN 部分发送初始数据包。可以使用这种方法避免与 TCP 相关的连接创建延迟(见 9.1.1 节)。出于安全的原因,这个数据包只能在 TCP 重连时使用,而不能在初次连接时使用,它同时需要客户端和服务端的支持。
使用拥塞控制算法,PRR 和 BBRTCP 有多种拥塞控制算法可以控制丢包时 TCP 的反应。
9.2 QUIC
QUIC(发音同 quick)是 Google(又是 Google)发明的一个基于 UDP 的协议,目标是替换 TCP 和 HTTP 栈中的某些部分,以解决本章中提到的低效率因素。
QUIC 是一个名字,不是缩写。”
创建 QUIC 时考虑到了以下特性:
- 大量减少连接创建时间。
- 改善拥塞控制 。
- 多路复用,但不要带来队头阻塞。
- 前向纠错。
- 连接迁移。
FEC(Forward Error Correction,前向纠错)试图通过在邻近的数据包中添加一个 QUIC 数据包的部分数据来减少数据包重传的需求。这个想法是,如果只丢了一个数据包,那应该可以从成功传送的数据包中重新组合出该数据包。
连接迁移旨在减少连接创建的开销,它通过支持连接在网络之间迁移来实现。在 TCP 下,两端的 IP 地址和端口决定一个连接。更改 IP 地址需要建立新的 TCP 连接。
QUIC 取代了 TCP 提供的大多数功能(创建连接、可靠性和拥塞控制部分),取代了 HTTPS 的全部(降低了创建延迟),甚至还有 HTTP/2 的一部分(流量控制和首部压缩)功能。
UDP 数据包乱序到达,上层的应用程序仍会看到它们。UDP 最初用于不需要可靠传输的应用程序(例如视频,其中一些帧可以丢失而不太会影响服务质量)。
UDP 是一种基础协议,也在内核中实现。在它之上的任何东西都需要在应用层中构建,也就是所说的用户空间。