gRPC .NET 网络恢复后仍一直 Unavailable,检查连接重试循环是否已退出
Grpc.Net.Client 的旧版 subchannel 在 ConnectTimeout 与重连交错时,可能退出循环却停在 TransientFailure。正式 2.83.0 已让取消出口恢复 Idle;单纯缩短 RPC deadline 或重启服务端不能恢复已卡住的客户端状态。
服务端已经恢复,其他客户端也能访问,某个 Grpc.Net.Client 进程却一直报 Unavailable,错误细节仍是:
Error connecting to subchannel.
若这个客户端此前经历连接超时,subchannel 长时间停在 TransientFailure,要检查连接重试循环是否还在运行。正常的暂时失败也会出现该状态;关键是网络恢复后没有新的连接尝试,旧错误持续被返回。
状态没回 Idle,下一次请求也启动不了循环
这条故障在 Grpc.Net.Client 2.71.0、2.76.0 等旧组合中出现过,2.80.0 也有同类表现。ConnectTimeout 与连接失败、退避唤醒交错时,连接上下文的取消令牌可能在循环外层触发 OperationCanceledException。
旧实现捕获异常并记录连接已取消,但没有把 subchannel 恢复为 Idle,循环就结束了。随后 RequestConnection 看到 TransientFailure,只尝试唤醒退避等待;如果循环已经退出,等待对象也不在,这次唤醒不会创建新的连接循环。
因此,反复调用 ConnectAsync 不一定能拉起这个状态。单纯调短每次 RPC 的 deadline,也不会修复通道内部已经停止的连接工作。
正式 2.83.0 修正取消出口
2.83.0 已在捕获连接取消时转换到 Idle,让后续连接请求可以重新进入循环;正式 2.84.0 仍保留这个处理。按项目的兼容要求采用包含修正的正式 Grpc.Net.Client,并核对部署产物中的实际包版本,不把预发布的工具包版本当作客户端已更新的证明。
这不是让所有异常都立即无限重连。DNS、路由、TLS 和服务端是否可达仍要分别检查,正常退避与 RPC 总预算也应保留。缺少新连接尝试与“每次确实尝试但仍连接失败”是两类现象。
排查时可以结合现有日志、连接状态变化和受控进程诊断,确认 TransientFailure 对应的循环已经退出,而非仍在退避。不要为获取状态绕开 TLS 校验或移除鉴权,也不要把一个状态快照当成永久卡死的充分证据。
旧进程恢复时保留通道配置和在途调用
尚未完成更新的受影响实例,可以按应用已有生命周期受控替换客户端通道,或通过正常部署重启客户端进程。服务端单独重启未必能改变旧客户端的内部状态。
替换时保留地址解析、负载均衡、TLS、凭据、拦截器和调用策略,不能仅创建一份默认通道后静默丢掉原有契约。在途调用按应用既定的取消或排空规则处理;不要为每个失败 RPC 新建通道,否则会把恢复问题变成连接与资源抖动。
连接失败也不自动证明此前的写 RPC 没有到达服务端。是否可以重发,仍要看操作幂等性和服务端记录,不以 Unavailable 为依据无限补发。
验收需要覆盖暂时不可达、连接超时触发、网络恢复与后续调用。确认状态能重新进入 Connecting/Ready,并且失败调用按原预算结束。健康网络下的一次成功调用,无法验证此前卡住的那条恢复路径。


