grpc进阶-HTTP/2

0. 前言

面试的时候,面试官问我 gRPC 所使用的 HTTP/2 协议有何优势,我支支吾吾没答好。

面试结束后,深刻意识到自身的问题:学习新技术框架,常常不求甚解,缺乏刨根问底的精神。

因此,抽空好好学习一下 HTTP/2 。

也借此勉励各位,千万不能只会 Hello world

1. HTTP/1.0 和 HTTP/1.1

1.1. HTTP/1.0 存在的问题

众所周知,HTTP/1.0 存在如下问题:

  • 非持续连接:一个TCP连接只允许一个请求-响应,HTTP请求结束后,TCP连接就关闭。

当同时有多个HTTP请求时,会造成TCP连接频繁地创建和销毁(三次握手、四次挥手),十分影响性能。

为了解决这个问题,HTTP/1.1 提出了长连接 Keep-Alive :多个HTTP请求可复用一个TCP连接,当没有HTTP请求时,TCP连接还会保持一段时间才关闭。

1.2. HTTP/1.1 存在的问题

但随着网站的请求越来越多,对网络通信性能要求越来越高,HTTP/1.1 也逐渐暴露出了一些问题:

  1. 线头阻塞(Head-of-line blocking)问题。当一个HTTP请求进入一个TCP连接时,它必须等待前一个请求-响应完成,才能发送。前一个请求-响应花费的时间越多,它等待的时间也就越长,即便它已经准备好了。为解决此问题,HTTP/1.1 提出了 HTTP管线化(HTTP pipelining) 技术,可同时发送多个 HTTP 请求,但服务器依旧按照顺序响应,还是会存在阻塞问题。

  2. 浏览器限制TCP连接数。虽然我们可以通过开启多个TCP连接并行的方式,解决线头阻塞的问题,但新的问题又出现了:对于同一个域名,浏览器最多只能同时创建 6~8 个 TCP 连接。并且,频繁地创建销毁TCP连接,依旧会影响性能。

  3. Header 头部内容多,而且没有进行压缩优化。

  4. 等等。。。

为了解决这一系列问题,HTTP/2 出现了。

2. HTTP/2 简史

2009年,谷歌公布了一个实验性协议——SPDY,用于解决 HTTP/1.1 的性能问题。

2012年,察觉到 SPDY 协议的趋势,HTTP工作组开始征集 HTTP/2 的建议,并基于 SPDY 制定了第一个 HTTP/2 草案。

2015年,IESG 批准 HTTP/2 和 HPACK 草案,RFC 7540 (HTTP/2) 和 RFC 7541 (HPACK) 发布。

3. HTTP/2 简介

官方文档:RFC 7540

官方介绍:

HTTP/2 enables a more efficient use of network resources and a reduced perception of latency by introducing header field compression and allowing multiple concurrent exchanges on the same connection. It also introduces unsolicited push of representations from servers to clients.

其中,提到了三个重点:

  1. header field compression
  2. multiple concurrent exchanges on the same connection
  3. unsolicited push of representations from servers to clients

以下,将从这三点依次介绍。

3.1. 头部压缩

在 HTTP/1.1 中,头部数据以文本形式传输(ASCII编码:一个字符占用一个字节)。

而在 HTTP/2 中,则采用HPACK 算法进行压缩。

HPACK 算法原理大致如下(参考博客:HTTP2 详解):

  • 每个TCP连接都维护着一个静态索引表(static table)动态索引表(Dynamic table),静态索引表是固定的,动态索引表初始为空。索引表的每一项是一个键值对。

  • 对于 HTTP/2 报文头部的每一项(键值对),匹配当前静态索引表和动态索引表:

    • 若某个键值对已存在,则用相应的索引号代替这个首部项,比如::method: GET 匹配到静态索引表中的第2项,传输时只需要传输一个包含 2 的字节即可;
    • 若索引空间中不存在,则用字符编码传输,字符编码可以选择Huffman 编码,然后分情况判断是否需要存入动态索引表中。

1

3.2. 同一连接上请求并发

3.2.1. 二进制分帧层

二进制分帧层是 HTTP/2 性能增强的核心,存在于应用层 HTTP/2 与传输层 TCP 之间。

1

它将一个 HTTP/2 请求报文()划分成更小的帧(frame),再将多个请求的帧合并成一个新的报文交给TCP。

接收方TCP将报文交给二进制分帧层后,它会将每一帧抽离出来,拼接到对应的 HTTP/2 报文中。

多个 HTTP/2 请求复用一个 TCP 报文,从而实现了并发。相比于 HTTP/1.1 中一个请求独占一个 TCP 报文,这就像数据交换中报文交换到分组交换的改进

同时,这也与进程并发的思想相同。

3.2.2. 帧

帧的结构如下:

1
2
3
4
5
6
7
8
9
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+

主要字段的含义如下:

  • Length:帧的长度。
  • Type:帧的类型。
  • Stream Identifier:流标识,指明该帧属于哪一个报文(流)。
  • Frame Payload:主体内容,由 Type 决定,其中包含了所承载的数据。

更详细的字段解析,可查看博客和官方文档。

3.2.3. 多路复用

这也就是上文所提到的:发送方将 HTTP/2 报文(流)分解成不同的帧,并将属于不同 HTTP/2 报文(流)的帧合并在一个 TCP 报文中,然后发送,最后接收方再把它们重新组装起来。

正是由于 HTTP/2 的多路复用,对于同一个域名,浏览器只需创建一个 TCP 连接

3.2.4. 优先级

由于可以同时发送多个 HTTP/2 报文的帧,那么,优先发送哪些报文的帧,就成了一个问题。

在 HTTP/2 中,每个 HTTP/2 报文(流)都可以被分配优先级,优先级高的先发送。

3.2.5. 流量控制

由于接收方需要缓存每个报文已接受的帧(所有帧到达后,再拼接起来),所以,为了防止发送方发送过快过多,导致接收方缓存溢出,HTTP/2 提供了流量控制。

这与 TCP 流量控制 大同小异。

3.3. 服务端推送

HTTP/2 新增的第三个强大新功能就是:服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无需客户端明确地请求。

1

这是不是类似于 Websocket ?

4. HTTP/1.1 与 HTTP/2 性能对比

测试网站:https://http2.akamai.com/demo

1

参考博客