
在使用aiohttp进行大量并发http请求时,尤其是当每个请求的载荷(payload)较大(例如5mb)时,开发者可能会遇到性能瓶颈。常见的挑战包括:
这些问题会严重影响应用程序的并发性能和响应速度。
为了解决JSON序列化阻塞事件循环的问题,核心思想是将CPU密集型的序列化操作从事件循环中剥离出来,使其不会阻塞异步I/O。
问题根源:aiohttp内部使用json.dumps()将json参数传递的对象转换为字符串,然后编码为字节。当数据量庞大时,这个过程会耗费大量CPU时间,从而阻塞异步事件循环。
解决方案:预编码JSON并利用线程池
最佳实践是手动在请求发送前完成JSON的序列化和编码,并利用asyncio.to_thread将此阻塞操作移至单独的线程中执行。
手动序列化和编码: 不要使用aiohttp的json参数。而是先使用json.dumps()将Python对象转换为JSON字符串,再使用.encode()将其转换为字节流。
import json
def prepare_data(obj: dict) -> bytes:
"""
将Python对象序列化为JSON字符串并编码为字节。
此操作可能耗时,适合在单独的线程中执行。
"""
return json.dumps(obj).encode('utf-8')利用asyncio.to_thread将操作移出事件循环:asyncio.to_thread是Python 3.9+引入的一个非常有用的工具,它允许我们将同步的、CPU密集型或阻塞型函数在单独的线程中运行,而不会阻塞主事件循环。
import asyncio
import aiohttp
async def send_large_post_request(session: aiohttp.ClientSession, url: str, data_obj: dict):
"""
异步发送大型POST请求,通过将JSON序列化移至单独线程来优化性能。
"""
# 将JSON序列化操作移至单独的线程,避免阻塞事件循环
# 注意:data_obj在传递给prepare_data后不应被修改,
# 最好使用不可变对象或确保在序列化期间不发生并发修改。
data_bytes = await asyncio.to_thread(prepare_data, data_obj)
# 使用data参数发送预编码的字节流,并显式设置Content-Type头
headers = {"Content-Type": "application/json"}
async with session.post(url, data=data_bytes, headers=headers) as resp:
resp.raise_for_status() # 检查HTTP响应状态
response_data = await resp.read()
print(f"请求 {url} 完成,状态码:{resp.status}")
return response_data
async def main():
async with aiohttp.ClientSession() as session:
# 示例:创建50个大型请求的数据
large_payload = {"key": "value" * 1000000, "data": [i for i in range(100000)]} # 约5MB的数据
requests_to_send = 50
tasks = [
send_large_post_request(session, "https://www.php.cn/link/fd605345abc18248f2fb95c3f4e32707", large_payload)
for _ in range(requests_to_send)
]
await asyncio.gather(*tasks)
print(f"所有 {requests_to_send} 个请求已发送并处理完成。")
if __name__ == "__main__":
asyncio.run(main())注意事项:
DNS解析是网络请求的另一个潜在瓶颈,尤其是在高并发或对延迟敏感的场景中。
问题根源:aiohttp默认的DNS解析器可能不是最高效的,或者在某些环境下可能引入延迟。在aiohttp的连接器(connector)层面,DNS解析是建立连接前的阻塞点。
解决方案:
安装aiohttp[speedups]:aiohttp提供了一个名为speedups的额外依赖包,它会安装aiodns。aiodns是一个基于c-ares的异步DNS解析库,通常比Python标准库或操作系统默认的解析器更快。
pip install aiohttp[speedups]
安装后,aiohttp会自动检测并使用aiodns进行DNS解析,无需额外配置。
直接使用IP地址: 如果你的应用程序允许,并且你能够预先获取目标服务的IP地址,那么直接在请求URL中使用IP地址而非域名可以完全跳过DNS解析步骤,从而消除这部分延迟。
# 示例:直接使用IP地址
async with session.post("http://192.168.1.100/upload", data=data_bytes, headers=headers) as resp:
# ...这种方法虽然能提供极致的DNS解析速度,但会牺牲灵活性(如服务IP变更时的维护成本)和负载均衡能力(如果后端有多个IP)。
复用ClientSession: 这是最基本也是最重要的性能优化实践。每次创建aiohttp.ClientSession实例都会涉及连接池的初始化、DNS缓存的清空等操作。复用同一个ClientSession实例,可以:
# 错误示例:每次请求都创建新的session # for _ in range(50): # async with aiohttp.ClientSession() as session: # await session.post(...)
async def main(): async with aiohttp.ClientSession() as session: # session只创建一次 tasks = [ send_large_post_request(session, "https://www.php.cn/link/fd605345abc18248f2fb95c3f4e32707", largepayload) for in range(requests_to_send) ] await asyncio.gather(*tasks)
在处理aiohttp大量并发大型请求时,以下是关键的性能优化策略:
通过综合运用上述策略,可以显著提升aiohttp处理高并发、大载荷请求场景下的性能和响应能力。
以上就是优化aiohttp处理大量并发请求的性能:避免阻塞与加速DNS解析的详细内容,更多请关注php中文网其它相关文章!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号