Go 的 net/http 默认支持 HTTP/2,但当请求卡住、大文件上传失败、gRPC 连接偶发断开时,如何定位是协议层问题还是业务层问题?
GODEBUG=http2debug=2 是 Go HTTP/2 实现内部的调试开关,能把 frame 级别的收发日志打出来。这篇文章讲清楚:它输出什么、怎么读日志、如何定位问题。
一、它和 http2debug=1 的区别
http2debug=1:打开 verbose logs,看连接生命周期http2debug=2:在 1 的基础上,额外输出 frame read/write 日志
实战建议:先用 1,不够再上 2。因为 2 的日志量很大,高并发场景会淹没关键信息。
二、启用方式
| |
三、什么时候该开
以下场景优先考虑:
- 请求卡住不返回 — 可能是 HEADERS 发了但 DATA 没发完,或流控窗口耗尽
- gRPC 偶发断连 — keepalive policy 不匹配,或中间代理行为异常
- 大文件上传/下载异常 — 流控、stream reset、连接被 GOAWAY
- HTTP/1.1 正常,HTTP/2 异常 — 典型的协议层问题
四、先确认是不是 HTTP/2 问题
别一上来就淹没在 frame 日志里。先做两件事:
1. 确认当前走的是 HTTP/2
| |
如果显示 HTTP/2.0,那开 http2debug=2 是对路的。
2. 用禁用 HTTP/2 做 A/B 对照
| |
如果关掉 HTTP/2 后恢复正常,问题就在协议层。
五、日志里会看到什么
核心 frame 类型:
| Frame | 作用 | 关注点 |
|---|---|---|
| SETTINGS | 连接参数交换 | 有没有正常交换和 ACK |
| HEADERS | 请求/响应头 | stream 编号、是否带 END_STREAM |
| DATA | 请求/响应体 | 是否持续发送、是否突然停止 |
| WINDOW_UPDATE | 流控 | 窗口是否增长、DATA 停住前是否有更新 |
| PING | 探活/keepalive | 是否有 ACK |
| RST_STREAM | 单流终止 | 谁发的、为什么 |
| GOAWAY | 连接级关闭 | LastStreamID、错误码 |
六、日志阅读方法:按 stream id 重建生命周期
HTTP/2 是多路复用的,日志会交错。不要按时间全局扫,要按 stream id 过滤。
假设怀疑 stream 13:
| |
然后重建生命周期:
- 请求 HEADERS 什么时候发
- DATA 有没有持续
- WINDOW_UPDATE 有没有
- 响应 HEADERS 有没有回来
- 最后是 END_STREAM 还是 RST_STREAM/GOAWAY
每个异常请求都要回答四个问题:
- 请求有没有真正发出去?
- 对端有没有真正接收并响应?
- 中途是谁先终止的?
- 是单流问题还是整连接问题?
七、典型排障模板
模板 1:请求卡住不返回
看 stream 的 HEADERS → DATA → WINDOW_UPDATE → 响应 HEADERS
- 只有请求 HEADERS,没有响应 HEADERS:服务端没处理到,或中间代理没转发
- DATA 发到一半停了:流控窗口用尽,检查 WINDOW_UPDATE
- 响应头回来了,但响应体不结束:服务端没发带 END_STREAM 的最终帧
模板 2:大文件上传失败
看 DATA → WINDOW_UPDATE → RST_STREAM → GOAWAY
- 大量 DATA 后突然 RST_STREAM:服务端拒绝请求体,或代理层 body 限制
- DATA 发不动但连接不关闭:典型流控卡死
- 连接级 GOAWAY:不只是这一个请求的问题
模板 3:gRPC keepalive 异常
看 PING → PING ACK → GOAWAY
- 客户端发 PING,服务端不 ACK:中间层吞掉,或服务端策略不允许
- PING 后紧接 GOAWAY:keepalive policy 不匹配
八、常见误区
- ❌ 看到 GOAWAY 就等于崩了 — 它是"连接进入收尾阶段",要看错误码
- ❌ 看到 RST_STREAM 就是服务端 bug — 客户端取消、超时、代理拒绝都可能导致
- ❌ 没有报错就不是协议问题 — HTTP/2 很多问题是"安静地挂住"
- ❌ HTTP/2 日志能替代抓包 — 它是 Go 实现视角,网络层问题还得抓包
九、调试清单
| |
小结
GODEBUG=http2debug=2 的核心不是背 frame 名字,而是形成判断模型:
- HEADERS — 请求/响应开始没开始
- DATA — 正文有没有真的流动
- WINDOW_UPDATE — 是不是流控卡住
- RST_STREAM — 是不是单请求被终止
- GOAWAY — 是不是整连接进入关闭
最有效的实战技巧:先用 http2client=0 / http2server=0 做二分,再用 http2debug=2 按 stream id 重建生命周期。