说明 NGINX gRPC 上游连接按 keepalive PING 周期被 RST 时,如何用日志、抓包和 TCPAbortOnClose 取证,安全关闭空闲主动 PING 并验证回退。
NGINX gRPC 上游空闲连接被 RST 重置怎么排查
先用 PING 周期和 TCPAbortOnClose 确认连接抖动,再关闭空闲主动 PING 或接受重连,避免套用不安全补丁
直接答案:如果 NGINX 复用 gRPC 上游连接后,连接总是在固定空闲时间被 RST,而业务请求大多仍成功,先核对这个时间是否等于 gRPC 服务端的 keepalive PING 周期。NGINX 上游 Issue #929 在 2026 年 9 月由维护者复现:连接进入通用 upstream keepalive 缓存后,空闲 PING 到达会让 NGINX 为保护缓存正确性而关闭连接。当前安全选择是关闭“没有活跃 RPC 时仍主动 PING”,让 NGINX 的 keepalive_timeout 管理空闲连接;不能直接给 NGINX 打“收到数据就 return”的简单补丁。
本文适用于 NGINX 反向代理 gRPC、upstream 配置了 keepalive,并且 gRPC 服务端允许空闲时主动发送 HTTP/2 PING 的现场。只读日志、抓包和内核计数器检查不会中断业务。修改 PING 策略需要重启或滚动发布 gRPC 服务端,可能造成短时连接重建;先在单个实例灰度,并保留原配置。
先确认是不是同一条故障路径
不要只看一条 connection reset by peer 就下结论。先记录一次 RPC 结束时间、RST 时间、服务端 PING 周期和 NGINX 的 keepalive_timeout。若连接在 2 秒、5 秒或其他固定 PING 周期关闭,而不是在 NGINX 空闲超时点关闭,才与上游复现场景相符。
在 NGINX 主机上只读查看错误日志和连接关闭计数。LOG_FILE 要替换为当前站点错误日志:
LOG_FILE=/var/log/nginx/error.log
grep -E 'keepalive close handler|upstream.*reset|grpc' "$LOG_FILE" | tail -100
nstat -az | grep -E 'TcpExtTCPAbortOnClose|TcpExtTCPAbortOnData'
普通发行版的 NGINX 未必记录 keepalive close handler,因为它需要 debug 日志。没有该行不等于排除;可比较 TcpExtTCPAbortOnClose 在复现前后的增量。这个计数是整台主机口径,其他进程也会影响,因此必须和故障时间、上游地址及抓包一起判断。
抓包时看什么
只在已授权服务器和明确上游端口上短时抓包。UPSTREAM_IP 与 PORT 必须替换;文件可能包含业务元数据,应按内部敏感日志管理:
UPSTREAM_IP=127.0.0.1
PORT=9200
sudo timeout 30 tcpdump -i any -nn -s 256 \
"host $UPSTREAM_IP and port $PORT" -w /tmp/nginx-grpc-keepalive.pcap
完成后核对:一次 RPC 正常结束、连接进入空闲、服务端按配置发送 PING、随后 NGINX 方向出现 RST。若 RST 发生在活跃 RPC 中,或前面已有上游进程退出、负载均衡摘除、网络超时,就应转去查应用崩溃、发布切换和网络故障,不套用本文结论。
为什么不能直接延长 keepalive_timeout
NGINX 的 upstream keepalive 保存的是每个 worker 的空闲连接,不限制活跃连接总数。keepalive_timeout 只规定空闲连接在缓存中保留多久。Issue #929 的复现中,空闲 PING 比该超时更早到达,所以把 60 秒改成 5 分钟不会解决问题。
同样,不要把 grpc_socket_keepalive on 当成修复。它控制操作系统 TCP keepalive,不负责处理 HTTP/2 PING 帧,与 gRPC 应用层 keepalive 是两套机制。
首选处理:关闭没有活跃 RPC 时的主动 PING
gRPC 官方建议客户端避免无调用时频繁发送 keepalive,并提醒服务端可能用 GOAWAY 限制过于激进的 PING。对于本故障,关键是 gRPC 服务端是否配置了类似 permit_without_calls 的选项。不同语言名称不同,不能照抄某一种 SDK 的字段到另一种语言。
- 导出当前 gRPC 服务端 keepalive 配置,记录 PING 间隔、超时和是否允许无调用 PING。
- 在一个非关键实例上关闭“无活跃 RPC 仍发送 PING”,其他参数先不变。
- 滚动重启该实例,不一次重启全部后端。
- 把少量测试流量指向灰度实例,观察至少覆盖多个原 PING 周期。
关闭空闲主动 PING 通常不产生云厂商额外费用,但连接重建会增加握手和 CPU;若因此改动负载均衡、监控或实例数量,费用以当前平台账单预估为准。
结果怎样判断
| 结果 | 判断 | 下一步 |
|---|---|---|
| RST 不再按原 PING 周期出现,请求正常 | 与已知兼容性现场一致 | 分批推广并继续观察连接数和延迟 |
| 仍按固定周期重置 | 还有其他实例发送空闲数据,或配置未生效 | 核对生效配置、进程版本和流量落点 |
| 活跃 RPC 失败或出现 502/504 | 不是单纯空闲连接抖动 | 立即回退,查上游退出、超时和网络 |
| 只减少重置但连接数持续上涨 | 空闲超时或连接池容量需要单独调整 | 按并发和 worker 数重新核算 keepalive |
接受重连也比不安全补丁更稳妥
维护者复现指出,原始 NGINX 行为会带来连接抖动,但测试请求仍得到正确响应。简单修改源码,让 idle handler 在看到数据时继续保留连接,会让未消费字节留在 socket 缓冲区;下一次请求复用时可能把旧字节当作新响应,造成请求失败,甚至产生跨请求响应混淆。因此在正式修复发布前,宁可接受可观测的重连,也不要部署这一行补丁。
验证、回退与上线检查
灰度后连续发起正常 RPC,再让连接空闲超过原 PING 周期和 NGINX 空闲超时。确认 RPC 成功率、P95 延迟、上游连接创建率、RST 数量与 TcpExtTCPAbortOnClose 不再同步异常增加。检查 NGINX 配置语法:
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl status nginx --no-pager
本文首选改动在 gRPC 服务端,不要求修改 NGINX。若灰度出现长连接异常、连接数飙升或延迟升高,把服务端 keepalive 配置恢复为原值并滚动回退;不要删除抓包和脱敏日志,保留给后续版本复核。上游 Issue 截至核验日仍开放且没有正式修复版本,升级前必须重新查看发布说明和实际版本行为。
相关排障
官方与上游来源
- NGINX Issue #929:空闲上游数据触发连接关闭
- NGINX ngx_http_grpc_module 官方文档
- NGINX upstream keepalive 官方文档
- gRPC Keepalive 官方指南
来源核验日期:2026-10-14。黑鲨云不是 NGINX 或 gRPC 官方。本文不把单个 Issue 泛化为所有 RST 的原因,也不宣称未发布修复已经可用。本稿未发现需要补充确认的黑鲨云业务事实。

