当前位置:首页 > 文章列表 > 文章 > python教程 > Python asyncio 批量请求变慢:用连接池和并发上限稳住接口耗时

Python asyncio 批量请求变慢:用连接池和并发上限稳住接口耗时

来源:17golang原创 2026-07-20 10:03:19 0浏览 收藏

订单补数任务原本只拉200条记录,后来调整为单次补8000条。代码把每个请求都塞进 asyncio.gather(),本地测试跑起来速度很快,一到预发环境就频繁出现连接等待、429报错和P99耗时抖动。问题根源不在asyncio本身性能不足,而是并发任务数、HTTP连接数和上游服务能承接的请求量没有匹配对齐。

实践要点
  • 先记录P50、P95、错误率和连接等待指标,再判断是否真的需要调高并发。
  • 全局复用同一个 httpx.AsyncClient,靠连接池实现连接复用,不要每个请求都单独新建客户端。
  • asyncio.Semaphore 的上限要结合上游服务容量和超时预算实测调试出来,不能直接照着CPU核数随便填。
  • 超时、任务取消和重试逻辑必须共用同一份请求配额,不然少量失败就会被层层放大演变为全链路请求积压。

先看基线:为什么80个任务反而把尾延迟拉高

遇到这类问题别急着直接把并发数改到500。订单补数服务的 /v1/settlement/detail 平均响应耗时约180ms,上游服务约定单实例能稳定承接30个并发请求。压测过程中把并发任务数从12提升到80之后,吞吐量没有跟着线性上涨:可用连接很快被占满,处于等待状态的协程在超时阈值边缘不断堆积,后续触发的重试又把下一轮请求压力推得更高。

并发上限P95 耗时成功率观察
12310ms99.9%连接复用状态稳定
24420ms99.8%吞吐量接近上游峰值
481.8s98.7%请求等待和限流开始出现
804.6s94.1%超时和重试互相放大影响

这里给出的数值不是通用配置,只是用来演示判断的先后顺序:先找到稳定运行的区间,再决定要不要继续调高并发。如果上游返回的429占比已经明显上升,继续扩充并发任务数只会更快地生成大量失败请求。

Python asyncio 批量请求从提交、获取连接、上游响应到超时重试的时间线,展示并发过高时等待增加

把客户端初始化放在循环外:连接复用才有实际意义

最常见的错误用法是在循环里或者单个协程内部单独创建 AsyncClient。这种写法下每次请求都可能重新建立TCP连接,连接池也就完全失去了复用的价值。下面的示例把客户端和连接限制放在整批任务的外部初始化,同时给请求分别设置连接阶段、读取阶段的耗时阈值和总时长配额。

import asyncio
import httpx

limits = httpx.Limits(max_connections=32, max_keepalive_connections=16)
timeout = httpx.Timeout(timeout=3.0, connect=0.5, read=2.0)
gate = asyncio.Semaphore(24)

async def fetch_detail(client: httpx.AsyncClient, order_id: str) -> dict:
    async with gate:
        response = await client.get(
            "https://partner.example.com/v1/settlement/detail",
            params={"order_id": order_id},
        )
        response.raise_for_status()
        return response.json()

async def fetch_batch(order_ids: list[str]) -> list[dict]:
    async with httpx.AsyncClient(limits=limits, timeout=timeout) as client:
        jobs = [fetch_detail(client, order_id) for order_id in order_ids]
        return await asyncio.gather(*jobs)

max_connections 是客户端最多能同时占用的连接数量,Semaphore(24) 是业务侧允许同时发往上流的请求数量。两个值不需要完全相等:如果单个请求后续还会访问其他域名,总连接数可以设置得稍大一点;如果当前这个上游的限流规则更严格,业务侧的并发闸门要设置得更小。

并发闸门的放置位置,决定系统是排队等待还是直接失控

并发闸门只要包住真正消耗上游资源的那小段请求逻辑就行,不需要把整批任务的所有流程都锁住。上面的示例在发起HTTP调用前才进入 gate,请求完成后立刻释放配额,解析响应结果、写入本地日志这类轻量操作不需要一直占着并发名额。

如果后续处理还要把结果写回数据库,还要单独核对数据库的连接池配置。不要用同一个并发数同时管控HTTP上游和数据库操作:两类资源的服务耗时和承载能力通常差异很大。必要的时候可以给两个不同的资源分别设置独立的并发闸门,同时在监控指标里区分开 upstream_wait_msdb_wait_ms

