超时必须用 context.withtimeout 控制整个生命周期,重试需手动编排或选 backoff/v4 等成熟库,二者不可混用,http.client.timeout 无法覆盖响应体读取且不支持主动 cancel。

直接结论:超时必须用 context.WithTimeout 控制整个生命周期,重试必须手动编排或选成熟库(如 backoff/v4),两者不能混用,也不能依赖 http.Client.Timeout 做业务级兜底。
为什么不能只靠 http.Client.Timeout
http.Client.Timeout 看似方便,但它只覆盖「连接建立 + 响应头读取」阶段,不包含响应体读取。比如服务端返回 200 后缓慢流式写入大文件,Timeout 不会中断,goroutine 就卡死了。更危险的是,它无法配合 cancel 信号做主动中断——上游请求取消后,底层连接仍可能继续跑完几秒甚至几十秒。
真正可控的方式是:每次请求都新建带 deadline 的 context,传给 req.WithContext(ctx),并 defer cancel()。这样无论卡在哪一环(DNS、TLS、写 body、读 header、读 body),都会在超时后统一返回 context.DeadlineExceeded 错误。
哪些错误值得重试,哪些必须立刻失败
重试不是补丁,而是有明确边界的策略。判断依据不是状态码数字本身,而是错误语义:
- 可重试:
context.DeadlineExceeded(说明只是抖动)、net.OpError中Temporary()返回 true 的情况(如 connection refused、i/o timeout) - 不可重试:
url.Error包含"parse"或"invalid"(URL 写错了)、tls.CertificateVerificationError(证书问题)、400/401/403/404(客户端问题,重试无意义) - 需谨慎:
5xx状态码要结合业务判断——如果是幂等接口(如带Idempotency-Key的 POST),可重试;否则跳过
别用 strings.Contains(err.Error(), "timeout") 判断,字符串匹配不稳定,且容易被中间件注入干扰文本。
手动重试时最容易踩的三个坑
手写 for 循环 + time.Sleep 看似简单,但实际落地常翻车:
- 没重建
*http.Request:POST 请求的req.Body是io.ReadCloser,第一次Do后就 EOF,后续重试发空 body —— 必须每次用bytes.NewReader(data)或提前设置req.GetBody - 复用同一个
context:第一次超时后ctx.Err()已触发,下次重试再传进去,client.Do直接返回context.Canceled,根本不会发请求 —— 每次重试都要新调context.WithTimeout - 退避时间没上限:指数增长到第 5 次就是 1.6 秒,第 7 次 6.4 秒,总耗时远超业务容忍 —— 建议设硬上限,比如
min(1
示例片段(非完整函数):
for i := 0; i <h3>用 <code>backoff/v4</code> 替代手写循环更可靠</h3><p>第三方库不是银弹,但 <code>github.com/cenkalti/backoff/v4</code> 解决了太多边界问题:自动 jitter 防雪崩、支持 <code>Stop</code> 条件提前退出、内置 <code>Retry</code> 和 <code>RetryNotify</code> 两种模式、可插拔的 backoff 策略(<code>ConstantBackOff</code>、<code>ExponentialBackOff</code>)。</p><p>关键点在于它把「是否重试」和「怎么退避」解耦了:</p>
- 用
backoff.Retry时,操作函数返回error,库自动判断是否重试(你只需在函数里抛出特定 error) - 用
backoff.RetryNotify可以加日志、埋点、熔断逻辑,比如第 3 次失败后上报 Prometheus - 它内部管理
context生命周期,你不用操心 cancel 泄漏
注意:即使用了库,req.Body 复用问题依然存在,必须确保每次重试的 body 可重放。
真正麻烦的从来不是写几行重试代码,而是想清楚「这次失败是不是真能恢复」——网络抖动、服务重启、DNS 缓存过期,这些才是重试的合理场景;而配置错误、权限不足、业务规则拒绝,重试只会让问题更难定位。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











