应使用 backoff/v4 或 retry-go 等成熟库替代手写 for+time.sleep,避免 goroutine 泄漏、取消失效和重试风暴;必须启用 jitter、隔离退避状态、前置错误分类。

别手写 for + time.Sleep 做重试
Go 标准库不提供重试能力,但手撸循环是最多人踩坑的起点。它看起来简单,实则极易导致 goroutine 泄漏、上下文取消失效、重试风暴或超时失控。
- 常见错误现象:
context deadline exceeded后还在 sleep 并重试;多个请求共享一个退避计数器,间隔错乱;固定 1s 重试压垮下游 - 根本原因:裸
time.Sleep阻塞 goroutine,无法响应ctx.Done();手动算2^i * time.Second容易溢出、没 jitter、无上限 - 使用场景:HTTP 调用、DB 查询、消息投递等临时性失败(如
net.OpError、io.EOF)需等待恢复,而非永久错误(如400 Bad Request、sql.ErrNoRows) - 实操建议:
– 改用github.com/cenkalti/backoff/v4或github.com/avast/retry-go
– 每次重试前必须调b.Reset()(复用 backoff 实例时)
– 操作函数必须接收context.Context,并在内部检查ctx.Err()
用 backoff.Retry 实现带 jitter 的指数退避
backoff.Retry 是目前 Go 社区最稳的退避封装,它把 jitter、最大重试、上下文取消都收口了,你只管传策略和操作函数。
- 参数差异:
–backoff.NewExponentialBackOff()默认base=100ms、maxInterval=1s、maxElapsedTime=10s,容易误判超时,建议显式构造
– 必须包一层backoff.WithContext(b, ctx),否则退避过程无视 cancel
– 加 jitter 是必须项:backoff.WithJitter()或自定义backoff.BackOff实现 - 性能影响:无 jitter 的纯指数退避会导致“重试风暴”——大量协程在相同时刻发起请求,放大下游压力;jitter 让重试时间随机偏移 ±25%,显著缓解冲击
- 实操示例:
bo := &backoff.ExponentialBackOff{<br> InitialInterval: 100 * time.Millisecond,<br> MaxInterval: 2 * time.Second,<br> MaxElapsedTime: 10 * time.Second,<br> Clock: backoff.SystemClock,<br>}<br>bo = backoff.WithJitter(bo)<br>err := backoff.Retry(func() error {<br> return doHTTPRequest(ctx, url)<br>}, backoff.WithContext(bo, ctx))
用 retry.Do 控制重试次数与错误过滤
retry.Do(来自 github.com/avast/retry-go)更贴近业务直觉:它明确区分“总执行次数”和“重试次数”,且原生支持错误白名单。
- 常见错误现象:
retry.Attempts(3)不是“最多重试 3 次”,而是“总共执行 3 次”(含首次),新手常误解为可重试 3 回 - 为什么这样做:
–retry.DelayType(retry.BackOffDelay)才激活指数退避,单设retry.Delay()只是固定延迟
–retry.RetryIf(func(err error) bool { return isTransientError(err) })强制收口判断逻辑,避免散落各处的strings.Contains(err.Error(), "timeout")这类脆弱匹配 - 使用场景:调用外部 HTTP 接口时,仅对
5xx、net.OpError、context.DeadlineExceeded(注意:需重置 context)重试,跳过400、401、429(后者应优先读Retry-Afterheader) - 实操建议:
– 总次数硬限制为 5~6 次,再往上退避时间可能达秒级,用户感知明显
– 设retry.MaxDelay(2 * time.Second)防止单次等待拖垮 SLA
– HTTP 请求务必克隆*http.Request并替换req.Context(),否则底层连接不响应取消
HTTP 客户端集成重试不能只靠业务层
在每个 http.Do 外套重试逻辑,等于把基础设施问题暴露到业务代码里。真正健壮的做法是下沉到 transport 层或换用专有 client。
- 常见错误现象:POST 请求默认不重试(因非幂等),但业务方强行 retry 导致重复下单;未处理
http.Client.Timeout与重试 timeout 的嵌套关系,整体超时失控 - 为什么这样做:
–github.com/hashicorp/go-retryablehttp原生支持状态码过滤、jitter、幂等控制,且保留 redirect、cookiejar 等全部行为
– 它的Client.CheckRetry允许你精细控制:比如对502/503/504和网络错误重试,但跳过400类错误 - 实操建议:
– 设置Client.RetryMax = 4(即总共 4 次请求)
–Client.RetryWaitMin = 100 * time.Millisecond,Client.RetryWaitMax = 2 * time.Second
– 若接口幂等,覆盖DefaultRetryPolicy并显式允许 POST 重试
重试不是加个循环就完事。最容易被忽略的是:每个请求必须隔离退避状态、错误分类必须前置、jitter 不是可选项而是上线前提。这些点一旦漏掉,压测看不出问题,一上生产就雪崩。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











