应使用自定义 RoundTripper 而非业务层 for 循环;需精准判断可重试错误(如 net.OpError、url.Error.Timeout()、5xx 响应码),避免重试客户端错误;gRPC 重试须满足拦截器、选项拼接、指定错误码三条件;context 每次重试必须新建;推荐使用 backoff/v4 库而非手写;服务端幂等性是重试安全前提。

HTTP 客户端重试该用自定义 RoundTripper 还是业务层 for 循环
直接改 http.Client.Transport 是更干净、更可控的做法。业务层写 for 循环容易把超时、错误分类、退避逻辑和业务代码混在一起,后续加监控或换策略就得动业务函数。
关键不是“重试几次”,而是“哪些错误值得重试”——net.OpError(如连接拒绝、DNS 失败)、url.Error 中的 Timeout() 返回 true、或者响应码落在 502/503/504 区间,才该重试;400、401、404 这类客户端错误基本不该重试。
- 别复用
http.DefaultClient并全局加重试逻辑,健康检查请求也会被重试三次,反而放大下游压力 - 用
&http.Transport{}包装原生RoundTripper,只拦截RoundTrip返回的错误,避免污染其他 HTTP 调用 - 对
url.Error类型判断:用err.Timeout()或strings.Contains(err.Error(), "connection refused") - 对响应码判断:仅当
resp.StatusCode >= 500 && resp.StatusCode < 600才考虑重试
gRPC 客户端重试为什么配置了还不生效
gRPC 官方从 v1.29+ 才正式支持客户端重试,且默认关闭。光配 grpc.WithTimeout 或 grpc_retry.WithMax(3) 都没用,必须同时满足三个条件:
- 显式传入
grpc.WithChainUnaryInterceptor(grpc_retry.UnaryClientInterceptor()) - 在
grpc.DialOption中正确拼接,比如grpc.WithTransportCredentials(...)后追加重试拦截器 - 用
grpc_retry.WithCodes(codes.Unavailable, codes.ResourceExhausted)明确指定可重试码,codes.NotFound和codes.InvalidArgument不会触发重试
还要注意:重试会重新序列化请求体,proto.Message 必须可重复编码,含 sync.Mutex 等不可序列化字段会导致 panic。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
context 超时后还能不能重试?怎么避免 goroutine 泄漏
能重试,但每次重试必须新建 context.WithTimeout 或 context.WithDeadline,绝不能复用已 cancel 的 ctx。否则第二次进循环时 ctx.Err() 已是 context.Canceled,重试变成空转,还可能泄漏 goroutine。
- 每次重试前先检查
ctx.Err() != nil,为真就立即返回,不进下一次循环 -
cancel()必须显式调用,尤其在循环内,延迟调用(defer cancel())会导致 context 泄漏 - 底层操作(如
http.Client.Do)必须透传ctx,否则重试超时控制失效 - 等待退避时间不能用
time.Sleep,得用select+time.After配合ctx.Done(),保证可中断
该选 backoff 库还是手写重试逻辑
除非有极特殊需求(比如按错误码动态跳过某次重试、或实时调整退避系数),否则别手写。社区库已覆盖绝大多数边界,github.com/cenkalti/backoff/v4 是最常用选择。
-
backoff.Retry默认支持context.Context,Operation函数签名强制返回error,天然适配重试语义 - 它内置指数退避、随机抖动、最大重试次数、回调钩子等能力,手写容易漏掉 jitter 或忘记设上限,导致雪崩
- 别用
github.com/hashicorp/go-retryablehttp做通用重试——它专为 HTTP 封装,内部耦合http.Client,没法复用于 gRPC 或数据库连接 - 错误判定要解耦:用自定义函数判断是否重试,而不是让重试框架自己猜;比如对
net.Error.Temporary()返回 true 的才交给 backoff 重试
重试机制本身不解决数据重复问题。服务端没做幂等校验,客户端重试两次,转账接口就可能扣两次款——这点最容易被忽略,也最难补救。

















