强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

01 - HTTP/2 概述与历史

第 01 章:HTTP/2 概述与历史

从 SPDY 到 HTTP/2,理解协议演进的内在逻辑


1.1 HTTP 协议的演进时间线

在深入 HTTP/2 之前,我们有必要回顾整个 HTTP 协议族的发展脉络。每一次协议升级,都是对前代瓶颈的直接回应。

版本年份核心特性主要瓶颈
HTTP/0.91991单行协议,仅支持 GET无头部、无状态码
HTTP/1.01996引入头部、状态码、Content-Type每个请求一个 TCP 连接
HTTP/1.11997持久连接、管线化、分块传输队头阻塞(HOL Blocking)
SPDY2009多路复用、头部压缩、优先级实验性质,未标准化
HTTP/22015二进制分帧、多路复用、服务器推送TCP 层队头阻塞
HTTP/32022基于 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/12009初始版本,基本多路复用
SPDY/22010改进流控制,引入 SETTINGS 帧
SPDY/32012改进头部压缩,修复安全问题
SPDY/3.12014流控制窗口更新,为 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 的设计遵循以下核心原则:

  1. 保持语义兼容:HTTP 方法、状态码、URI、头部字段不变
  2. 传输层透明:对应用层透明,仅改变数据的编码和传输方式
  3. 可选 TLS:虽不强制,但主流浏览器仅支持基于 TLS 的 HTTP/2
  4. 协商机制:通过 ALPN(Application-Layer Protocol Negotiation)协商协议版本

1.5 HTTP/2 的性能优势

1.5.1 核心优势概览

特性HTTP/1.1HTTP/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 浏览器支持

浏览器支持版本备注
Chrome41+ (2015)基于 SPDY 经验,早期支持
Firefox36+ (2015)完整支持
Safari9+ (2015)macOS/iOS 支持
Edge12+ (2015)早期版本基于 Chromium
IE不支持已停止更新

1.7.2 服务器支持

服务器版本要求配置方式
Nginx1.9.5+listen 443 ssl http2;
Apache2.4.17+Protocols h2 http/1.1
Caddy2.0+默认启用
Go net/http1.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 扩展阅读


下一章: 第 02 章 - 二进制分帧层