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 也逐渐暴露出了一些问题:
线头阻塞(Head-of-line blocking)问题。当一个HTTP请求进入一个TCP连接时,它必须等待前一个请求-响应完成,才能发送。前一个请求-响应花费的时间越多,它等待的时间也就越长,即便它已经准备好了。为解决此问题,HTTP/1.1 提出了 HTTP管线化(HTTP pipelining) 技术,可同时发送多个 HTTP 请求,但服务器依旧按照顺序响应,还是会存在阻塞问题。
浏览器限制TCP连接数。虽然我们可以通过开启多个TCP连接并行的方式,解决线头阻塞的问题,但新的问题又出现了:对于同一个域名,浏览器最多只能同时创建 6~8 个 TCP 连接。并且,频繁地创建销毁TCP连接,依旧会影响性能。
Header 头部内容多,而且没有进行压缩优化。
等等。。。
为了解决这一系列问题,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.
其中,提到了三个重点:
header field compression
multiple concurrent exchanges on the same connection
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 编码,然后分情况判断是否需要存入动态索引表中。
- 若某个键值对已存在,则用相应的索引号代替这个首部项,比如:
3.2. 同一连接上请求并发
3.2.1. 二进制分帧层
二进制分帧层是 HTTP/2 性能增强的核心,存在于应用层 HTTP/2 与传输层 TCP 之间。
它将一个 HTTP/2 请求报文(流)划分成更小的帧(frame),再将多个请求的帧合并成一个新的报文交给TCP。
接收方TCP将报文交给二进制分帧层后,它会将每一帧抽离出来,拼接到对应的 HTTP/2 报文中。
多个 HTTP/2 请求复用一个 TCP 报文,从而实现了并发。相比于 HTTP/1.1 中一个请求独占一个 TCP 报文,这就像数据交换中报文交换到分组交换的改进。
同时,这也与进程并发的思想相同。
3.2.2. 帧
帧的结构如下:
1 | +-----------------------------------------------+ |
主要字段的含义如下:
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 新增的第三个强大新功能就是:服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无需客户端明确地请求。
这是不是类似于 Websocket ?
4. HTTP/1.1 与 HTTP/2 性能对比
测试网站:https://http2.akamai.com/demo