用 backoff.retry 做带退避的重试最稳妥,因其解耦策略与执行、默认支持 jitter、避免雪崩,且需配合 context、错误过滤和最大重试次数使用。

用 backoff.Retry 做带退避的重试最稳妥
Go 标准库不提供重试机制,硬写 for 循环 + sleep 容易漏掉指数退避、上下文取消、错误过滤等关键逻辑。直接上 github.com/cenkalti/backoff/v4 是多数项目的实际选择——它把重试策略和执行解耦,且默认支持 jitter,避免雪崩。
常见错误现象:time.Sleep 写死 100ms 导致下游被压垮;没检查 context.Context 被取消就继续重试;把网络超时错误和业务校验失败混在一起重试。
- 只对临时性错误重试,比如
io.EOF、net.OpError、HTTP 503/429,跳过sql.ErrNoRows或 JSON 解析失败这类确定性错误 - 用
backoff.WithMaxRetries控制次数,别依赖backoff.WithMaxInterval单独限频——它不阻止重试次数,只限制间隔上限 - 务必传入带 timeout 的
context.Context,否则backoff.Retry可能卡死在最后一次重试的阻塞调用里
err := backoff.Retry(func() error {
resp, err := http.DefaultClient.Do(req)
if err != nil {
return backoff.Permanent(err) // 永久性错误,不重试
}
if resp.StatusCode == 503 {
return fmt.Errorf("service unavailable")
}
return nil
}, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 3))
自己写 for 循环重试时,必须手动处理这三件事
不是所有场景都值得引入第三方包,比如简单 CLI 工具或单元测试辅助函数。但手写必须显式覆盖三个点,否则大概率出问题。
使用场景:调用本地 socket、读取配置文件、触发 shell 命令等低频、低并发操作。
- 每次循环开头检查
ctx.Err() != nil,立即返回,否则可能忽略 cancel 信号 - 用
time.AfterFunc或time.Sleep做延迟时,要先判断是否已是最后一次尝试——最后一下不该 sleep - 错误判断不能只看
err != nil,得用errors.Is或类型断言区分可重试/不可重试错误,例如errors.Is(err, os.ErrNotExist)就不该重试
HTTP 请求重试别直接包 http.Client,改用中间件式封装
很多人想给 http.Client 加重试,试图包装 Do 方法,结果发现 Transport 层的连接复用、TLS 握手、redirect 处理全乱了。正确做法是把重试逻辑放在请求构造之后、发送之前。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
性能影响:每次重试都会新建 *http.Request,但不会重复解析 URL 或初始化 header——只要你不改 req.Header 的底层 map。
- 不要修改原
req.Body,重试时需重新生成,否则第二次会报body closed - 如果用了
req.SetBasicAuth或req.AddCookie,这些调用要在每次重试前重做 - 避免在重试逻辑里调用
req.Clone(ctx)——它不复制 Body,且对 context 处理有陷阱,老老实实用http.NewRequestWithContext重建更安全
retryablehttp 库在长连接场景下容易误判超时
这个库(github.com/hashicorp/go-retryablehttp)封装友好,但默认行为对 HTTP/2 和 keep-alive 连接不透明。它把底层连接错误(如 TCP reset)和应用层错误(如 500)一并重试,而某些服务端在连接异常时已部分写入响应,重试会导致重复消费。
兼容性影响:Go 1.21+ 默认启用 HTTP/2,retryablehttp.Client 的 CheckRetry 函数收不到原始 net.Error,只能靠 status code 和 response body 字符串判断,精度下降。
- 设
Client.RetryMax后,必须同步设Client.RetryWaitMin和Client.RetryWaitMax,否则退避逻辑失效,默认值是 0,导致无限重试 - 禁用连接复用:
Transport.MaxIdleConnsPerHost = 1,可减少因复用脏连接引发的误重试,代价是多几次 TLS 握手 - 如果后端是 gRPC-gateway,优先考虑用
grpc-go自带的WithBlock+FailFast: false,比 HTTP 重试更可控
重试不是万能补丁,真正难的是区分“暂时不可达”和“业务拒绝”。网络抖动可以等,但权限不足或参数错,重多少次都是错的。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










