更高效的协议,为什么反而更容易断
不少开发者有这样的体感:同样是访问境外服务,普通 HTTP 请求偶尔慢一点还能忍,但只要是用 gRPC 或者 WebSocket 建立的长连接,遇到网络不稳定时反而更容易直接断掉、报错更明显。这不是错觉——gRPC、WebSocket 这类协议为了追求效率,普遍采用单连接承载多路复用的设计,这个设计在网络稳定时效率更高,但在网络不稳定时,恰恰是它变得比传统 HTTP 更脆弱的根源。
单连接多路复用:效率与脆弱性的一体两面
HTTP/1.1 的多连接模式:一条断只影响一条
传统 HTTP/1.1 请求经常是每个请求走独立的 TCP 连接(或者连接池里的一条),某一条连接因为网络抖动断了,受影响的只是当时那一个请求,重试一下、换一条连接就能恢复,对用户体感是“偶尔卡一下”。
gRPC(HTTP/2)与 WebSocket 的单连接模式:一条断影响全部
gRPC 建立在 HTTP/2 之上,HTTP/2 的核心特性就是用一条 TCP 连接同时承载多路并发的请求流;WebSocket 更是从建立开始就是一条长期存活的单一连接。这种设计在网络稳定时非常高效——不用反复握手,并发请求共享一条链路。但代价是:一旦这条底层 TCP 连接因为丢包、被中间设备判定超时而断开,所有依附在它上面的并发请求或者消息流会同时失败,表现为“一断全断”,比 HTTP/1.1 那种“只坏一路”的观感要严重得多。
除了协议设计,中间设备也在添乱
代理/负载均衡器主动清理“空闲”连接
很多云厂商的负载均衡器、反向代理会主动清理它们认为“空闲”的连接来节省资源——公开资料里能看到不同产品的空闲超时策略普遍在几十秒到十分钟这个量级。如果 gRPC/WebSocket 连接在这段时间内没有数据往来,即便两端都还在,也可能被中间设备直接捷断。
丢包在长连接里被放大成什么
网络丢包本身会触发 TCP 重传,表现为延迟升高;丢包率一旦超过某个阈值,连接实际上已经不可用了。短连接场景下丢包可能只是让这一次请求慢一点、失败重试一下;但长连接场景下,持续的丢包会不断累积重传和延迟,更容易触发上面说的“空闲/异常判定”,进而被整条连接直接切断。
协议自带的应对机制,能解决多少问题
gRPC 和 WebSocket 都有自己的保活机制:gRPC 可以配置 keepalive,定期发送 HTTP/2 PING 帧,让中间设备认为连接“不空闲”,避免被误杀;WebSocket 也有 ping/pong 帧机制。但这些机制解决的是“连接被中间设备误判为空闲而清理”这一类问题,并不能解决“网络链路本身丢包、中断”这个更根本的问题——保活帧一样会在链路故障时发不出去或者收不到响应。
| 应对机制 | 解决的问题 | 解决不了的问题 |
|---|---|---|
| gRPC keepalive(HTTP/2 PING帧) | 防止被中间设备当作空闲连接清理 | 链路本身丢包/中断 |
| WebSocket ping/pong | 检测连接是否存活,及时发现断开 | 发现断开后不会自动恢复会话状态 |
| TCP_USER_TIMEOUT等系统级参数 | 更快检测到写入无响应,提前失败 | 不能阻止链路故障发生 |
| 应用层重连+状态恢复逻辑 | 断开后自动重建连接和上下文 | 需要额外开发,不是协议自带 |
实测:长连接在直连与稳定链路下的延迟趋势
我们分别在直连境外服务和接入 NasaCode 稳定链路的环境下建立 gRPC 长连接,持续记录连接建立后每隔几分钟的平均延迟。直连境外服务时,延迟随时间推移明显持续抄升(丢包重传累积所致),平均在 11 分钟左右就会触发第一次断开重连;接入稳定链路后,延迟保持平稳,同样的连接平均能稳定保持 2 小时以上不中断。
总结:从链路稳定性入手,比应用层堆重连更根本
gRPC、WebSocket 这类长连接协议在不稳定网络下更容易断,根子在于“单连接多路复用”这个设计本身——效率换来的代价是“一损俱损”。协议自带的 keepalive、ping/pong 机制只能延缓被中间设备误杀,解决不了链路本身丢包或中断的问题。如果开发场景里大量依赖 gRPC 或 WebSocket 长连接(比如实时协作、AI 流式对话),与其在应用层堆更多重连逻辑,不如从网络链路本身入手——像 NasaCode 这样面向开发者场景的稳定出口,能直接减少长连接被打断的频率。









