← 中文资源

简体中文版本

Node.js — 服务器发送 GOAWAY 导致 ERR_HTTP2_GOAWAY_SESSION

诊断 Node.js http2 客户端的 ERR_HTTP2_GOAWAY_SESSION,将其与服务器 GOAWAY 帧及空闲超时关联,并在重连前正确收尾在途请求流。

阅读英文原文

症状

报错集中在会话被拆除前一刻发出的请求上。Node 把流级错误包装为 ERR_HTTP2_GOAWAY_SESSION,说明死掉的是整个 HTTP/2 会话而不是单个请求:

Error [ERR_HTTP2_GOAWAY_SESSION]: The HTTP/2 session has been
shut down (GOAWAY), code: 0, lastProcessedStreamID: 2147483647

关键细节是 lastProcessedStreamID。编号不大于它的流已被服务器处理,副作用可能已经在上游生效;大于它的流服务器从未见到,可以安全重试。

诊断思路

先用 nghttp 确认是服务器在发 GOAWAY 帧;再在客户端监听 http2 会话的 goaway 与 close 事件,如果 goaway 先于 close 触发,即可排除客户端主动关闭。最后对比两侧空闲超时:当服务器(nginx、Envoy 或 CDN)的 idle timeout 小于客户端两次请求的间隔时,每个静默期都会以 GOAWAY 收场。批量失败、随后自愈的形态正是空闲超时与请求节奏错位的表现。

根因

上游在客户端仍复用该会话发起新流时,用 GOAWAY 帧关闭了 HTTP/2 会话。从服务器发出 GOAWAY 到客户端感知关闭之间存在窗口期,这段时间内发出的请求没有机会换到新会话上,于是直接报错,而不是被正常重试。

修复与验证

把 GOAWAY 当作正常的会话生命周期事件:监听 goaway 事件记录错误码与 lastProcessedStreamID;小于等于该值的流做收尾或幂等重试,大于该值的流可以安全重放;为每个请求设置整体超时。同时让服务器 keepalive 比客户端空闲窗口长几秒,使客户端总是先于服务器重连。验证时把上游 http2_idle_timeout 调到 5 秒、每 10 秒发一个请求跑两分钟:修复前会出现成片的 ERR_HTTP2_GOAWAY_SESSION,修复后 goaway 计数照常增长而请求失败数为零。注意重试次数应与 goaway 事件相当,超出则说明掩盖了另一个故障。