HTTP/2 与 RPC 精讲教程 / 01 - HTTP/2 概述与历史
第 01 章:HTTP/2 概述与历史
从 SPDY 到 HTTP/2,理解协议演进的内在逻辑
1.1 HTTP 协议的演进时间线
在深入 HTTP/2 之前,我们有必要回顾整个 HTTP 协议族的发展脉络。每一次协议升级,都是对前代瓶颈的直接回应。
| 版本 | 年份 | 核心特性 | 主要瓶颈 |
|---|---|---|---|
| HTTP/0.9 | 1991 | 单行协议,仅支持 GET | 无头部、无状态码 |
| HTTP/1.0 | 1996 | 引入头部、状态码、Content-Type | 每个请求一个 TCP 连接 |
| HTTP/1.1 | 1997 | 持久连接、管线化、分块传输 | 队头阻塞(HOL Blocking) |
| SPDY | 2009 | 多路复用、头部压缩、优先级 | 实验性质,未标准化 |
| HTTP/2 | 2015 | 二进制分帧、多路复用、服务器推送 | TCP 层队头阻塞 |
| HTTP/3 | 2022 | 基于 QUIC/UDP、0-RTT、连接迁移 | 生态兼容性仍在推进 |
1.2 HTTP/1.1 的性能瓶颈
HTTP/1.1 虽然引入了持久连接(Keep-Alive)和管线化(Pipelining),但在现代 Web 应用中仍面临严峻挑战。
1.2.1 队头阻塞问题
HTTP/1.1 的管线化要求响应必须按请求顺序返回。即使后端已经处理完第二个请求,也必须等待第一个请求的响应完成。
客户端 服务器
|--- 请求 A --------->|
|--- 请求 B --------->|
| | (B 先处理完)
|<-------- 响应 A ----| (但必须等 A 先返回)
|<-------- 响应 B ----|
1.2.2 连接数限制
浏览器对同一域名的并发连接数有限制(通常为 6 个),这导致资源加载被人为串行化。
传统优化手段(Domain Sharding):
- 将资源分散到 cdn1.example.com、cdn2.example.com
- 每个域名独立 6 个连接
- 问题:增加 DNS 解析开销,管理复杂
1.2.3 头部冗余
HTTP/1.1 的文本格式头部在每次请求中重复传输相同的元数据(如 Cookie、User-Agent),造成带宽浪费。
典型 HTTP/1.1 请求头部(约 500-800 字节):
GET /api/users HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate, br
Cookie: session_id=abc123; theme=dark; lang=zh-CN
Connection: keep-alive
1.3 Google SPDY:HTTP/2 的前身
1.3.1 SPDY 的诞生背景
2009 年,Google 发起了 SPDY(发音同 “speedy”)项目,旨在解决 HTTP/1.1 的性能问题。SPDY 并非替代 HTTP,而是在 HTTP 与 TCP 之间插入了一个"会话层"。
SPDY 的核心设计目标:
| 目标 | 实现方式 |
|---|---|
| 降低延迟 | 多路复用、优先级 |
| 减少带宽 | 头部压缩(压缩率 85%+) |
| 兼容 HTTP | 保留 HTTP 语义,仅改变传输层 |
| 安全性 | 强制 TLS 加密 |
1.3.2 SPDY 的关键特性
# 模拟 SPDY 的多路复用模型
# 多个逻辑流共享一个 TCP 连接
class SPDYConnection:
def __init__(self):
self.streams = {} # stream_id -> Stream
self.tcp_socket = None
def send_request(self, stream_id, headers, data):
"""在同一个连接上发送多个请求"""
frame = self.create_syn_stream(stream_id, headers)
self.tcp_socket.send(frame)
if data:
data_frame = self.create_data_frame(stream_id, data)
self.tcp_socket.send(data_frame)
def receive(self):
"""接收帧并分发到对应的流"""
frame = self.tcp_socket.recv()
stream_id = frame.stream_id
self.streams[stream_id].handle(frame)
1.3.3 SPDY 的版本演进
| 版本 | 时间 | 主要变更 |
|---|---|---|
| SPDY/1 | 2009 | 初始版本,基本多路复用 |
| SPDY/2 | 2010 | 改进流控制,引入 SETTINGS 帧 |
| SPDY/3 | 2012 | 改进头部压缩,修复安全问题 |
| SPDY/3.1 | 2014 | 流控制窗口更新,为 HTTP/2 铺路 |
⚠️ 注意: SPDY 已于 2016 年被 Chrome 移除支持。HTTP/2 完全吸收了 SPDY 的设计思想,是 SPDY 的标准化升级版本。
1.4 HTTP/2 的标准化过程
1.4.1 IETF 标准化
HTTP/2 的标准化由 IETF(Internet Engineering Task Force)的 HTTP 工作组负责:
| 时间节点 | 事件 |
|---|---|
| 2012 年 11 月 | IETF 会议讨论基于 SPDY 制定新标准 |
| 2014 年 12 月 | 发布 RFC 7540 草案 |
| 2015 年 5 月 | RFC 7540 正式发布(HTTP/2) |
| 2015 年 5 月 | RFC 7541 发布(HPACK 头部压缩) |
1.4.2 设计原则
HTTP/2 的设计遵循以下核心原则:
- 保持语义兼容:HTTP 方法、状态码、URI、头部字段不变
- 传输层透明:对应用层透明,仅改变数据的编码和传输方式
- 可选 TLS:虽不强制,但主流浏览器仅支持基于 TLS 的 HTTP/2
- 协商机制:通过 ALPN(Application-Layer Protocol Negotiation)协商协议版本
1.5 HTTP/2 的性能优势
1.5.1 核心优势概览
| 特性 | HTTP/1.1 | HTTP/2 | 性能提升 |
|---|---|---|---|
| 传输格式 | 文本 | 二进制 | 解析效率提升 10x+ |
| 多路复用 | 不支持(管线化受限) | 原生支持 | 并发性能提升 3-5x |
| 头部压缩 | 无 | HPACK | 带宽节省 50-90% |
| 服务器推送 | 不支持 | PUSH_PROMISE | 首屏时间减少 30-50% |
| 流优先级 | 无 | 依赖树 + 权重 | 关键资源优先加载 |
| 连接数 | 6-8 个/域名 | 1 个 | 减少 TCP/TLS 握手 |
1.5.2 基准测试数据
以下数据来自多个公开的性能基准测试(参考 HTTP/2 官方测试与 WebPageTest):
测试场景:加载包含 36 个资源的典型 Web 页面
┌─────────────────────┬──────────┬──────────┬──────────┐
│ 指标 │ HTTP/1.1 │ HTTP/2 │ 提升幅度 │
├─────────────────────┼──────────┼──────────┼──────────┤
│ 首次内容绘制 (FCP) │ 2.1s │ 1.2s │ 43% │
│ 最大内容绘制 (LCP) │ 3.8s │ 2.1s │ 45% │
│ 总页面加载时间 │ 4.5s │ 2.6s │ 42% │
│ TCP 连接数 │ 6 │ 1 │ 83% │
│ 总传输字节数 │ 142 KB │ 98 KB │ 31% │
│ 请求数 │ 36 │ 36 │ - │
└─────────────────────┴──────────┴──────────┴──────────┘
💡 提示: 性能提升幅度与网络延迟(RTT)正相关。在高延迟网络(如移动端 3G)下,HTTP/2 的优势更为显著。
1.6 HTTP/2 的适用场景
1.6.1 最适合的场景
| 场景 | 原因 | 典型案例 |
|---|---|---|
| 高并发 API 服务 | 多路复用减少连接开销 | 微服务间通信 |
| 密集型 Web 页面 | 多资源并行加载 | 电商首页、新闻门户 |
| 实时数据推送 | 服务器推送 + 流式传输 | 股票行情、通知系统 |
| 移动端应用 | 高延迟网络下优势明显 | App 后端 API |
| RESTful API 网关 | 统一入口,连接复用 | API Gateway |
1.6.2 不太适合的场景
| 场景 | 原因 | 替代方案 |
|---|---|---|
| 简单低频 API | 收益不明显,增加复杂度 | HTTP/1.1 足够 |
| 内网低延迟通信 | 多路复用优势减弱 | gRPC over HTTP/2 |
| 大文件传输 | 单流传输,多路复用无优势 | 专用协议(如 S3) |
| WebSocket 长连接 | 语义不同,场景不同 | WebSocket |
1.7 HTTP/2 的生态系统支持
1.7.1 浏览器支持
| 浏览器 | 支持版本 | 备注 |
|---|---|---|
| Chrome | 41+ (2015) | 基于 SPDY 经验,早期支持 |
| Firefox | 36+ (2015) | 完整支持 |
| Safari | 9+ (2015) | macOS/iOS 支持 |
| Edge | 12+ (2015) | 早期版本基于 Chromium |
| IE | 不支持 | 已停止更新 |
1.7.2 服务器支持
| 服务器 | 版本要求 | 配置方式 |
|---|---|---|
| Nginx | 1.9.5+ | listen 443 ssl http2; |
| Apache | 2.4.17+ | Protocols h2 http/1.1 |
| Caddy | 2.0+ | 默认启用 |
| Go net/http | 1.6+ | golang.org/x/net/http2 |
| Node.js | 内置 | http2 模块 |
1.8 动手实验:HTTP/2 vs HTTP/1.1
1.8.1 使用 curl 测试 HTTP/2
# 测试网站是否支持 HTTP/2
curl -I --http2 https://www.google.com
# 输出示例:
# HTTP/2 200
# content-type: text/html; charset=ISO-8859-1
# 对比 HTTP/1.1
curl -I --http1.1 https://www.google.com
# 输出示例:
# HTTP/1.1 200 OK
# content-type: text/html; charset=ISO-8859-1
1.8.2 使用 nghttp2 工具分析帧
# 安装 nghttp2
# Ubuntu/Debian
sudo apt-get install nghttp2-client
# macOS
brew install nghttp2
# 查看 HTTP/2 帧详情
nghttp -v https://www.google.com
# 输出示例(帧级别日志):
# [ 0.075] send SETTINGS frame ...
# [ 0.075] recv SETTINGS frame ...
# [ 0.075] recv WINDOW_UPDATE frame ...
# [ 0.075] send SETTINGS frame ...
# [ 0.104] send HEADERS frame ...
# [ 0.150] recv HEADERS frame ...
# [ 0.150] recv DATA frame ...
1.8.3 Go 搭建 HTTP/2 服务器
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 检查是否使用 HTTP/2
fmt.Fprintf(w, "Protocol: %s\n", r.Proto)
fmt.Fprintf(w, "TLS: %v\n", r.TLS != nil)
fmt.Fprintf(w, "RemoteAddr: %s\n", r.RemoteAddr)
})
// 使用自签名证书启动 HTTP/2 服务器
log.Println("Starting HTTP/2 server on :8443")
err := http.ListenAndServeTLS(":8443", "server.crt", "server.key", nil)
if err != nil {
log.Fatal(err)
}
}
# 生成自签名证书(测试用)
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt \
-days 365 -nodes -subj "/CN=localhost"
# 运行服务器
go run main.go
# 测试
curl --http2 -k https://localhost:8443/
# 输出: Protocol: HTTP/2.0
1.9 注意事项
⚠️ HTTP/2 并非银弹:
- HTTP/2 解决的是 HTTP 层的队头阻塞,但 TCP 层的队头阻塞仍然存在
- 对于低并发、低延迟场景,HTTP/2 的优势不明显
- 错误的优先级配置可能导致资源加载顺序不理想
⚠️ TLS 配置要求:
- 主流浏览器仅支持基于 TLS 的 HTTP/2(h2)
- 必须使用 TLS 1.2+,推荐 TLS 1.3
- 需配置 ALPN 协商(h2, http/1.1)
💡 性能优化建议:
- 启用 HTTP/2 时,可移除域名分片(Domain Sharding)优化
- 不再需要合并 CSS/JS 文件(HTTP/2 多路复用下独立请求更高效)
- 合理设置服务器推送策略,避免推送已缓存的资源
1.10 业务场景:电商网站性能优化
某电商平台首页需要加载 80+ 个资源(CSS、JS、图片、API 请求),在 HTTP/1.1 下面临以下问题:
HTTP/1.1 方案(优化前):
├── 6 个并行连接
├── 资源合并:10 个 CSS → 2 个,30 个 JS → 5 个
├── 域名分片:img1~img4.example.com
├── 内联关键 CSS
└── 首屏加载时间:4.2s
HTTP/2 方案(优化后):
├── 1 个连接
├── 保持独立资源文件(便于缓存更新)
├── 移除域名分片
├── 服务器推送关键 CSS/JS
└── 首屏加载时间:2.1s(提升 50%)
1.11 扩展阅读
- 📖 RFC 7540 - HTTP/2 规范
- 📖 RFC 7541 - HPACK 头部压缩
- 📖 High Performance Browser Networking - HTTP/2
- 📖 Google SPDY 白皮书
- 📖 Can I Use - HTTP/2
- 📖 HTTP/2 FAQ
下一章: 第 02 章 - 二进制分帧层 →