正确做法是每次重试前调用 context.WithTimeout 创建新 context 并及时 cancel,用 time.Sleep 或 select+time.After 实现可取消的退避,超时、退避、重试次数三者解耦,HTTP client 超时设为 0 以完全依赖 context。

用 context.WithTimeout 包裹每次重试的请求逻辑
Go 的超时必须绑定到 context.Context,而重试时不能复用同一个 context(否则第一次超时后整个链路就 cancel 了)。正确做法是在每次重试循环内新建带独立超时的 context。
常见错误是把 context.WithTimeout 放在 for 循环外,导致第二次重试直接继承已 cancel 的 context,返回 context.Canceled。
- 每次重试前调用
ctx, cancel := context.WithTimeout(parentCtx, timeoutPerRetry) - 务必在本次重试结束时调用
cancel()(哪怕成功或失败),避免 goroutine 泄漏 - 不要把外部传入的
ctx直接传给 HTTP client 或数据库操作 —— 它可能已过期或太短
用 time.AfterFunc 或 time.Sleep 控制退避间隔,别用 time.After 阻塞
退避(backoff)本质是“等一段时间再发起下一次请求”,但若用 select + time.After 等待,会阻塞当前 goroutine 且无法响应上级 context 取消信号。
更安全的做法是:先 sleep,再检查是否该继续重试。
- 用
time.Sleep(backoffDuration)最简洁;若需响应取消,改用select+time.After+ctx.Done() - 退避时间建议递增,比如指数退避:
backoffDuration = time.Duration(math.Pow(2, float64(attempt))) * time.Second - 注意:
time.Sleep不受 context 控制,所以 sleep 前应先判断ctx.Err() != nil,避免无意义等待
组合超时与重试的典型结构:显式管理 attempt、timeout、backoff
没有标准库函数能自动处理「每次不同超时 + 每次不同退避」,必须手动写循环。关键在于把三者解耦:超时作用于单次请求,退避作用于两次请求之间,attempt 计数用于终止条件。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
for attempt := 0; attempt // 2. 执行请求(如 http.Do、db.QueryContext)
resp, err := doSomething(reqCtx)
if err == nil {
return resp, nil
}
// 3. 判断是否值得重试(如网络错误、5xx)
if !shouldRetry(err) {
return nil, err
}
// 4. 计算并等待退避间隔(最后一次不 wait)
if attempt <p>}
</p><p>注意 <code>timeouts</code> 和 <code>backoffs</code> 是切片,允许每轮完全独立配置 —— 这正是你问题里要的“独立设置”能力。</p><h3>容易被忽略的坑:HTTP client 的 <code>Timeout</code> 字段会覆盖 context 超时</h3><p>如果你用 <code>http.Client</code> 发请求,且设置了 <code>client.Timeout</code>,它会优先于 <code>context.WithTimeout</code> 生效,并且无法被 cancel 中断(Go 1.20+ 有所改善,但仍有边界情况)。</p>
- 始终将
http.Client.Timeout设为 0,完全依赖context控制超时 - 确保所有下游调用(如
database/sql的QueryContext、grpc.ClientConn.Invoke)都接收 context 参数 - 自定义 transport 或中间件时,检查是否无意中屏蔽了 context 传递(比如用了
http.DefaultClient却没设Transport的IdleConnTimeout)
真正的难点不在语法,而在确保整条调用链每个环节都尊重 context —— 少一个环节,你的超时和重试就可能失效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










