反向代理gRPC双向流失败的根本原因是HTTP/2配置不当:需调大http2_window_size(如4MB)和http2_max_concurrent_streams(如1000),禁用proxy_buffering与缓存,设proxy_http_version 2.0,延长proxy_read_timeout等超时至1800秒,并验证ALPN协商成功避免降级。

反向代理(如 Nginx、Envoy、Istio)在转发 gRPC 双向流时,若缓冲与超时配置不当,会导致客户端收不到实时状态、流中途断开、或卡在某进度不再更新。核心问题不是协议不支持,而是代理层默认按 HTTP/1.1 逻辑处理 HTTP/2 流帧,未适配长连接、低频响应、窗口流控等特性。
必须调大 HTTP/2 流控窗口和并发流数
gRPC 基于 HTTP/2,而反代默认的 http2_window_size 往往只有 64KB~65535 字节,服务端一发多个 chunk 就被阻塞;http2_max_concurrent_streams 默认常为 100,高并发流会静默拒绝新子流。
- Nginx 示例(需 1.19.13+,启用
http_v2):http2_max_field_size 64k;<br>http2_max_header_size 64k;<br>http2_max_requests 1000;<br>http2_recv_buffer_size 4m;<br>http2_idle_timeout 300s;
- Envoy(Istio)需显式覆盖
stream_idle_timeout和max_stream_duration,不能依赖自动 gRPC 检测——它常失效。建议在 VirtualService 或 EnvoyFilter 中设:stream_idle_timeout: 600s<br>max_stream_duration: 0s
(禁用)
禁用代理层的响应缓冲与缓存逻辑
HTTP/1.1 代理习惯把响应攒够再吐,但双向流要求“边写边传”。Nginx 的 proxy_buffering on、proxy_cache、proxy_http_version 1.1 都会破坏流式语义。
- 务必强制使用 HTTP/2:
proxy_http_version 2.0;<br>proxy_set_header Connection '';<br>proxy_set_header Upgrade $http_upgrade;
- 关闭所有缓冲:
proxy_buffering off;<br>proxy_buffers 8 128k;<br>proxy_busy_buffers_size 256k;<br>proxy_max_temp_file_size 0;
- 禁止任何缓存头干扰:
proxy_cache off;<br>proxy_cache_bypass $http_upgrade;<br>proxy_no_cache $http_upgrade;
调整超时参数,避免 120 秒静默重置
Istio/Envoy 默认 stream_idle_timeout=120s 是最常见断连元凶——只要流上 2 分钟没数据帧,就发 RST_STREAM。Nginx 的 proxy_read_timeout 和 proxy_send_timeout 若小于传输耗时,也会主动断连。
- Nginx 推荐值:
proxy_read_timeout 1800;<br>proxy_send_timeout 1800;<br>keepalive_timeout 1800;
- Envoy/Istio 中,除
stream_idle_timeout外,还需检查connection_idle_timeout和max_connection_duration,三者都应 ≥ 30 分钟或设为 0(不限制) - 客户端侧同步调大
grpc.keepalive_time_ms(如 30000)和grpc.keepalive_timeout_ms(如 10000),确保心跳能穿透代理
验证是否真正透传 HTTP/2 帧,而非降级成 HTTP/1.1
很多反代看似启用了 HTTP/2,实则因 TLS ALPN 协商失败或后端不支持,悄悄回落到 HTTP/1.1,导致 stream 完全不可用。
- 用
curl -v --http2 https://your-domain.com确认响应头含HTTP/2 200,且无Upgrade: h2c类降级提示 - 抓包看 TCP 流:HTTP/2 应有 SETTINGS、HEADERS、DATA 帧;若全是 GET/POST 文本,则已降级
- 服务端日志中检查实际使用的协议版本(如 ASP.NET Core 的
HttpContext.Request.Protocol或 Go 的r.Proto)










