asyncio搭配backoff仅重试失败API而非整批请求的方案问询
问题根源
你当前的问题根源是把重试装饰器加在了整批请求的外层函数上,且单条请求触发429时直接抛出异常,导致外层函数触发重试逻辑,整批10个请求全部重新执行。调整逻辑的核心是把重试粒度从整批降到单个请求。
调整方案
1. 封装单个请求函数,将重试逻辑迁移到单请求层
把backoff装饰器从批量请求函数移除,转而装饰单个API请求的异步函数,让每个请求自己管理自己的重试逻辑,失败时不会影响其他已经成功的请求。
注意要把同步的time.sleep替换为异步的asyncio.sleep,避免阻塞整个事件循环。
# 单个请求的重试逻辑,装饰器加在单请求函数上 @backoff.on_exception(backoff.expo, aiohttp.ClientError, max_tries=20, logger=my_logger) async def fetch_single(session, url, params=None): async with session.get(url, params=params) as response: if response.status == 429: print("Code 429 received waiting for 2 minutes") # 必须用异步sleep,不能用同步time.sleep await asyncio.sleep(120) raise aiohttp.ClientError("Rate limit hit") return await response.read()
2. 修改批量请求逻辑
原来的批量请求函数不需要再加backoff装饰器,生成的任务列表直接对应每个带重试逻辑的单请求协程即可:
async def get_symbols(size, position): async with aiohttp.ClientSession() as session: # get_tasks内部调整为返回多个fetch_single(session, 接口地址, 请求参数)的协程对象 queries = get_tasks(session, size, position) # 可选:加return_exceptions=True避免单请求重试彻底失败后中断整批任务 responses = await asyncio.gather(*queries, return_exceptions=True) print("gathering responses") for result in responses: # 可以单独判断单个请求是否重试失败 if isinstance(result, Exception): print(f"Single request failed after max retries: {str(result)}") continue # 正常处理返回的接口数据 query_data = result # 后续业务逻辑
额外优化建议
- 如果希望避免多个请求同时触发429导致多次重复等待2分钟,可以设置一个全局的限流等待标记,所有请求遇到429时先检查是否已经有其他请求在触发等待,避免重复等待浪费时间。
- 可以根据API返回的429响应头里的
Retry-After字段来动态调整等待时间,比固定等2分钟更合理。
内容的提问来源于stack exchange,提问作者Javip
相关产品推荐
相关产品推荐

