2486 字
约 8 分钟
17
S02-A20 · HTTP/2 与 keep-alive:为什么"升级协议"没让页面更快

一个站点从 HTTP/1.1 加 keep-alive 升级到 HTTP/2,理论上前者"每域名 6 连接 + 队头阻塞"的问题应该消失。上线后测速几乎没变——因为瓶颈不在"连接数",而在"关键路径上的 4 个串行接口"与"一个 1.2MB 的未压缩 JS"。

协议升级解决的是"并发与队头阻塞",而它不解决"串行依赖"与"传输体积"。这个区分很重要,因为它决定了"投入方向":协议升级是"一次配置",而减少串行与体积是"改代码"。前者便宜、后者贵——于是团队容易先做前者,然后得到"没变化"的结果。
而这两项往往才是真正的瓶颈。


1. 两种机制各解决什么

机制 解决
keep-alive 省掉重复握手(TCP + TLS)
HTTP/1.1 多连接 绕开"一连接一请求"的串行
HTTP/2 多路复用 同一连接上并发,消除 HTTP 层队头阻塞

第三项是"质的提升",但它仍然受"一个 TCP 连接"的约束:TCP 层的丢包会让所有流一起等(这就是常说的 TCP 层队头阻塞)。

所以"HTTP/2 在弱网下的表现可能不如多连接 HTTP/1.1"——这是一个反直觉但在高丢包环境里成立的事实。这个事实的机制是"拥塞窗口共享":所有流共用一个窗口,一个丢包就让窗口减半,于是"全部流一起变慢"。而"多连接"相当于"多个窗口",代价是握手开销。

2. 升级后"没变快"的四个原因

原因 表现
瓶颈是串行依赖 首屏等 4 个接口串行
瓶颈是体积 JS/CSS 未压缩或未按需
服务端未真开 h2 协商回落到 1.1
CDN/中间层不支持 各自降级

第三项的"伪装"很常见:入口开了 h2,但 curl 走 HTTP/1.1 也能通(协商回落)——于是"访问日志里两个版本都有",而"到底哪个生效"取决于客户端能力与中间层。
第三项最常见:"配置了 h2"与"实际用了 h2"是两件事。用 curl --http2 能看到实际协议,或用 DevTools 的 Protocol 列。

第四项在"多层代理"里常见:入口支持 h2,但回源是 1.1,且某些中间层会重写第四项的"典型结构"是"CDN(h2) → nginx(h2) → Tomcat(1.1)":用户侧的协议生效,但"内部三段"各自协商,于是"哪一段是瓶颈"要逐段确认。
——于是用户侧看到 h2,性能收益却打折。

3. 怎么确认"真的在用 h2"

手段 得到
curl -I --http2 https://… 协商结果
DevTools Network → Protocol 列 h2 / http/1.1
openssl s_client -alpn h2 ALPN 协商
服务端访问日志 协议版本字段

第一项最快:返回 HTTP/2 200 表示协商成功;若显示 HTTP/1.1,说明"服务端或中间层没开"。

第二项的"混合"还有一个成因:某些中间件对"带 body 的请求"不做 h2 升级(历史实现限制),于是"GET 走 h2、POST 走 1.1"——而它在 DevTools 里逐条可见。
第二项最直观:逐请求看协议,于是能发现"静态资源走 h2、接口回落到 1.1"这类混合情况。

// 三步确认
curl -sI --http2 https://example.com | head -1        # 期望 HTTP/2 200
curl -sI --http2 https://example.com/api/x | head -1  # 接口是否也 h2
openssl s_client -connect example.com:443 -alpn h2 </dev/null 2>/dev/null | grep ALPN

"回源链路"的 keep-alive 还影响"连接数与端口耗尽":一个请求新建一次连接,在 QPS 高时会撞到"本地端口耗尽"(约 6 万临时端口),表现为"连接失败"而 CPU 不高。

// 端口耗尽的三个信号
1 TIME_WAIT 数量极高(netstat/ss 计数)
2 连接失败率上升,而服务端负载正常
3 集中在"高 QPS 短请求"的服务
// 修法:回源开 keep-alive(复用连接),而不是"加端口范围"

4. "keep-alive"仍要开的三个理由

理由 说明
省握手 TLS 握手 1–2 RTT
兼容旧客户端 不是所有客户端都 h2
回源链路 反向代理到后端常用 1.1

第三项很实际:nginx 到上游 Tomcat 通常是 HTTP/1.1 + keep-alive第三项的"参数"值得知道:keepalive_timeout 与 keepalive_requests 决定"连接复用多久/多少次",而默认值(65s / 100 次)在"高 QPS 短请求"下会让连接频繁重建。
,于是"前端 h2、回源 1.1"是常态。这条链路上的 keep-alive 直接影响"每个请求是否要新建连接"。

