http客户端遇429应优先解析retry-after头(秒数或http日期),无此头再启用上限30秒的指数退避;需用自定义http.client配置总超时,避免阻塞goroutine,结合rate.limiter主动限流与429被动容错分层使用。

Go HTTP客户端遇到429 Too Many Requests怎么自动重试
直接重试不行,必须等服务端给的Retry-After头,或者自己按指数退避节奏来。硬写time.Sleep(1 * time.Second)在真实API里大概率触发更长封禁。
- 优先检查响应头:
resp.Header.Get("Retry-After"),它可能是秒数(如"60")也可能是HTTP日期(如"Wed, 21 Oct 2024 07:28:00 GMT") - 没这个头时,才启用指数退避:第1次等
1s,第2次2s,第3次4s……上限建议设为30s,避免单次请求卡太久 - 别在
http.DefaultClient上直接改Timeout——重试期间需要更长总超时,建议用自定义http.Client并设Timeout为“单次请求超时 × 最大重试次数 + 累计等待时间”
用golang.org/x/time/rate做客户端限频,和429回退冲突吗
不冲突,但角色完全不同:限频是“主动控速”,429回退是“被动容错”。两者要分层使用,不能互相替代。
-
rate.Limiter放在请求发出前,控制QPS,适合预防性限流(比如你承诺不超10qps) -
429处理放在请求返回后,属于兜底逻辑,应对突发限频、服务端策略变更或限频阈值误判 - 如果同时用了
rate.Limiter又收到429,说明服务端限频比你本地设得更严——此时应临时降低rate.Limiter的r(速率)或b(桶容量),否则后续请求大概率继续429
实现指数退避时,time.Sleep阻塞goroutine,会影响并发吞吐吗
会,而且影响明显。一个goroutine卡住,就少一个可用worker;高并发下容易堆积大量待重试请求,反而加重服务端压力。
- 别用同步
time.Sleep,改用time.AfterFunc或带超时的select+time.After - 更稳妥的做法是把重试逻辑抽成异步任务:失败后推入延迟队列(如用
time.Timer或简单map+goroutine管理),原goroutine立刻返回 - 注意
context.WithTimeout要包住整个重试流程,不是单次请求——否则第2次重试还没开始就被cancel了
为什么有些API返回429却不带Retry-After头,自己算指数退避又老被拒绝
因为服务端根本没打算让你重试,或者它的限频策略是动态的(比如基于IP+用户token双维度)。这时候硬退避只是拖延失败时间。
立即学习“go语言免费学习笔记(深入)”;
- 先确认是否真该重试:读API文档,看
429是否明确标注“可重试”,有些平台(如GitHub API)要求你降级到更低配额等级再试 - 检查请求标识:确保
User-Agent、Authorization、甚至X-Forwarded-For没被污染,同一token在多个出口IP间跳会造成限频叠加 - 记录每次
429的响应体,有些服务会在body里返回提示,比如{"message":"rate limit exceeded for user"}——这种比空响应更值得解析
真正难处理的不是退避算法本身,而是判断“该不该退避”:服务端沉默、文档模糊、错误体不一致,这些才是日常踩坑最多的地方。










