重试逻辑应封装在HTTP客户端层,而非业务代码中;推荐包装http.Client的Do方法形成RetryClient,或实现自定义RoundTripper,使业务调用简洁且重试策略集中可控。

重试逻辑该放在哪一层?别塞进业务代码里
Golang 的 HTTP 客户端本身不带重试,但也不该让每个 http.Do 都自己写 for 循环。正确位置是封装一个带重试能力的 http.RoundTripper,或更常见、更可控的做法:包装 http.Client 的 Do 方法,形成自定义的请求执行器。
- 业务层只调用
client.RetryDo(req),不感知重试次数、间隔、判定条件 - 重试策略(指数退避、错误类型过滤)集中配置,避免散落在各处
- 不要改
http.DefaultClient,它被标准库和其他模块共享,容易引发隐性冲突
用 backoff 库实现可配置的指数退避重试
直接手写退避逻辑易出错(比如忘记 jitter、误判状态码)。推荐用 github.com/cenkalti/backoff/v4,它提供标准 backoff 策略和可组合的重试判定函数。
import "github.com/cenkalti/backoff/v4"
<p>func (c <em>RetryClient) Do(req </em>http.Request) (<em>http.Response, error) {
var resp </em>http.Response
err := backoff.Retry(func() error {
var err error
resp, err = c.client.Do(req)
if err != nil {
return backoff.Permanent(err) // 连接失败等网络错误,允许重试
}
if resp.StatusCode >= 500 || resp.StatusCode == 429 {
return fmt.Errorf("server error: %d", resp.StatusCode) // 可重试状态码
}
return nil // 成功,停止重试
}, backoff.WithContext(backoff.NewExponentialBackOff(), req.Context()))
return resp, err
}-
NewExponentialBackOff()默认初始间隔 100ms,最大 1s,最多 9 次尝试(总耗时约 12.5s) - 必须传入
req.Context(),否则超时/取消无法中断重试循环 -
backoff.Permanent()表示“这次错误绝对不重试”,比如 DNS 解析失败后立即重试毫无意义
如何区分哪些错误该重试?别盲目 retry all
HTTP 请求失败分三类,处理方式完全不同:
- 网络层错误(
net.OpError、url.Error):通常可重试,如连接拒绝、超时、TLS 握手失败 - 客户端错误(4xx,除
429外):基本不该重试,比如400 Bad Request或401 Unauthorized,重试只会重复失败 - 服务端错误(5xx、
429):典型重试场景,但要注意503 Service Unavailable带Retry-After头时,应优先遵守该头而非固定退避
建议用类型断言 + 状态码组合判断:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
if urlErr, ok := err.(*url.Error); ok {
if _, isNetErr := urlErr.Err.(net.Error); isNetErr {
return fmt.Errorf("network error: %w", err)
}
}
// 然后检查 resp.StatusCode...并发请求下重试可能放大雪崩?必须加熔断和限流
当上游服务抖动时,重试会成倍放大请求量。比如 100 QPS 的请求,若平均重试 2 次,实际打到下游就是 300 QPS —— 如果下游已过载,这会加速崩溃。
- 在重试客户端外再包一层熔断器,例如
github.com/sony/gobreaker,连续失败超过阈值就短路,直接返回错误 - 对高频调用的下游接口,用
golang.org/x/time/rate.Limiter控制重试请求的发出速率,避免重试风暴 - 日志中必须记录重试次数(
req.Header.Set("X-Retry-Count", strconv.Itoa(attempt))),方便排查是否重试失控
重试不是万能补丁。真正难的是判断「什么时候该停」—— 熔断阈值设太松压垮下游,设太紧又影响可用性;退避时间算不准,可能错过恢复窗口。这些没监控、没实验数据支撑,光靠代码逻辑很难调优。

















