gRPC客户端默认不重试,需显式配置retry.UnaryClientInterceptor并指定可重试错误码(如Unavailable、Aborted、ResourceExhausted),配合WithMax和BackoffExponential避免打爆服务,且服务端必须用status.Errorf返回准确code。

gRPC客户端重试不是默认开启的,得手动配 grpc_retry 和 WithRetry
Go 的 gRPC 官方 client 默认**完全不重试**,哪怕遇到 UNAVAILABLE 或 DEADLINE_EXCEEDED 这类典型可恢复错误。想让它重试,必须显式启用重试策略,且依赖 grpc_retry 这个第三方拦截器(非标准库)。
常见错误现象:调用返回 rpc error: code = Unavailable desc = connection refused,但程序立刻失败,没重试——不是网络真坏了,是根本没配重试逻辑。
- 必须用
github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry(v2 版本,v1 已弃用) - 重试配置只能在 client 创建时通过
grpc.WithUnaryInterceptor或grpc.WithStreamInterceptor注入 -
retry.WithMax控制最大尝试次数,但注意:第 1 次是原始调用,WithMax(3)表示最多发 4 次请求(1 次原 + 3 次重试) - 不设
retry.WithBackoff会立即重试,容易打爆服务;推荐用retry.BackoffExponential
conn, _ := grpc.Dial("localhost:8080",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(retry.UnaryClientInterceptor(
retry.WithMax(2),
retry.WithBackoff(retry.BackoffExponential(100 * time.Millisecond)),
retry.WithCodes(codes.Unavailable, codes.Aborted, codes.ResourceExhausted),
)),
)哪些错误码该重试?别无脑加 codes.Unknown 或 codes.Internal
重试只对「可能临时失败、下次能成功」的错误有意义。乱加错误码反而掩盖真实问题,比如把 codes.Internal 加进重试列表,等于把服务端 panic 当成网络抖动来处理。
典型可重试场景:服务刚启停、LB 转发未就绪、短时连接拒绝、限流触发 ResourceExhausted。
立即学习“go语言免费学习笔记(深入)”;
- 安全范围:优先选
codes.Unavailable、codes.Aborted、codes.ResourceExhausted(需确认是配额类而非业务超限) - 谨慎使用:
codes.DeadlineExceeded—— 可能是上游 timeout 太短,重试前先检查上下文 deadline - 禁止加入:
codes.NotFound、codes.PermissionDenied、codes.InvalidArgument:这些是明确的业务或客户端错误,重试毫无意义 - 永远别加
codes.OK或codes.Unknown:前者不该进错误分支,后者含义模糊,必须先查日志定位真实 code
重试时 Context 被 cancel 或 timeout,会导致所有重试瞬间终止
gRPC 的重试是在单次 RPC 调用的 context 下进行的。如果这个 context 在第一次请求还没返回时就被 cancel 或超时,后续重试不会发起——它不是“后台另起 goroutine”,而是串行复用原 context。
这意味着:如果你传入一个带 short deadline 的 context(比如 context.WithTimeout(ctx, 500*time.Millisecond)),而重试间隔总和超过 500ms,那第 2 次重试甚至都触发不了。
- 务必确保调用时传入的 context 有足够余量,至少 > 单次重试耗时 × 重试次数
- 不要在重试拦截器里修改 context(如套新 timeout),拦截器看到的就是你传进来的原始 context
- 若需精细控制每轮重试的 deadline,得自己实现拦截器,
grpc_retry不支持 per-attempt context 覆盖 - 注意:
WithPerRetryTimeout是 v1 的 API,v2 中已移除,别被旧文档误导
服务端没开 grpc.RPCStatsHandler 或没返回正确 status,客户端重试可能失效
客户端重试依赖服务端返回的 gRPC status code。如果服务端用 status.Errorf 构造错误,没问题;但如果直接 return fmt.Errorf("xxx"),gRPC 会统一转成 codes.Unknown,而你又没把它加进重试列表,那就彻底不重试。
更隐蔽的问题:某些网关或代理(如 Envoy)可能吞掉原始 status,返回泛化错误;或者服务端用了自定义 codec 但没正确透传 error detail。
- 服务端必须用
status.Errorf(code, msg)显式设置 code,避免裸 error - 关键路径上建议加日志,打印
status.Code(err),确认实际返回值是否符合预期 - 若用 gRPC-Gateway,注意它默认把 HTTP 503 映射为
codes.Unavailable,这通常合理;但 4xx 类错误会被映射为codes.InvalidArgument等,不可重试 - 本地调试时,用
grpcurl直连服务端验证返回的 code:grpcurl -plaintext -d '{}' localhost:8080 pkg.Service/Method
重试机制真正生效的前提,是客户端知道「这次失败可以再试一次」——而这高度依赖服务端错误码的准确性和链路中各环节对 status 的保真度。少一个环节出错,整套重试就形同虚设。










