记录一下那天晚上的排查过程。服务在 HTTP/2 下推 SSE,单条事件 4KB,客户端要等 2 秒才收到第一批,降到 HTTP/1.1 后 200ms 就出字。用 curl 的两种协议参数都能稳定复现,服务端日志里 WINDOW_UPDATE 的相关记录很少。
刚开始盯着「反向代理在 HTTP/2 与 HTTP/1.1 之间转换时做了缓冲」,折腾了半个多小时,排掉了。
排查方向其实有限,先把可能性排一排。初始流窗口只有 65535 字节,接收端不主动发 WINDOW_UPDATE。少数情况下是客户端 HTTP/2 实现把接收窗口压得很小。
还有一种情况,应用层每次只写几十字节,Nagle 与延迟 ACK 叠加放大延迟。
先对比两种协议下的首次字节时间
curl -N --http2 -w '%{time_starttransfer}\n' -o /dev/null https://api/stream; curl -N --http1.1 -w '%{time_starttransfer}\n' -o /dev/null https://api/stream
接着抓包看窗口更新帧的频率
tshark -i eth0 -f 'tcp port 443' -Y 'http2.type == 8' -c 20
调大接收窗口后复测首字
nghttp -nv https://api/stream | grep -i window
必要时把流式路径回退到 HTTP/1.1
curl -N --http1.1 https://api/stream
别只看报错消失,验证要多做一步。同一路径在 HTTP/2 下的首字时间与 HTTP/1.1 相差不到 200ms,抓包里能看到持续的窗口更新。
多路复用下窗口是按流算的,代理和客户端各有一份,只调一端经常看不出变化。改动前把原配置备份一份,出问题能马上回滚,这比任何技巧都实用。
