kratos 的 grpc 超时需分层配置:连接建立(grpc.dial 超时仅限建连)、调用执行(每次 rpc 必须传独立带超时 context)、服务端响应(检查 ctx.done())、网络拓扑(保活与 goaway 配合);发布期需摘流+优雅下线,重试须按错误码精准控制。

Kratos 的 gRPC 连接超时不是单一配置能解决的问题,必须分层处理:连接建立、调用执行、服务端响应、网络拓扑四者都要设超时,漏掉任一环都会导致 context deadline exceeded 或 UNAVAILABLE。
grpc.Dial() 的超时只管建连,不管业务调用
很多人在 grpc.Dial() 里加了 grpc.WithTimeout(5 * time.Second) 就以为万事大吉,其实它只控制初始 TCP 握手 + TLS 协商 + HTTP/2 Preface 发送的耗时。一旦连接成功返回 *grpc.ClientConn,这个超时就彻底失效了。
- 网络抖动时,连接可能卡在后台重连队列里,直到第一次
client.Method(ctx, req)才暴露问题 - 真实报错常是
rpc error: code = Unavailable desc = connection closed,本质是连接层失败,不是你设的业务超时没生效 - 必须配,但不能替代调用层超时;建议值设为 3–5 秒,比业务超时长一点即可
每次 RPC 调用必须传独立带超时的 context
这是最常被跳过的一步。Kratos 的 client.NewClient() 或直接用 xxxClient.MethodName() 都不会自动注入超时 —— 你得手动构造并及时 cancel。
- 写法必须是:
ctx, cancel := context.WithTimeout(parentCtx, 1200*time.Millisecond); defer cancel() - 超时值建议设为下游 P95 延迟的 1.2–1.5 倍(例如压测得 P95=950ms,就设 1200ms)
- 别复用已
cancel()的ctx进重试循环,否则第二次调用会立刻返回context.Canceled - 服务端也得检查
ctx.Done(),及时退出长逻辑,否则资源白占着
连接保活和发布期超时强相关
滚动发布时大量 DEADLINE_EXCEEDED,大概率不是代码问题,而是旧连接没被及时清理。gRPC 是 HTTP/2 长连接,客户端不会主动感知服务端进程已杀,还在往“死线”上发请求。
- 客户端需开启保活:
grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: 30 * time.Second, Timeout: 10 * time.Second, PermitWithoutStream: true}) - 服务端配合发送 GOAWAY:
grpc.KeepaliveParams(keepalive.ServerParameters{MaxConnectionIdle: 30 * time.Second, MaxConnectionAge: 10 * time.Minute}) - Kubernetes 环境下,必须确保先从注册中心摘流(如 Nacos/Eureka 下线),再发
SIGTERM杀进程,否则客户端连接固化
重试不能靠猜,要按错误码精准控制
gRPC 官方重试拦截器默认不生效,且条件苛刻;自己实现更可控,但必须严格区分错误类型。
- 只对
codes.Unavailable(连接断开)、codes.DeadlineExceeded(真超时)做指数退避,用backoff.Retry() -
codes.NotFound、codes.InvalidArgument绝对不重试 —— 它们是业务错误,重试只会放大压力 - HTTP 客户端同理:只重试
url.Error且err.Timeout()为true的,或errors.Is(err, syscall.ECONNREFUSED) - 别在 for 循环里硬写
time.Sleep(),必须带 jitter,避免雪崩式重试
真正容易被忽略的是:连接生命周期管理不在代码里,而在发布流程中。一个没配 MaxConnectionAgeGrace 的服务端,加上没做摘流的 Kubernetes Deployment,再配上没设 PermitWithoutStream 的客户端,三者叠加,发布窗口必出超时 —— 这不是 bug,是配置契约没对齐。











