重试逻辑必须由客户端显式控制,kratos默认不自动重试;grpc可用retry中间件按指定错误码重试,http需自行封装http.client并区分可重试错误,同时需协调负载均衡与熔断器避免负反馈。

重试逻辑必须放在客户端,不能依赖服务端或网关
Kratos 的服务间通信(gRPC/HTTP)默认不带重试,重试行为由调用方显式控制。框架本身不自动重试失败请求,这是有意设计:避免在服务端不可控场景下(如幂等性未保障、状态已变更)盲目重试导致数据不一致。你必须在 client 层主动封装重试逻辑,或通过中间件注入。
用 retry 中间件配置 gRPC 重试策略
Kratos 官方 middleware/retry 提供了基于 gRPC status code 的重试能力,但只适用于 gRPC 调用,且要求服务端返回标准 gRPC 错误码(如 codes.Unavailable、codes.DeadlineExceeded)。它不支持 HTTP 客户端重试,也不处理连接层超时或 DNS 解析失败这类底层错误。
- 必须配合
grpc.WithStreamInterceptor或grpc.WithUnaryInterceptor注册,不是全局生效 - 退避策略固定为指数退避(
backoff.Exponential),起始间隔默认 100ms,最大重试次数默认 3 次,可通过retry.WithMax和retry.WithBackoff覆盖 - 仅对指定错误码重试:
codes.Unavailable、codes.Internal、codes.ResourceExhausted、codes.Aborted、codes.OutOfRange、codes.DataLoss - 示例代码中若服务端返回
codes.NotFound或自定义 HTTP 404,该中间件完全忽略,不会重试
HTTP 客户端重试需自行封装 http.Client
Kratos 的 transport/http 客户端本质是包装了标准 http.Client,所以重试必须从底层 http.Transport 或请求发起处控制。常见错误是只改 http.Client.Timeout,这只能防止卡死,不触发重试。
- 推荐用
golang.org/x/net/http/httpproxy+ 自定义RoundTripper实现带退避的重试,例如用backoff.Retry包包裹http.DefaultTransport.RoundTrip - 注意区分错误类型:网络层错误(
net.OpError)、TLS 握手失败、HTTP 状态码(如 503)需分别判断是否可重试 - 务必设置
req.Header.Set("X-Retry-Count", strconv.Itoa(attempt))类似标识,方便服务端识别并做幂等处理 - 不要在 middleware 层对所有 HTTP 请求无差别重试——比如 POST /order 这类非幂等接口,重试前必须确认业务已实现幂等键(如 idempotency-key)
真正容易被忽略的点:重试与负载均衡、熔断器的交互
当你同时启用 retry 中间件、P2C 负载均衡和 circuitbreaker,它们不是独立运行的。一次失败请求可能先被熔断器拒绝(breaker.Allow() 返回 error),根本不会走到重试逻辑;而重试过程中如果切换到另一个节点,P2C 的 EWMA 延迟统计会被打乱,导致后续流量倾斜。
- 重试次数 × 单次请求耗时 > 熔断器窗口期时,会持续触发熔断,形成负反馈循环
- P2C 的
prePick随机选两个节点,重试若固定 fallback 到同一节点,会掩盖真实节点健康度 - 建议把重试最大次数设为 2,退避上限压到 1s 内,避免拖长整体 P99 延迟











