批量调用被限流,先别急着降并发。判断的关键是看错误回来的语义:如果提示偏向“稍后重试”“请求过快”,通常是短时窗口限流,降低并发或拉开间隔就能缓解;如果提示偏向“用量已用尽”“配额/额度不足”,那就是配额问题,继续重试只会浪费时间和请求预算,正确动作是记录进度、把未完成项放进队列、等配额重置后从断点继续。两种情况的处理顺序不同,混在一起调参数往往越调越乱。
先看错误文案和响应状态,把“短时限流”和“配额耗尽”分开归因;短时限流用并发上限加最小间隔解决,配额耗尽用队列缓冲加等待重置解决。判断依据是可观察的响应状态、错误文案和错误随时间的变化趋势,不是猜测。不同账号等级与计费方式的表现可能不同,最终阈值需要在自己环境里用阶梯测试确认。
在响应头或错误信息里区分限流类型
建议在批量任务里给每次请求统一打三样东西:发起时间戳、响应状态、错误文案原文(不要只记“失败”),再补上请求序号或业务键,方便回捞。这些字段可以先落到本地日志或一张排错表里,不依赖具体接口字段名,各家返回结构不一样,但上面三类信息通常都能拿到。
拿到日志后按时间窗口(例如 1 分钟、5 分钟)分组统计错误条数,看错误是否随时间自然衰减。短时限流的典型表现是:集中报错后隔一小段时间,同样参数的请求又能成功;配额耗尽的典型表现是:在相当长的一个窗口内,降低并发、拉长间隔,成功数依然接近零,错误文案里反复出现额度、用量或预算相关表述。
还有一种容易误判的情况:认证失效或参数错误也会表现为大批失败,但它既不是限流也不是配额,重试没有意义。建议把这类错误单独归一类,命中即停止,不要参与退避循环。
用阶梯并发测试找到触发限流的阈值
确定稳定并发范围的方法是从低往高加,而不是从高往低降。可以先从并发 1 或 2 跑一批固定数量的请求,记录成功数、失败数、错误类型;稳定后翻倍再跑同一批,重复到开始出现失败为止。
- 固定请求总量和参数,避免变量混在一起。
- 每一档持续足够长时间,覆盖至少一个限流窗口。
- 记录每档的失败率和首个错误出现的时间点。
- 出现失败后不要立刻停,再观察一段时间,确认错误是否自愈,用来区分短时限流和配额耗尽。
档位 并发 请求数 成功 失败 主要错误类型 是否自愈
1 1 200 ... ... ... -
2 2 200 ... ... ... -
3 4 200 ... ... ... -
4 8 200 ... ... ... -
出现第一档失败的上一档,就是当前环境可稳定运行的并发参考值。留出余量,不要直接压在上限运行。
加入信号量或队列控制并发并记录等待时间
把并发压到安全范围内,最直接的做法是用信号量限制同时进行的请求数,超出的请求在获取信号量处排队。排队本身会带来等待,建议把等待时间也记下来,否则任务变慢时无法判断是接口慢还是自己排的队太长。
import asyncio, time
CONCURRENCY = 4 # 先用阶梯测试得到的保守值
sem = asyncio.Semaphore(CONCURRENCY)
async def call_one(idx, payload, log):
t0 = time.monotonic()
async with sem: # 排队发生在 acquire 处
waited = time.monotonic() - t0 # 排队等待时间
resp = await send(payload) # 替换为实际调用
log.info("idx=%s waited=%.3fs status=%s", idx, waited, resp.status)
return resp
async def main(items):
tasks = [call_one(i, p, log) for i, p in enumerate(items)]
return await asyncio.gather(*tasks, return_exceptions=True)
如果等待时间持续增长而请求本身没变慢,说明并发上限设置低于当前吞吐需求,可以考虑在失败率不变的前提下小幅上调;如果等待时间很短但失败集中出现,说明瓶颈不在排队,而在限流窗口本身,应转向拉长间隔或加队列缓冲。
对配额类错误设计队列缓冲与重试上限
配额耗尽不会因为多试几次就恢复,因此批量任务要能“停得住、续得上”。建议把待处理项放进队列,命中配额类错误时暂停出队,而不是让每个请求各自重试到底。
batch:
concurrency: 4 # 同时进行的请求数上限
min_interval_ms: 200 # 两次发起之间的最小间隔
queue_max: 500 # 队列缓冲深度,超出则暂停入队
retry_max: 3 # 单个请求最大重试次数
backoff_base_ms: 500 # 退避基数
backoff_cap_ms: 8000 # 单次等待上限,避免无限拉长
stop_conditions:
- kind: quota_exhausted
consecutive: 5 # 连续命中配额类错误达到次数即暂停整批
- kind: auth_or_config_error
consecutive: 1 # 认证或参数错误不重试,直接停止
配置里要明确两点:单个请求的重试次数上限,以及整批任务的停止条件。重试上限控制的是单点噪声,停止条件控制的是整批失败风险。命中停止条件后,把已完成项的标识落盘,等配额窗口过去再从未完成部分继续,比重新跑一遍更可控。
整理可配置的并发参数与回压策略
把参数集中到配置文件或环境变量里,避免散落在代码各处。三类核心参数的建议方向如下,具体数值以阶梯测试结果为准:
- 并发数:从 1 到 2 起步,按阶梯测试找到稳定档位后取略低于该档的值。宁可低一点,也不要贴上限跑。
- 最小间隔:短时限流场景下通常需要,在两次发起之间加固定或带抖动的等待,抖动可以避免请求整齐地撞在同一时间点。
- 重试上限:单个请求保持较小值,配合退避基数与退避上限使用;退避上限存在的意义是防止等待时间被指数拉长到不可接受。
调整顺序建议是:先定并发上限,再定最小间隔,最后定重试上限与退避上限。每改一次只改一个参数,跑同一批固定请求做对比,否则无法知道是哪个参数起了作用。回压策略的核心是队列满或命中停止条件时,任务能主动暂停并保留进度,而不是把请求全部发出去再一起失败。