文章目录
- gRPC基础操作之HTTP2.0【1】
- [一、Http2.0 概述](#一、Http2.0 概述)
-
- [1.1 概述](#1.1 概述)
- [1.2 HTTP/2 诞生背景:为何需要升级 HTTP/1.1?](#1.2 HTTP/2 诞生背景:为何需要升级 HTTP/1.1?)
- 二、HTTP2.0核心特性
-
- [2.1 核心基础:二进制传输(Binary Framing)](#2.1 核心基础:二进制传输(Binary Framing))
- [2.2 核心机制一:多路复用(Multiplexing)](#2.2 核心机制一:多路复用(Multiplexing))
- [2.3 核心机制二:头部压缩(HPACK)](#2.3 核心机制二:头部压缩(HPACK))
-
- [2.3.1 静态字典(Static Dictionary](#2.3.1 静态字典(Static Dictionary)
- [2.3.2 动态字典(Dynamic Dictionary)](#2.3.2 动态字典(Dynamic Dictionary))
- [2.3.3 哈夫曼编码(Huffman Coding)](#2.3.3 哈夫曼编码(Huffman Coding))
- [2.3.4 压缩效果示例](#2.3.4 压缩效果示例)
- [2.4 核心机制三:流控(Flow Control)](#2.4 核心机制三:流控(Flow Control))
-
- [2.4.1 流控的核心规则](#2.4.1 流控的核心规则)
- [2.4.2 流控的工作流程](#2.4.2 流控的工作流程)
- [2.5 核心机制四:服务器推送(Server Push)](#2.5 核心机制四:服务器推送(Server Push))
-
- [2.5.1 服务器推送的核心逻辑](#2.5.1 服务器推送的核心逻辑)
- [2.5.2 推送场景示例](#2.5.2 推送场景示例)
gRPC基础操作之HTTP2.0【1】
一、Http2.0 概述
1.1 概述
HTTP/2(Hypertext Transfer Protocol Version 2)是 HTTP 协议的重大升级版本,由 IETF 于 2015 年发布(RFC 7540),旨在解决 HTTP/1.1 在高并发、大流量场景下的性能瓶颈。
它并非颠覆 HTTP 语义(如请求方法、状态码、URI 模型保持不变),而是通过二进制传输、多路复用、头部压缩等核心特性,将 HTTP 协议的传输效率提升数倍,成为 gRPC、现代浏览器、云原生服务通信的底层核心协议。
1.2 HTTP/2 诞生背景:为何需要升级 HTTP/1.1?
HTTP/1.1 自 1999 年发布后,长期支撑互联网通信,但随着 Web 应用的复杂化(如单页应用、高清图片 / 视频、微服务架构),其设计缺陷逐渐成为性能瓶颈:
问题场景 | HTTP/1.1 的局限性 | 具体影响 |
---|---|---|
并发请求阻塞 | 单 TCP 连接仅支持 "串行请求"(请求 A 未响应前,请求 B 必须排队),即 "队头阻塞(Head-of-Line Blocking, HOLB)" | 若一个请求因网络延迟卡住,后续所有请求均被阻塞;为解决并发,浏览器需建立多 TCP 连接(通常 6-8 个 / 域名),但多连接会增加服务器资源占用(如 TCP 状态维护、缓冲区) |
头部开销过大 | 请求头(Header)以文本格式传输,包含大量重复字段(如 Host 、User-Agent 、Cookie ),且无压缩机制 |
高频请求场景(如微服务间调用、API 接口)中,头部占比可达 30%-50%,浪费带宽;尤其移动网络(带宽有限)下,延迟显著增加 |
无服务器推送能力 | 仅支持 "客户端主动请求 - 服务端被动响应" 模式,服务端无法主动向客户端推送资源 | 浏览器加载页面时,需先请求 HTML,解析后再请求 CSS/JS/ 图片,多轮往返增加页面加载时间 |
文本传输效率低 | 数据以明文文本格式传输,需额外处理字符编码(如 UTF-8),且文本格式对机器解析不友好(需按分隔符拆分字段) | 序列化 / 反序列化耗时较长,不适合二进制数据(如 Protobuf、图片)的高效传输 |
为解决上述问题,Google 于 2012 年推出 SPDY 协议(HTTP 优化方案),经过数年实践验证后,IETF 以 SPDY 为基础,标准化为 HTTP/2 协议,于 2015 年正式发布。
二、HTTP2.0核心特性
解决 HTTP/1.1 痛点的关键设计
2.1 核心基础:二进制传输(Binary Framing)
HTTP/1.1 以文本格式 传输数据(Header 和 Body 均为明文),而 HTTP/2 采用二进制帧(Frame) 作为最小传输单位,这是所有其他特性的基础。
- 帧的本质:HTTP/2 不再直接传输 "请求 / 响应",而是将每个请求 / 响应拆分为多个二进制帧,每个帧包含 "帧头(Frame Header)" 和 "帧载荷(Frame Payload)",通过帧的 "类型" 和 "标识" 实现数据的分类和关联。
- 二进制的优势
- 解析效率高 :机器无需处理文本分隔符(如
\r\n
),直接按固定格式解析帧头,解析速度比文本快 30%-50%; - 体积更小:二进制格式无冗余字符(如文本中的空格、换行),尤其适合压缩后的数据传输;
- 兼容性强:可承载任意二进制数据(如 Protobuf、图片、视频),无需额外编码转换。
- 解析效率高 :机器无需处理文本分隔符(如
2.2 核心机制一:多路复用(Multiplexing)
多路复用是 HTTP/2 解决 "队头阻塞" 的核心方案 ,其核心思想是:在单个 TCP 连接中,通过 "流(Stream)" 机制承载多个并发的请求 / 响应,每个请求 / 响应对应一个独立的流,流之间互不干扰。
关键概念:流(Stream)、帧(Frame)、连接(Connection)
- 连接(Connection):客户端与服务端之间的一个 TCP 连接,HTTP/2 中一个连接可承载任意数量的流;
- 流(Stream) :一个独立的、双向的请求 / 响应通道,每个流有唯一的 Stream ID(31 位整数,客户端发起的流用奇数 ID,服务端发起的流用偶数 ID);
- 帧(Frame) :HTTP/2 的最小传输单位,每个帧都包含
Stream ID
字段,标识该帧属于哪个流,通过Stream ID
可将多个帧重组为完整的请求 / 响应。
✅ 多路复用的工作原理
- 客户端与服务端建立一个 TCP 连接;
- 客户端发起多个请求时,为每个请求创建一个独立的流(分配不同的
Stream ID
); - 每个请求的 Header 和 Body 被拆分为多个帧,帧的
Stream ID
与对应流一致; - 所有帧在 TCP 连接中混合传输(无需按请求顺序);
- 服务端接收帧后,通过
Stream ID
识别每个帧所属的流,重组为完整的请求,处理后将响应拆分为帧,通过同一流返回给客户端; - 客户端通过
Stream ID
重组响应帧,得到最终结果。
解决的核心问题:
- 彻底消除 "队头阻塞":一个流的帧传输延迟,不会影响其他流的帧;
- 减少 TCP 连接数:单个连接即可承载所有并发请求,降低服务器资源占用和连接建立开销(三次握手)。
2.3 核心机制二:头部压缩(HPACK)
HTTP 请求 / 响应的头部(Header)包含大量重复信息(如 Host: api.example.com
、User-Agent: Chrome/120.0
),HTTP/1.1 每次请求都会重复传输这些头部,占用大量带宽。HTTP/2 通过 HPACK 算法 实现头部压缩,大幅减少头部传输体积。
压缩原理:
- HPACK 压缩原理:结合 "静态字典 + 动态字典 + 哈夫曼编码"
2.3.1 静态字典(Static Dictionary
- HTTP/2 预定义了一个包含 61 个常见头部字段的字典(如
:method: GET
、:path: /
、Host
),每个字段有唯一的索引(如:method: GET
对应索引 2); - 传输时,若头部字段在静态字典中,只需传输 "索引"(1-2 字节),无需传输完整的字段名和值,大幅减少体积。
2.3.2 动态字典(Dynamic Dictionary)
- 针对静态字典中没有的自定义头部(如
X-Request-ID
、Authorization
),HTTP/2 会在当前连接中维护一个动态字典,记录已传输过的头部字段; - 首次传输自定义头部时,需传输完整字段名和值,但后续传输相同头部时,只需传输动态字典中的索引;
- 动态字典会根据新的头部字段动态更新,自动淘汰不常用的字段(默认最大容量 4096 字节,可通过
SETTINGS
帧协商调整)。
2.3.3 哈夫曼编码(Huffman Coding)
- 对头部字段的 "值"(如
api.example.com
)进行哈夫曼编码,通过 "高频字符用短码、低频字符用长码" 的方式,进一步压缩文本体积(平均压缩率约 30%)。
2.3.4 压缩效果示例
假设一个 HTTP 请求头包含以下字段:
bash
:method: GET
:path: /user/123
Host: api.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0
X-Request-ID: abc123
- HTTP/1.1 文本传输:约 200 字节;
- HTTP/2 HPACK 压缩:
- :method: GET → 静态字典索引(2)→ 1 字节;
- :path: /user/123 → 哈夫曼编码 → 约 10 字节;
- Host: api.example.com → 静态字典索引(3)+ 哈夫曼编码值 → 约 12 字节;
- User-Agent → 首次传输:字段名哈夫曼编码 + 值哈夫曼编码 → 约 40 字节;后续传输:动态字典索引 → 1 字节;
- X-Request-ID: abc123 → 首次传输:约 20 字节;后续传输:1 字节;
- 首次压缩后约 83 字节(减少 58.5%),后续压缩后约 24 字节(减少 88%)。
2.4 核心机制三:流控(Flow Control)
HTTP/2 的多路复用允许单个 TCP 连接承载大量流,但可能出现 "单个大流(如大文件传输)占用全部带宽,导致小流(如 API 请求)延迟" 的问题。HTTP/2 通过 流级别的流量控制 解决这一问题,确保每个流的资源占用可控。
2.4.1 流控的核心规则
- 基于窗口的控制:每个流(及整个连接)都有一个 "接收窗口(Receive Window)",表示接收方当前可接收的最大字节数(默认 65535 字节,即 64KB);
- 双向控制:客户端和服务端均可独立控制对方的发送窗口(客户端控制服务端向自己发送的流量,服务端控制客户端向自己发送的流量);
- 窗口更新机制:当接收方处理完部分数据,释放缓冲区后,会发送 WINDOW_UPDATE 帧,告知发送方 "可增加的窗口大小",发送方可继续发送数据;
- 禁止覆盖:发送方不能发送超过接收窗口大小的数据,否则接收方会丢弃超出部分,并返回 PROTOCOL_ERROR 帧;
- 流控可选 :流控仅针对
DATA
帧(承载消息体),HEADER
、SETTINGS
等控制帧不受流控限制,确保协议控制逻辑不被阻塞。
2.4.2 流控的工作流程
以客户端接收服务端响应为例
- 连接建立时,客户端通过
SETTINGS
帧告知服务端:"我的初始接收窗口为 65535 字节"; - 服务端向客户端发送
DATA
帧,总大小不超过 65535 字节; - 客户端接收并处理 30000 字节数据后,缓冲区剩余 35535 字节,发送
WINDOW_UPDATE
帧,告知服务端:"窗口可增加 30000 字节,当前窗口恢复为 65535 字节"; - 服务端继续发送不超过 65535 字节的
DATA
帧,循环直至响应发送完成。
2.5 核心机制四:服务器推送(Server Push)
HTTP/1.1 中,服务端只能被动响应客户端的请求,无法主动推送资源。HTTP/2 的 服务器推送 允许服务端在客户端未请求的情况下,主动向客户端推送资源(如 CSS、JS、图片),减少客户端的请求次数和往返延迟。
2.5.1 服务器推送的核心逻辑
- 基于关联请求 :服务端只能在处理一个客户端请求时,推送与该请求相关的资源(如客户端请求
index.html
,服务端可主动推送style.css
和app.js
),避免无意义的推送; - 推送承诺(Push Promise) :服务端推送资源前,需先发送
PUSH_PROMISE
帧,告知客户端:"我将向你推送某个资源(如/style.css
)",并包含该资源的请求头(如:path: /style.css
); - 客户端可控:客户端可通过以下方式拒绝推送:
- 连接建立时,通过
SETTINGS
帧设置 SETTINGS_ENABLE_PUSH = 0,禁用所有推送; - 收到 PUSH_PROMISE 帧后,发送
RST_STREAM
帧,终止特定推送流;
- 连接建立时,通过
- 推送资源缓存:客户端接收推送的资源后,会存入本地缓存,后续若需该资源,可直接从缓存读取,无需再次请求。
2.5.2 推送场景示例
- 客户端向服务端请求
index.html
; - 服务端解析
index.html
后,发现依赖style.css
和app.js
; - 服务端发送
PUSH_PROMISE
帧,告知客户端:"将推送/style.css
和/app.js
"; - 服务端同时发送
index.html
的响应帧和style.css
、app.js
的推送帧; - 客户端接收所有资源后,直接渲染页面,无需再发起
style.css
和app.js
的请求,减少 2 次请求往返。