grpc unavailable 错误表示连接层或服务端不可达,而非业务错误;常见原因包括服务未启动、网络中断、lb转发失败、过载拒绝或dns解析失败,需优先检查通信链路。

gRPC Unavailable 错误到底代表什么
它不是业务逻辑错误,而是连接层或服务端不可达的信号:服务没起来、网络中断、LB 转发失败、服务端主动拒绝新连接(比如过载保护),甚至 DNS 解析失败都可能触发这个状态码。别急着重试业务逻辑,先确认通信链路是否真实通畅。
常见现象:rpc error: code = Unavailable desc = connection refused、transport is closing、failed to connect to all addresses —— 这些都不是 NotFound 或 InvalidArgument 那种能靠改参数解决的问题。
Go 客户端怎么识别和响应 Unavailable
不能只靠 err != nil 判断,必须用 status.Code() 提取真实状态码。gRPC Go 的错误是包装过的,直接打印 err.Error() 看不到结构化信息。
if err != nil {
st, ok := status.FromError(err)
if ok && st.Code() == codes.Unavailable {
// 这里做连接恢复逻辑
log.Printf("service unavailable: %v", st.Message())
return
}
}
注意点:
立即学习“go语言免费学习笔记(深入)”;
-
status.FromError()对非 gRPC 错误返回ok=false,务必检查ok - 别用
strings.Contains(err.Error(), "Unavailable")—— 描述文本可能被本地化或修改,不可靠 -
codes.Unavailable是常量,不是字符串,导入路径是google.golang.org/grpc/codes
重试策略里为什么不能无脑 retry Unavailable
盲目重试会让问题更糟:如果服务根本没部署,重试只是放大客户端日志噪音;如果是因为 CPU 打满被 LB 剔除,高频重试反而加剧雪崩。
合理做法是分级响应:
- 首次遇到
Unavailable:立即退避,比如time.Sleep(100 * time.Millisecond) - 连续 3 次失败:切换备用 endpoint(如果有),或降级到本地缓存/默认值
- 超过 30 秒仍不可用:上报健康检查探针,触发告警,而不是继续轮询
gRPC 默认不带重试中间件,要用 grpc.WithStreamInterceptor 或 grpc.WithUnaryInterceptor 自己套一层,但拦截器里判断 codes.Unavailable 后,别直接 return err 就完事——得控制重试次数和间隔。
服务端怎么避免误报 Unavailable
客户端看到 Unavailable,不一定真是服务挂了。常见误报来源:
- 服务启动时还没完成 gRPC Server.ListenAndServe,但 readiness probe 已返回成功
- Kubernetes 中
readinessProbe检查的是 HTTP 端口,而 gRPC 走的是另一个 port,probe 通过但 gRPC 端口未就绪 - 服务端在
Server.Register之后、Server.Serve之前就返回了,此时任何请求都会得到Unavailable
解决方案很简单:gRPC Server 启动前,先确保监听地址已 bind 成功,并在 Serve 返回后才更新 readiness 状态。K8s 场景下,把 readiness probe 改成调用一个真实的 gRPC health check 方法(比如用 grpc_health_v1.NewHealthClient 主动探测),而不是只 ping HTTP 端口。
最容易被忽略的一点:gRPC 连接复用下,Unavailable 可能来自旧连接残留状态——客户端没及时感知到服务端重启,还拿着已关闭的连接发请求。这时候光看错误码没用,得配合连接生命周期管理,比如设置 WithKeepaliveParams 让连接主动探测有效性。










