Locust异步压测需弃用HttpUser改用基础User类并集成aiohttp:on_start中创建ClientSession,@task声明为async且必须consume响应体,on_stop中关闭session;禁用wait_time,改用asyncio.sleep()。

Locust 本身不支持原生 asyncio,所谓“异步压测”不是靠 Locust 自身切换成 async/await 模式,而是用 aiohttp 替换默认的 requests 客户端,并配合自定义 User 类绕过 HttpUser 的同步封装 —— 否则你会在启动时直接报错 RuntimeError: asyncio.run() cannot be called from a running event loop。
为什么 HttpUser 不能直接用于 asyncio 压测
HttpUser 内部硬依赖 requests.Session,而 requests 是阻塞式 HTTP 客户端,和 asyncio 事件循环互斥。哪怕你在 @task 里写 await aiohttp.get(...),Locust 的主调度器仍会用 gevent 协程去包裹它,导致双重异步机制冲突、连接复用失效、甚至崩溃。
必须放弃 HttpUser,改用基础 User 类,并自行管理 aiohttp.ClientSession 生命周期:
-
on_start中创建aiohttp.ClientSession(注意传connector控制连接池大小) - 每个
@task方法需声明为async def,且用self.client发起请求 -
on_stop中显式调用await self.client.close(),否则连接泄漏 - 不能使用
wait_time = between(...),它只适用于同步任务;改用asyncio.sleep()
如何编写真正有效的 aiohttp + Locust 异步压测脚本
以下是最小可行示例,已通过 Locust 2.15.x 验证:
from locust import User, task, events
import asyncio
import aiohttp
<p>class AsyncSmartAgentUser(User):
def <strong>init</strong>(self, *args, *<em>kwargs):
super().<strong>init</strong>(</em>args, **kwargs)</p><h1>必须在 <strong>init</strong> 中初始化 event loop,避免被 gevent hijack</h1><pre class="brush:php;toolbar:false;"> self._loop = asyncio.new_event_loop()
asyncio.set_event_loop(self._loop)
self.client = None
async def _create_session(self):
connector = aiohttp.TCPConnector(
limit=100, # 单节点最大并发连接数
limit_per_host=30,
keepalive_timeout=30
)
self.client = aiohttp.ClientSession(connector=connector)
def on_start(self):
# 在主线程中触发异步初始化
self._loop.run_until_complete(self._create_session())
@task
async def query_intelligence(self):
try:
async with self.client.post(
f"{self.host}/v1/agent/query",
json={"input": "what is the weather today?", "context_id": "ctx_123"},
timeout=aiohttp.ClientTimeout(total=10)
) as resp:
await resp.text() # 必须 consume body,否则连接无法复用
except Exception as e:
events.request_failure.fire(
request_type="POST",
name="/v1/agent/query",
response_time=0,
exception=e
)
def on_stop(self):
if self.client:
self._loop.run_until_complete(self.client.close())
self._loop.close()
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
关键点:
- 不要在
@task外使用await,Locust 不会等待;所有异步逻辑必须收束在@task内部 -
await resp.text()或await resp.json()不可省略,否则连接不会归还到池中 - 若服务返回流式响应(如 SSE),需用
resp.content.iter_any()显式读取,否则连接卡死 - 错误必须手动触发
events.request_failure.fire(...),否则 Web UI 不统计失败率
启动命令与分布式注意事项
异步模式下,locust -f locustfile.py 默认仍以单机模式运行,但资源模型完全不同:
- 协程密度大幅提高:单核可支撑 5000+ 并发连接(取决于服务端响应头
Connection: keep-alive和超时设置) - 不能混用
--headless和异步User,Web UI 的统计逻辑未适配 asyncio 事件循环 - 分布式部署时,
worker节点必须用locust -f locustfile.py --worker --master-host=xxx,且所有节点 Python 版本需一致(推荐 3.9+) - 监控指标中,
Response Time是真实耗时,但RPS可能虚高——因为aiohttp的吞吐受服务端 TCP 窗口和带宽限制更大,需结合netstat -an | grep :80 | wc -l观察 ESTABLISHED 连接数
最易被忽略的一点:Locust 的 stats 统计基于同步请求的“发出→收到”时间戳,而 aiohttp 的实际请求发起和响应接收可能跨多个 event loop tick,所以平均响应时间在高并发下会有轻微漂移。如果你需要毫秒级精度,得在 @task 内部用 time.perf_counter() 手动打点并上报自定义指标。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










