必须用 context.WithTimeout 传递超时,不能只设 client.Timeout;Kratos 中需透传 context 截止时间,避免超时叠加,HTTP/gRPC 超时需分层对齐,熔断重试须联动 context deadline,指标监控应聚焦 request_duration_seconds{quantile="0.99"}。

超时必须用 context.WithTimeout 传递,不能只设 client.Timeout
在 Kratos 中,仅给 http.Client 或 grpc.Dial 设置整体超时(如 Timeout: 5 * time.Second)是不够的。下游服务无法感知上游时限,容易导致“超时叠加”——比如 A 调 B 设了 5s,B 再调 C 又设 5s,用户端实际等了近 10s 才失败。
正确做法是:所有跨服务调用必须通过 context.Context 传递截止时间。Kratos 的 http.Server 和 grpc.Server 默认从请求中提取 Deadline 并注入到 handler 的 ctx 中,你只需原样透传下去:
func (s *service) GetUser(ctx context.Context, req *v1.GetUserRequest) (*v1.GetUserReply, error) {
// ctx 已携带上游设定的 deadline
reply, err := s.userClient.GetUser(ctx, req) // 直接传入,不额外 WithTimeout
if err != nil {
return nil, err
}
return reply, nil
}- 不要在中间层重复调用
context.WithTimeout,否则会截断原始 deadline - 如果业务逻辑里有耗时操作(如本地计算、DB 查询),才需单独加子 timeout,且值应 ≤ 父 ctx 剩余时间(可用
ctx.Deadline()计算) - Kratos 的
transport.GRPCClient和transport.HTTPClient工厂函数已自动处理 context 透传,无需手动包装
HTTP 和 gRPC 的超时配置要分层对齐
Kratos 支持 HTTP/gRPC 双协议共存,但两者的超时机制底层不同:HTTP 依赖 http.Client.Timeout 和 transport 层各阶段 timeout;gRPC 则靠 context.Deadline + grpc.WaitForReady(false) 控制。若不统一,会出现“HTTP 请求已超时,但 gRPC 调用还在跑”的错配。
推荐配置方式:
- HTTP Server 入口统一用
http.Timeout中间件,设为最宽松的全局上限(如 10s) - gRPC Client 初始化时,
grpc.Dial不设WithTimeout,完全交由 context 控制 - 在
data层封装 client 时,确保NewUserClient等工厂函数不覆盖 context,例如:
func NewUserClient(conn *grpc.ClientConn) pb.UserClient {
return pb.NewUserClient(conn)
// ❌ 不要写:return pb.NewUserClient(keepaliveClient(conn))
}注意:Kratos 的 transport 包默认启用 grpc.WithBlock(),这会导致连接建立阻塞,应显式关闭:
conn, err := grpc.Dial(
"discovery:///user.service",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(), // ❌ 移除它,改用异步连接
)熔断+重试必须和超时联动,否则雪崩风险更高
单纯加重试,会在超时前反复发起请求;单纯开熔断,又可能把偶发延迟误判为故障。Kratos 的 circuitbreaker 和 retry 中间件必须基于 context deadline 动态调整行为。
- 重试次数最多 2 次,且每次间隔用指数退避(如 50ms → 100ms),总耗时不能超过父 ctx 剩余时间
- 熔断器统计窗口(
WithWindow)建议设为 60s,但错误率阈值(WithErrorRatio)要结合 P99 延迟定:若下游 P99 是 800ms,你的超时设 1s,那错误率阈值就得高于 20%,否则正常抖动也会触发熔断 - Kratos 的
retry中间件默认不检查 context 是否已 cancel,务必手动加判断:
if ctx.Err() != nil {
return nil, ctx.Err()
}否则可能在 deadline 到达后仍执行第 2 次重试,浪费资源。
超时指标必须监控 request_duration_seconds{quantile="0.99"}
Kratos 默认集成 Prometheus,但很多人只看 timeout_count_total,忽略更关键的 request_duration_seconds 分位数。这个指标才是超时策略是否合理的直接反馈。
判断标准很简单:
- 若
request_duration_seconds{quantile="0.99"}持续 > 你设的超时值 × 0.8,说明超时太紧,大量正常请求被误杀 - 若该指标稳定在超时值的 30% 以下,说明超时太松,没起到资源保护作用
- Kratos 的 tracing 会自动记录每个 span 的 duration,但前提是 HTTP/gRPC 的 transport 层没被自定义拦截器干扰(比如手写的日志中间件没调用
span.End())
真正难的是动态适配:P99 延迟每天波动,但代码里的 context.WithTimeout 是写死的。生产环境建议用配置中心下发超时阈值,而不是硬编码。


















