GODEBUG=http2debug=2 实战指南:Go HTTP/2 调试从入门到定位

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 的日志量很大,高并发场景会淹没关键信息。

二、启用方式

1
2
3
4
5
6
7
8
# 直接运行
GODEBUG=http2debug=2 go run main.go

# 输出到文件
GODEBUG=http2debug=2 ./your-app 2>http2.log

# 测试时启用
GODEBUG=http2debug=2 go test -run TestName -v ./...

三、什么时候该开

以下场景优先考虑:

  • 请求卡住不返回 — 可能是 HEADERS 发了但 DATA 没发完,或流控窗口耗尽
  • gRPC 偶发断连 — keepalive policy 不匹配,或中间代理行为异常
  • 大文件上传/下载异常 — 流控、stream reset、连接被 GOAWAY
  • HTTP/1.1 正常,HTTP/2 异常 — 典型的协议层问题

四、先确认是不是 HTTP/2 问题

别一上来就淹没在 frame 日志里。先做两件事:

1. 确认当前走的是 HTTP/2

1
2
3
4
5
// 客户端
fmt.Println("proto:", resp.Proto)

// 服务端
fmt.Println("request proto:", r.Proto)

如果显示 HTTP/2.0,那开 http2debug=2 是对路的。

2. 用禁用 HTTP/2 做 A/B 对照

1
2
3
4
5
# 客户端禁用
GODEBUG=http2client=0 ./client

# 服务端禁用
GODEBUG=http2server=0 ./server

如果关掉 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:

1
grep 'stream=13\|StreamID=13' http2.log

然后重建生命周期:

  1. 请求 HEADERS 什么时候发
  2. DATA 有没有持续
  3. WINDOW_UPDATE 有没有
  4. 响应 HEADERS 有没有回来
  5. 最后是 END_STREAM 还是 RST_STREAM/GOAWAY

每个异常请求都要回答四个问题:

  1. 请求有没有真正发出去?
  2. 对端有没有真正接收并响应?
  3. 中途是谁先终止的?
  4. 是单流问题还是整连接问题?

七、典型排障模板

模板 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 实现视角,网络层问题还得抓包

九、调试清单

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[ ] 确认当前请求是否走 HTTP/2
[ ] 用 http2client=0 / http2server=0 做 A/B 对照
[ ] 开 http2debug=1 看粗粒度
[ ] 开 http2debug=2 看 frame
[ ] 按 stream id 重建请求生命周期
[ ] 找最后一个正常 frame 和第一个异常 frame
[ ] 区分单流问题(RST_STREAM)还是连接问题(GOAWAY)
[ ] 检查 DATA / WINDOW_UPDATE 判断流控
[ ] 检查 PING / ACK 判断 keepalive
[ ] 结合业务日志、代理日志、超时配置回溯根因

小结

GODEBUG=http2debug=2 的核心不是背 frame 名字,而是形成判断模型:

  • HEADERS — 请求/响应开始没开始
  • DATA — 正文有没有真的流动
  • WINDOW_UPDATE — 是不是流控卡住
  • RST_STREAM — 是不是单请求被终止
  • GOAWAY — 是不是整连接进入关闭

最有效的实战技巧:先用 http2client=0 / http2server=0 做二分,再用 http2debug=2 按 stream id 重建生命周期