
在使用 aiohttp 发送大量(例如 50 个)大型(例如每个 5mb)http post 请求时,开发者可能会遇到显著的性能问题。核心挑战在于 aiohttp 内部处理 json 参数时,json 序列化过程(如 json.dumps)是同步且耗时的。对于大型数据负载,这可能导致事件循环被阻塞数十毫秒甚至更长时间。在 asyncio.gather 等并发场景下,这意味着所有请求的序列化操作会串行执行,导致请求并非“可用即发送”,而是等待所有序列化完成后才批量发送,从而显著增加整体延迟。
此外,DNS 解析也是潜在的性能瓶颈,尤其是在高并发或延迟敏感的应用中,每次新的连接都可能涉及 DNS 查询。
aiohttp 的 ClientSession.post 方法提供了一个 json 参数,方便地处理 JSON 数据的序列化和 Content-Type 头设置。然而,对于大型数据,这种便利性是以阻塞事件循环为代价的。为了避免这种情况,我们应该手动进行 JSON 序列化,并确保这个阻塞操作在独立的线程中执行,不影响主事件循环。
不再使用 json 参数,而是将 Python 对象预先序列化为字节串,并通过 data 参数传递。同时,手动设置 Content-Type 头。
import json
import asyncio
import aiohttp
async def prepare_data_in_thread(obj) -> bytes:
"""在单独的线程中同步执行 JSON 序列化,避免阻塞事件循环。"""
# json.dumps 是同步操作,对于大对象会阻塞,因此使用 asyncio.to_thread
return await asyncio.to_thread(lambda: json.dumps(obj).encode('utf-8'))
async def send_large_post_request(session: aiohttp.ClientSession, url: str, payload: dict):
"""
发送一个大型 POST 请求,数据预先序列化。
"""
# 1. 在单独的线程中序列化数据
serialized_data = await prepare_data_in_thread(payload)
# 2. 使用 data 参数发送预序列化后的字节数据
# 并手动设置 Content-Type 头
headers = {"Content-Type": "application/json"}
async with session.post(url, data=serialized_data, headers=headers) as response:
response.raise_for_status() # 检查 HTTP 状态码
return await response.text()
async def main():
target_url = "http://example.com/api/upload" # 替换为你的 API 地址
# 构造一个大型的模拟数据负载
large_payload = {"key": "value" * 1000000, "data": [i for i in range(100000)]}
async with aiohttp.ClientSession() as session:
tasks = []
for i in range(50): # 发送 50 个大请求
print(f"准备发送请求 {i+1}...")
tasks.append(send_large_post_request(session, target_url, large_payload))
print("所有请求已准备,等待完成...")
responses = await asyncio.gather(*tasks)
print("所有请求完成。")
# print(responses) # 根据需要处理响应
if __name__ == "__main__":
asyncio.run(main())DNS 解析是网络请求生命周期中的一个关键步骤。如果 DNS 解析速度慢,即使后端服务器响应迅速,整体延迟也会增加。
aiohttp 提供了可选的“加速包”,其中包含了 aiodns 库,它是一个异步 DNS 解析器。安装 aiohttp[speedups] 可以显著提升 DNS 解析性能。
pip install aiohttp[speedups]
安装后,aiohttp 会自动检测并使用 aiodns 进行 DNS 解析,无需额外的代码修改。
如果你的应用场景允许,并且你知道目标服务的 IP 地址,可以直接在 URL 中使用 IP 地址而不是域名。这将完全跳过 DNS 解析步骤,从而消除 DNS 带来的延迟。
# 替换为实际的 IP 地址和端口 target_url_with_ip = "http://192.168.1.100:8080/api/upload"
然而,这种方法通常不适用于生产环境,因为 IP 地址可能会变化,并且不利于负载均衡和服务发现。
这是一个非常重要的性能优化点,无论是否涉及大请求或 DNS 优化。每次创建 aiohttp.ClientSession 都会建立新的连接池和 DNS 缓存。重复创建会丢弃之前的 DNS 缓存和 TCP 连接,导致性能下降。始终复用同一个 ClientSession 实例,尤其是在发送多个请求时。
# 错误示例:每次请求都创建新的 session
# async def bad_example():
# for _ in range(50):
# async with aiohttp.ClientSession() as session:
# await session.post(...)
# 正确示例:复用 session
async def good_example():
async with aiohttp.ClientSession() as session: # session 只创建一次
tasks = []
for _ in range(50):
tasks.append(send_large_post_request(session, "...", {}))
await asyncio.gather(*tasks)优化 aiohttp 处理大量大型请求的性能,关键在于识别并解决阻塞事件循环的操作。通过以下策略,可以显著提升应用的响应性和吞吐量:
通过实施这些优化措施,您的 aiohttp 应用程序将能够更高效、更快速地处理高并发的大型 HTTP 请求。
以上就是优化 aiohttp 大请求性能:解决 JSON 序列化与 DNS 解析瓶颈的详细内容,更多请关注php中文网其它相关文章!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号