Python asyncio 的并发闸门控制订单请求进入 HTTP 连接池并返回成功或限流结果的二维技术插画

压测时只调整单个变量,才能准确定位瓶颈位置

对比不同配置的效果时,保持请求数据量、上游运行环境、超时和重试策略完全一致,只按档位逐步调整并发上限。每一档配置至少要观察三类核心信号:

  • 业务结果:请求成功率、429占比、可重试错误的占比。
  • 耗时分布:P50/P95/P99耗时、连接阶段耗时和总超时请求数。
  • 资源状态:活跃连接数、等待协程数量、上游实例的CPU使用率或者限流计数。

一份清晰的实测结果记录,远比“感觉速度变快了”的主观判断靠谱。如果并发设24和32的吞吐量差不多,但32的P99耗时明显变差,更建议选择24这个档位,给突发流量和上游服务的正常抖动留出足够的冗余空间。

超时和重试要有明确边界,别让失败批次无限拉长

连接超时可以设置得短一些,读取超时按照接口的历史运行耗时配置,总耗时配额要覆盖一次完整调用的全流程。针对429、连接被重置这类临时故障,可以做少量带退避逻辑的重试;遇到4xx类的参数错误直接记录失败即可,不要继续发起无效请求。重试逻辑也必须走同一个并发闸门,不然系统拥塞的时候重试会直接形成一波额外的请求洪峰。

批量任务还要区分两种运行模式:允许部分失败、任意失败就终止全流程。前者可以用 asyncio.gather(..., return_exceptions=True) 收集每一条任务的运行结果;后者在检测到关键失败后直接取消剩余未执行的任务,同时把已经执行完成的记录落盘,避免下一次重复执行相同的补数操作。

延伸问答

连接池上限和Semaphore应该设成同一个数值吗?

不需要。连接池限制的是客户端的总连接数量,并发闸门限制的是业务逻辑侧对上流的同时访问数量。按照不同资源边界分开设置,后续排查问题的时候逻辑会更清晰。

为什么asyncio.gather会让接口突然变慢?

它会尽可能快地调度所有传入的协程。当总任务数远大于下游服务的承载能力时,请求等待、限流和重试的数量会同步上涨,尾延迟往往先出现失控。

只调大max_connections参数能解决429报错吗?

不能。429报错说明上游服务正在做限流或者你的配额已经用完,增加连接数只会更快触发限流规则。正确的处理方式是降低并发、缩小单批任务的数量或者和上游服务的对接方确认可承载的容量上限。

批量任务执行失败后怎么避免重复处理相同数据?

给每一条业务记录单独保存幂等键和执行状态;下一次启动任务的时候只拉取未完成的条目,同时把每条记录的重试次数和最后一次报错信息一并存储下来。

把并发当成配额使用,不要当成一键提速开关

asyncio可以很高效地把多个I/O等待的时间段重叠起来提升效率,但它没办法凭空增加上游服务的承载能力。全局复用同一个HTTP客户端、明确连接数和业务侧的并发闸门、用同一份超时配额约束所有重试逻辑,再通过实际压测拿到的指标选出最稳定的配置档位,批量请求的流程在流量上涨之后也能保持可控。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go 1.25 testing/synctest 怎么写并发测试:用虚拟时间替掉 time.SleepGo 1.25 testing/synctest 怎么写并发测试:用虚拟时间替掉 time.Sleep
上一篇
Go 1.25 testing/synctest 怎么写并发测试:用虚拟时间替掉 time.Sleep
Python 3.14 free-threaded build 转正:服务端多线程任务该怎么评估并迁移
下一篇
Python 3.14 free-threaded build 转正:服务端多线程任务该怎么评估并迁移
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ljg-skills -
    ljg-skills
    ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
    4638次使用
  • MELO音乐 - AI 音乐生成平台,支持多模态创作能力
    MELO音乐
    MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
    4251次使用
  • UniScribe - AI 免费在线音视频转文字平台
    UniScribe
    UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
    4209次使用
  • 剧云 - 免费 AI 智能中文剧本创作平台
    剧云
    剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
    4432次使用
  • 万象有声 - AI 一站式有声内容创作平台
    万象有声
    万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
    4386次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码