所以"上了 h2 就关 keep-alive"是错的——它们在不同链路上起作用。

"关键路径"该怎么看:把页面首屏需要的请求按"依赖关系"画出来,找出"最长的串行链"。

// 一条典型的关键路径(四层串行)
HTML → main.js → (鉴权接口)→ (配置接口)→ (列表接口)→ 渲染
// 若每层 RTT 80ms + 处理 60ms,首屏 ≈ 4 × 140ms = 560ms
// 优化:① 把"配置"内联进 HTML  ② 鉴权与配置并行  ③ 列表接口预取
// 收益:从 560ms 降到 300ms 级,而协议升级对此无效

5. 真正的"变快"来自哪里

手段 收益
减少关键路径串行 高
减小体积(压缩/按需) 高
就近部署(CDN) 高(远距离用户)
协议升级 中(并发请求多的页面)

第一项的"代价"是"聚合接口把多个后端绑在一起":一个变慢就全慢,且它成为单点。所以实践中常见折中是"聚合 2–3 个强相关接口",而不是"全部合并"。
第一项是"结构问题":把 4 个串行接口合并成 1 个聚合接口,收益远大于协议升级。

第二项在"未压缩"的站点上收益最大:一个 1.2MB 的 JS 压缩后 300KB,传输时间差数倍。

"体积"要先量再压,否则容易在"已经压缩过"的资源上白费工:

// 三步量体积(对"首屏关键资源")
// 1 传输体积(含压缩后)与实际解压体积
curl -s -o /dev/null -w '%{size_download}
' -H 'Accept-Encoding: br,gzip' https://…/main.js
// 2 压缩比(gzip 后 / 原始)
gzip -c main.js | wc -c; wc -c < main.js
// 3 未被使用的代码(用覆盖率工具)
// DevTools → Coverage → 记录 → 看"未使用字节"

6. 排错:五种"协议升级无效"

现象 成因 处理方向
测速没变 瓶颈不在并发 查串行与体积
部分请求仍是 1.1 中间层降级 逐层确认 ALPN
弱网更差 TCP 队头阻塞 考虑多域名分片
首屏没变 关键路径串行 合并/预取
TTFB 没变 与服务端有关 查后端与缓存

第三项是"h2 的代价":单连接上的所有流共享一个拥塞窗口第三项的"缓解"有一个老办法:把静态资源分到 2 个域名(多一个连接/窗口)。而它在 h2 时代被"合并域名"取代——所以"域名分片"不是过时,而是"按网络质量选"。
,于是"一个丢包让全部等"。所以"h2 + 单域名"在丢包多的网络里可能更差。

最后一个"验证"方式:用两个"只差协议"的环境做 A/B。因为"协议升级"很容易与"同时做的其它优化"混在一起,于是"到底是不是它带来的"无法归因。

// 可归因的验证方式
A:同域名 + HTTP/1.1 + keep-alive
B:同域名 + HTTP/2
其余(体积、串行、CDN)完全一致
比:首屏 LCP、资源加载完成时间、每资源 TTFB
// 若差异 < 噪声 → 协议不是瓶颈;此时不该继续在这上面投入

7. 边界:什么情况不必升级协议

场景 为什么 做法
请求数很少(< 6) 无并发问题 不必
纯内部 API(低延迟内网) 握手成本低 不必
已有 h3/QUIC 更进一步 不必回头
客户端老旧(无法协商) 自动回落 不必强推

最后一条与"观测"有关的边界:"协议升级的收益"要按"用户网络质量"分层看。在"低延迟 + 无丢包"的环境里,h2 的收益几乎为零;而在"移动网络 + 高丢包"里,它可能有收益也可能更差——所以要分群测。
判断口径一句话:"同一页面/接口的并发请求数"是否超过"单连接能高效承载的量"。超过(几十个资源、图片墙),h2 有明显收益;不超过(3 个接口),它只是"更好但看不出"。


8. 自测

  1. 三种机制各解决什么问题?为什么 HTTP/2 仍受"TCP 队头阻塞"约束?
  2. "升级后没变快"的四个原因里,哪一个最常见?怎么确认"真的在用 h2"?
  3. 为什么"上了 h2 就关 keep-alive"是错的?回源链路通常用什么协议?
  4. 真正的"变快"来自哪四项?为什么"合并串行接口"收益更高?
  5. 为什么"h2 + 单域名"在弱网下可能更差?
  6. 判断"是否需要升级协议"的那个问题是什么?
S02-A20 · HTTP/2 与 keep-alive:为什么"升级协议"没让页面更快
https://www.clxhxhhr.top/posts/6574/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。