指数退避需手动计算等待时间:第n次失败后等待baseDelay

用 time.Sleep 实现指数退避的核心逻辑
指数退避不是靠库“自动给”,而是手动计算每次重试前的等待时间:第 n 次失败后,等待 baseDelay * 2^n,再加点随机抖动防雪崩。Go 标准库没封装这个模式,得自己写循环 + time.Sleep。
关键点是别直接写 time.Sleep(time.Second * time.Duration(math.Pow(2, float64(n))))——math.Pow 返回 float64,精度丢失且慢;改用位移更安全:baseDelay (前提是 <code>baseDelay 是整数毫秒或纳秒)。
- 起始延迟建议设为
100 * time.Millisecond,太小易打满下游,太大拖慢恢复 - 必须加抖动:
time.Sleep(delay + time.Duration(rand.Int63n(int64(jitter)))),jitter通常取delay的 1/3 - 最大重试次数别硬编码,应作为参数传入,避免调用方无法控制
封装成泛型函数时如何处理错误判定
不是所有错误都该重试。比如 http.StatusNotFound 或 validation.ErrInvalidInput 是客户端问题,重试无意义;而 context.DeadlineExceeded、net.OpError、io.EOF 才值得等下次。
所以函数签名里得接收一个 shouldRetry func(error) bool,而不是只看 error != nil。
- 别在函数内部写死判断逻辑,否则复用性归零
- 常见误判:把
json.UnmarshalError当网络错误重试——它其实是响应体解析失败,说明服务已返回,不该重试 - 如果调用的是 HTTP 客户端,建议先检查
resp.StatusCode再决定是否进重试循环,别等err != nil才开始判断
用 context.Context 控制重试生命周期
用户可能只想等 5 秒就放弃,不管重试了几次。这时候靠 for i := 0; i 不够,必须结合 <code>ctx.Done()。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
典型错误是:先 sleep 再 select ctx,导致最后一次 sleep 还没结束,上下文已超时,但 goroutine 还在等——这会卡住调用方。
- 每次循环开头就
select检查ctx.Done(),有信号立刻返回ctx.Err() - sleep 前也做一次
select,防止刚算完 delay 就超时 - 别用
time.After替代time.Sleep,它会泄漏 timer,尤其高频调用时
完整可抄的最小可用示例
下面这段可以直接粘贴进项目,支持泛型、抖动、自定义重试判断、Context 取消:
func DoWithExponentialBackoff[T any](ctx context.Context, fn func() (T, error), opts ...func(*backoffConfig)) (T, error) {
cfg := &backoffConfig{
baseDelay: 100 * time.Millisecond,
maxRetries: 5,
jitter: 50 * time.Millisecond,
shouldRetry: func(error) bool { return true },
}
for _, opt := range opts {
opt(cfg)
}
<pre class="brush:php;toolbar:false;">var result T
var err error
for i := 0; i time.Second {
delay = time.Second // 防止指数爆炸
}
delay += time.Duration(rand.Int63n(int64(cfg.jitter)))
select {
case <p>}</p><p>type backoffConfig struct {
baseDelay time.Duration
maxRetries int
jitter time.Duration
shouldRetry func(error) bool
}</p><p>func WithBaseDelay(d time.Duration) func(<em>backoffConfig) {
return func(c </em>backoffConfig) { c.baseDelay = d }
}</p><p>func WithMaxRetries(n int) func(<em>backoffConfig) {
return func(c </em>backoffConfig) { c.maxRetries = n }
}</p><p>func WithJitter(j time.Duration) func(<em>backoffConfig) {
return func(c </em>backoffConfig) { c.jitter = j }
}</p><p>func WithShouldRetry(f func(error) bool) func(<em>backoffConfig) {
return func(c </em>backoffConfig) { c.shouldRetry = f }
}
</p>真正容易被忽略的是:抖动值 jitter 和 baseDelay 单位必须一致;maxRetries 包含首次尝试(即最多执行 maxRetries + 1 次),文档不写清楚,调用方极易误解。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










