go语言http.client本身不支持自动重试,必须由业务层手动封装;timeout和transport参数仅控制超时与连接复用,与重试无关;可重试错误包括context.deadlineexceeded、net.operror及5xx/408/429等,不可重试4xx客户端错误;post/put需确保body可重放,推荐用[]byte+bytes.newreader并每次req.clone(ctx)。

http.Client 本身不支持重试,所谓“框架中实现”其实是业务层封装——你得自己控制重试逻辑,而不是等某个框架自动给你加上。
为什么 http.Client.Timeout 和 Transport 参数不能替代重试
http.Client.Timeout 只控制单次请求总耗时,超时后直接返回 context.DeadlineExceeded 或 net/http: request canceled,不会发起第二次请求;Transport 的 MaxIdleConnsPerHost 等参数只影响连接复用,和失败重发完全无关。线上故障里,误以为设了 Timeout 就等于“有重试”,是最常见的配置盲区。
哪些错误值得重试,哪些绝对不该重试
- 可重试的典型错误:
context.DeadlineExceeded(上层超时)、net.OpError(含"i/o timeout"、"connection refused"、"no such host")、HTTP 状态码500/502/503/504、408、429 - 不可重试的错误:
400、401、403、404、405——这些是客户端语义错误,重试只会放大问题 - 判断方式必须用
errors.Is(err, context.DeadlineExceeded)或errors.As(err, &netErr),别用strings.Contains(err.Error(), "timeout"),容易误判
带 body 的 POST/PUT 请求怎么安全重试
*http.Request.Body 是 io.ReadCloser,读完即关闭,不能重复读。直接重试会导致后续请求 Body 为空,服务端收不到数据。
- 推荐做法:把原始 payload 存为
[]byte,每次重试前用bytes.NewReader(payload)新建Body - 必须调用
req.Clone(ctx),否则Body已关闭或上下文失效,第二次Do会报http: Request.Body is nil - 避免用
strings.NewReader直接传字符串——中文或特殊字符可能因编码出错,统一走[]byte + bytes.NewReader - 如果 payload 很大(>1MB),别反复拷贝,改用可 seek 的
io.Seeker实现(如bytes.Reader支持Seek)
用 backoff/v4 封装重试最省心,但几个坑必须避开
社区最常用的是 github.com/cenkalti/backoff/v4(v4 支持 Go modules,修复了 context 取消传播问题;别用已归档的 v3)。
- 必须用
backoff.WithContext(b, ctx),否则req.Context().Done()无法中断重试循环,导致 goroutine 泄漏 -
backoff.NewExponentialBackOff()默认MaxInterval = 128 * time.Second,对 API 调用太长,要显式设b.MaxInterval = 1 * time.Second - 重试函数里不能复用同一个
*http.Request,每次都要req.Clone(ctx)并重置Body - 用
backoff.WithMaxRetries(..., 3)明确上限,太多会放大下游压力
真正复杂的地方不在“怎么写 for 循环”,而在于错误分类是否精准、body 是否可重放、context 取消能否及时生效——这三点漏掉任一个,重试就从可靠性保障变成故障放大器。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











