retry.attempts(3)表示总共执行3次(含首次),即最多重试2次;需写retry.attempts(4)才能重试3回;必须配retry.context(ctx)响应取消、retry.delaytype(retry.backoffdelay)启用指数退避、retry.retryif过滤错误,且http重试须克隆body并区分临时性错误。

直接用 github.com/avast/retry-go 或 github.com/cenkalti/backoff/v4,别手写 for + time.Sleep —— 容易漏掉 ctx.Done()、共享退避状态、无 jitter、goroutine 泄露。
为什么 retry.Attempts(3) 不是“重试 3 次”
这是新手最常踩的坑:retry.Attempts(3) 表示「总共执行 3 次」,即首次调用 + 最多重试 2 次。若你希望最多重试 3 回,得写 retry.Attempts(4)。
-
retry.Delay(100 * time.Millisecond)只设初始延迟,不启用指数退避 - 必须搭配
retry.DelayType(retry.BackOffDelay)才会按 100ms → 200ms → 400ms 增长 - 不传
retry.Context(ctx),超时或取消信号就进不来,重试会卡死 - 错误过滤要用
retry.RetryIf(func(err error) bool { return isTransientError(err) }),别用字符串匹配
backoff.Retry 要怎么配才不翻车
backoff.NewExponentialBackOff() 默认参数很危险:base=100ms、maxInterval=1s、maxElapsedTime=10s,第 4 次后间隔就卡在 1s 不再增长,容易把下游打满。
- 显式构造:
bo := &backoff.ExponentialBackOff{InitialInterval: 100 * time.Millisecond, MaxInterval: 2 * time.Second, MaxElapsedTime: 30 * time.Second} - 每次复用前必须调
bo.Reset(),否则NextBackOff()返回值错乱(内部 attempt 是状态化的) - 必须包一层
backoff.WithContext(bo, ctx),否则退避循环本身不响应 cancel - 加 jitter 是刚需:
bo = backoff.WithJitter(bo),否则多协程会在同一时刻发起请求,形成“重试风暴”
HTTP 请求重试必须处理的三个细节
HTTP 场景下,光有退避和次数控制远远不够,这三个点漏一个就会出生产事故:
-
*http.Request.Body是一次性资源,重试时必须req.Clone(ctx)或重新构造 Body,否则第二次读到io.EOF - 只对临时性错误重试:
net.OpError、context.DeadlineExceeded(注意:这是上一次请求的超时,新请求要重置 context)、5xx 状态码;4xx 如400 Bad Request、401 Unauthorized绝对不该重试 - 判断 HTTP 错误不能只看
err != nil,得显式检查resp.StatusCode >= 500,有些 4xx 也会带非空err
最隐蔽的坑是退避状态共享——把 backoff.BackOff 实例声明为全局变量或结构体字段,多个 goroutine 并发调用时,NextBackOff() 返回的间隔完全不可控。每个请求必须独立 new + Reset。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











