收到 429 先停下,有 Retry-After 照它等,没有就翻倍加随机错开

429 一出现,这个接口上的新请求就一个都别再发,然后看这次响应的响应头里有没有 Retry-After 这一行。响应头是服务器回信里排在正文前面的几行说明,在程序的日志或调试输出里找。有 Retry-After,就按它给的时间等够再发,等待期间不要插一个请求进去"看看好了没有"。
响应里没有 Retry-After,等多久就得你这边定:头一次等一个很短的基数,比如 1 秒;之后每失败一次,把等待时间翻倍;每次再让实际等待在这个数以内随机浮动;同时定一个封顶值和最多重试几次,次数用完就停下来通知人。这套做法叫指数退避加随机抖动。
429 Too Many Requests 表示这段时间内请求发得太多
429 是一个 HTTP 状态码,名字叫 Too Many Requests。按 RFC 6585(定义这个状态码的互联网标准文档)的定义,429 表示用户在给定的一段时间里发送了太多请求,这个状态码就是给限流用的。
同一份文本里,排错用得上的还有三点。响应正文应当带上解释情况的内容,所以正文值得读,别只看状态码。响应可以带 Retry-After 告诉你等多久,用的词是"可以",也就是说它可能不出现。标准不规定服务器怎么认人、怎么计数:可以按单个资源数,可以按整台服务器数,也可以跨一组服务器数;认人可以凭认证凭据,也可以凭 cookie。
同样一个 429,多少次算多、按多长的时间窗算、几个接口是分开数还是合起来数,只有你用的那家接口文档能回答。收到 429 也不用先怀疑参数或签名写错了,它说的是发送节奏的事。
Retry-After 响应头怎么读,秒数和日期两种写法
Retry-After 表示在发下一次请求之前应当等多久,它的值有两种写法,见 RFC 9110 里 Retry-After 的条目:
- 秒数:一个非负整数,例如 Retry-After: 120,就是等 120 秒,也就是 2 分钟。
- 日期:一个 HTTP 格式的时间,例如 Retry-After: Fri, 31 Dec 1999 23:59:59 GMT,意思是到这个时刻之后再来。
秒数那种写法,建议从收到这条响应的时刻起算,等满再发。日期那种写法要多留意两处。结尾的 GMT 说明它用的是格林尼治时间,北京时间比它快 8 小时,换算错了会早到或晚到。另外它要拿你这台机器的时钟去比,机器时间不准,算出来的等待就不准,所以跑程序的机器先开自动对时。算出来的时刻已经过去了,就当作没给等待时间处理。
用现成机器人的话,先确认日志里看得到响应头。日志里只有状态码、没有响应头,就到设置里找日志级别或调试输出的开关,调高之后再触发一次看。工具如果有"遵从 Retry-After"之类的选项,打开它。
先停:暂停对这个接口的轮询和重试,已经排着队的请求也别放出去。再读:把这次响应的状态码、响应头和响应正文整段记进日志,找 Retry-After。然后等:有 Retry-After 就照它等,没有就从基数开始退避;等够之后只放一个请求出去探路,成功了再恢复原来的节奏。
响应里没有 Retry-After 时,用指数退避加随机抖动
指数退避的算法很短:每次重试的等待 = 基数 × 2 的若干次方,算出来超过封顶值就按封顶值算。AWS 架构博客的《Exponential Backoff and Jitter》把它写成 min(cap, base * 2 ^ attempt),cap 是封顶值,base 是基数,attempt 是已经重试过几次。
只翻倍还不够。那篇文章用多个客户端同时争抢同一个资源的场景做了模拟:大家同时失败,又按同样的间隔一起回来,调用仍然一簇一簇地出现。抖动就是往等待时间里掺随机数,把这一簇摊开。文中给了几种掺法,其中两种好懂:
- 全抖动(Full Jitter):在 0 和这一次的等待上限之间随机取一个数。
- 等量抖动(Equal Jitter):等待上限的一半固定要等,另一半随机取。
拿基数 1 秒、封顶 60 秒、每次翻倍来算,头一次重试时 attempt 记 0,也就是乘 2 的 0 次方,八次重试的等待如下(单位:秒):
| 重试次序 | 这一次的等待上限 | 全抖动的实际等待 | 等量抖动的实际等待 |
|---|---|---|---|
| 1 | 1 | 0 到 1 | 0.5 到 1 |
| 2 | 2 | 0 到 2 | 1 到 2 |
| 3 | 4 | 0 到 4 | 2 到 4 |
| 4 | 8 | 0 到 8 | 4 到 8 |
| 5 | 16 | 0 到 16 | 8 到 16 |
| 6 | 32 | 0 到 32 | 16 到 32 |
| 7 | 60(算出 64,按封顶值) | 0 到 60 | 30 到 60 |
| 8 | 60 | 0 到 60 | 30 到 60 |
把第二列加起来是 183 秒:八次重试全部等满,一共 3 分 3 秒。封顶值从第 7 次重试起生效;没有它,照这样翻倍,第 10 次重试要等 512 秒。
AWS 那篇文章的结论是,不加抖动的指数退避是几种做法里明显最差的一种,带抖动的退避应当作为远程调用客户端的标准做法;和等量抖动相比,那次模拟里全抖动发出的调用略少,完成得也快得多。全抖动有可能抽到接近 0 的数,等于几乎没等就重发,介意这一点就用等量抖动,或者给等待加一个下限。
倍数也不是非 2 不可。Google Cloud 存储文档的重试策略页把 429 和 408、5xx 一起列为可以重试的响应,建议的也是带抖动的指数退避,页面上一段示例代码用的是初始等待 1 秒、倍数 3.0、最长 1 分钟。照这组数算,等待上限依次是 1、3、9、27 秒,下一次算出 81 秒,按 60 秒封顶。倍数大,退得快,重试几次就到封顶值;倍数小,恢复得快,多占几次额度。行情查询可以退得狠一点,撤单这类急事用小一点的倍数,并且在平时就给它留出额度。
立刻重试、固定间隔重试为什么会让 429 越来越多
429 的意思是这段时间里发多了。被拒的请求算不算数由服务器自己定;算的话,马上再发等于往同一段时间里又添了一次,下一个 429 来得更早。循环写成"失败就重发"的程序,中间没有任何等待,添得最快。
固定间隔重试,比如每 1 秒重发一次,比立刻重试慢,毛病是同一种:限制没解除的这段时间里,每一秒仍然在往计数里添。几个循环用同一个间隔的话,它们会一直踩在同一拍上,一起发、一起被拒。只翻倍不加随机尚且会成簇,固定间隔连把间隔拉开这一步都没有。
重试循环还有一个更麻烦的后果:额度被它耗光之后,撤单这样的请求也发不出去。这条路径写在自动下单程序的九类故障讲限频的那部分。
429 和 403、418、IP 被封有什么区别
这几样在日志里容易被当成同一件事,处理办法差得远。
403 Forbidden 的含义是服务器看懂了请求,但拒绝执行,拒绝的理由可以写在响应正文里。它和发送节奏没有必然关系,拒绝的理由没变,等一等再发也换不来别的结果。遇到它先读响应正文,再去查密钥的权限范围、来源地址限制这类设置。
418 在现行 HTTP 标准的状态码表里标着 Unused,属于保留未用,标准没有给它含义。哪家接口返回了 418,它代表什么只能看那家自己的文档,别拿别家的解释来套。
"IP 被封"没有对应的专用状态码。标准并不要求服务器一定用 429 来限流,直接断开连接、或者采取别的措施,也在允许之列。所以更重的限制在你这边看到的,可能是连接被断开、请求超时,而没有任何一个带状态码的响应。
至于收到 429 之后继续猛发会不会招来更重的限制、持续多久,标准没有规定,各家的规则写在各家文档讲限流和违规处理的部分,动手之前先读那一段。建议把 429 当成对方明确叫你放慢的信号:它还肯回一个状态码、甚至告诉你等多久,这时照着做,代价只是多等一会儿。
429、Retry-After、403、418 这几处对照的是 RFC 6585(2012 年 4 月发布)和 RFC 9110(2022 年 6 月发布)的文本;退避与抖动对照的是 AWS 架构博客 2015 年 3 月发表、2023 年 5 月补过更新说明的那篇,以及 Google Cloud 存储文档页脚标着 2026-09-30 更新的重试策略页。
429 反复出现时,先从日志判断撞的是哪种上限,再调请求间隔和并发数
调设置之前先弄清是哪一种上限在拦你。接口文档里的计法可能有这样几种:按请求次数算,按权重算(不同接口调用一次扣的分不一样),按下单数算。你用的那家用哪几种、窗口多长,以它的文档为准。日志这边,每次出现 429 时记下三样东西:时间、被拒的是哪个接口、此前一分钟里各个接口分别发了多少次。然后对着文档看:
- 所有接口同时开始返回 429,前一分钟的总请求数又明显偏高,先按请求次数这一类去查。
- 总次数不高,但 429 出现在刚调过某个"重"接口之后,比如一次拉很长一段历史数据,先查文档里各接口的权重表。
- 查行情一切正常,只有下单、改单被拒,先查针对下单数的上限。
响应头整段都记下来还有一个用处:对照文档,看里面有没有回报已用额度的字段。有的话,程序可以在撞上限之前就自己放慢。
查清以后,能动的设置是这几样:
- 请求间隔(也叫轮询周期、刷新频率):平时多久问一次接口。它决定你离上限有多远,撞的是请求次数或权重这类上限时先把它调大。
- 重试次数:一次请求失败后最多再试几回。别设成无限,用完就停下,同时发通知。
- 退避倍数(常和初始等待、最大等待放在一起):对应上面表里的基数、倍数、封顶值。只有一个"重试间隔"而没有倍数的工具,做的是固定间隔重试,把间隔设长一些,重试次数设少一些。
- 并发数(同时处理几个交易对、同时开几个连接):并发数乘以每个任务的请求频率,才是你真实的发送速度。
接着读:请求发出去之后迟迟没有回应、不知道单子成没成,处理顺序和 429 不一样,写在 下单请求超时后先查单再决定重发 里;轮询行情的间隔调大以后,程序看到的价格会更旧,这一点的影响见 行情价为什么各家不一样。