gRPC客户端超时必须通过context.WithTimeout显式设置,不能依赖ClientConn或服务端配置;HTTP客户端需手动指定http.Client.Timeout,避免使用默认永不超时的http.DefaultClient。

gRPC客户端超时必须靠ctx,不是靠ClientConn
Kratos的gRPC client不会读取http.Client.Timeout,也不认服务端配置;它只响应你传进去的context.Context。不显式派生带超时的ctx,调用就可能无限卡住。
- 每次gRPC调用前必须用
context.WithTimeout(ctx, 2*time.Second)生成新ctx,defer cancel()写在函数最开头 - 别复用全局
*grpc.ClientConn并指望它“自带timeout”——gRPC没这机制 - 如果用了
errgroup.Group,每个子goroutine都要派生自己的带超时ctx,不能共用父ctx
HTTP客户端超时容易被默认值坑
kratos.NewHTTPClient()默认用http.DefaultClient,而它的Timeout == 0,等同于永不超时。这不是bug,是Go标准库设计使然。
- 必须手动构造
&http.Client{Timeout: 3 * time.Second},再传给kratos.NewHTTPClient(client.Option) - 千万别改
http.DefaultClient——这是全局变量,会污染其他模块 - transport层的
DialContext超时(如Timeout、KeepAlive)只管建连,不管整条请求生命周期;真正起作用的只有http.Client.Timeout或你传的ctx
连接建立阶段超时要单独配,和业务超时不混用
gRPC的grpc.Dial默认是异步建连:立即返回*grpc.ClientConn,但TCP握手、TLS协商可能还在后台跑。服务端没起来时,第一次RPC才暴露“context deadline exceeded”,这其实是连接失败,不是业务超时。
- 必须在
grpc.Dial时加grpc.WithTimeout(5 * time.Second)控制建连总耗时 - 搭配
grpc.WithBlock()让Dial阻塞直到成功或超时,避免后续调用才爆问题 - 别指望用
context.WithTimeout包住第一次RPC调用来“补救”——那等的是请求,不等连接
超时错误不是异常,是正常控制流信号
看到rpc error: code = Canceled desc = context canceled或context deadline exceeded,别急着加recover或打panic日志。这是context按设计发出的信号,说明上游已放弃请求,你的代码及时响应了。
- 正确做法是显式检查:
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) - 记录warn日志,返回对应状态码(如HTTP 499或gRPC Canceled),然后退出处理逻辑
- 继续用已cancel的ctx发起下游调用、操作数据库连接或发消息,就会触发更隐蔽的资源泄漏或panic
真正难的不是设几个timeout参数,而是所有分支路径都得覆盖ctx生命周期管理——比如defer cancel()写在if分支里、return早了timer还在跑、中间件透传ctx时丢了超时信息,这些地方一漏,超时就形同虚设。


















