推荐使用 github.com/cenkalti/backoff/v4 实现带指数退避的错误重试,需传入 context.Context、设置 MaxElapsedTime 和 MaxInterval,并确保业务函数幂等。
backoff.Retry 实现带退避的错误重试Go 标准库不内置重试逻辑,直接手写容易漏掉指数退避、最大重试次数、上下文取消等关键点。推荐用 github.com/cenkalti/backoff/v4 —— 它把重试策略和执行解耦得比较干净。
常见错误是把重试逻辑硬塞进业务函数里,导致测试困难、超时控制失效、goroutine 泄漏。正确做法是让重试器只负责“什么时候重试”,业务函数只返回 error。
context.Context,否则无法响应外部取消(比如 HTTP 请求被客户端断开)backoff.NewExponentialBackOff(),别用固定间隔——服务抖动时固定重试会加剧雪崩MaxElapsedTime 和 MaxInterval 都要设,前者防无限重试,后者防退避时间过长(默认 MaxInterval 是 1 分钟,对多数 API 来说太长)bo := backoff.NewExponentialBackOff()
bo.MaxElapsedTime = 5 * time.Second
bo.MaxInterval = 500 * time.Millisecond
err := backoff.Retry(func() error {
_, err := http.Get("https://api.example.com/data")
return err
}, bo)有人用 for + time.Sleep 写重试,但没检查 ctx.Done() 就直接 sleep,会导致 goroutine 卡住不退出。这是线上事故高发点。
核心原则:每次 sleep 前都 select 等待 ctx.Done();每次重试前都检查 ctx.Err()。
time.Sleep(d),改用 time.AfterFunc 或 select + time.After
func retryWithCtx(ctx context.Context, fn func() error, maxRetries int) error {
var err error
for i := 0; i <= maxRetries; i+
+ {
if ctx.Err() != nil {
return ctx.Err()
}
err = fn()
if err == nil {
return nil
}
if i == maxRetries {
break
}
select {
case <-time.After(time.Duration(i+1) * 100 * time.Millisecond):
case <-ctx.Done():
return ctx.Err()
}
}
return err
}先限流再重试,不是反过来。否则限流器看到的是“重试请求洪峰”,可能直接拒绝所有流量。
典型错误:用 golang.org/x/time/rate.Limiter 包裹整个重试块,导致第一次失败后,后续重试全被限流器挡在门外,实际重试次数远低于预期。
http.Do 前做 limiter.Wait(ctx)
rate.Limiter 的 Wait 会阻塞,务必传入带超时的 ctx,否则重试整体耗时不可控limiter := rate.NewLimiter(rate.Every(100*time.Millisecond), 1)
for i := 0; i <= 3; i++ {
if err := limiter.Wait(ctx); err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err == nil {
return nil
}
// 失败则继续下一次重试
}HTTP 400、401、403、404、422 这类客户端错误,重试毫无意义,反而浪费资源。但 Go 的 net/http 不区分错误类型,全塞进 error 接口里。
必须显式解析响应状态码或错误底层原因。别依赖 err.Error() 字符串匹配——不稳定且难维护。
*url.Error,检查 err.Unwrap() 是否为 *net.OpError,再看 Timeout() 或 Temporary() 返回值resp.StatusCode 后再决定是否重试,4xx 错误直接返回sql.ErrNoRows 不是错误,driver.ErrBadConn 才适合重试最易忽略的一点:重试逻辑里没做错误分类,把所有 error 当成可重试,结果把参数校验失败也重试了三次。