Django中aiohttp异步HTTP请求的内存泄漏问题排查与优化
Django + aiohttp 异步请求内存泄漏问题解决方案
1. 内存泄漏的可能原因
- ClientSession 未正确复用/关闭:每次请求新建
ClientSession且未关闭,会残留连接池、TCP连接等资源,长期累积占用内存。 - 孤儿异步任务:用
asyncio.create_task创建的任务未被await,或任务抛出异常未捕获,导致任务对象及其引用的资源无法被GC回收。 - Django 上下文残留:异步视图中引用的
request对象、ORM实例等上下文未被正确销毁,若异步任务持有这些对象的引用,会导致内存无法释放。 - 连接池配置不合理:
TCPConnector的连接数限制过高,或未开启关闭连接的自动清理,导致大量空闲keep-alive连接长期占用内存。 - 依赖版本bug:部分Python 3.9/3.10/3.12与对应aiohttp版本的组合存在已知内存泄漏问题,或第三方解析库(如json、xml处理库)本身存在泄漏。
- 全局数据堆积:用全局列表、字典存储请求结果或中间数据,未及时清理,导致数据量持续增长。
2. aiohttp 内存管理优化措施
- 复用全局ClientSession:在Django应用启动时初始化一个全局
ClientSession,所有异步请求复用该实例,应用关闭时调用session.close()销毁。 - 优化连接池配置:使用
TCPConnector限制连接数,开启自动清理:connector = aiohttp.TCPConnector( limit=100, # 全局最大连接数 limit_per_host=20, # 单主机最大连接数 enable_cleanup_closed=True # 自动清理已关闭的连接 ) session = aiohttp.ClientSession(connector=connector) - 显式管理响应对象:始终用
async with语法处理请求,确保响应体被正确关闭:async with session.get(url) as resp: data = await resp.json() - 清理异步任务:所有异步任务必须被
await,批量任务用asyncio.gather并捕获异常,避免任务成为孤儿:tasks = [fetch_data(session, url) for url in urls] results = await asyncio.gather(*tasks, return_exceptions=True) # 处理异常结果,避免残留未处理的异常对象 - 禁用不必要功能:若不需要keep-alive,设置
connector=aiohttp.TCPConnector(force_close=True);关闭自动重试等非必要功能。
3. 代码优化避免连续请求内存增长
- 全局初始化核心资源:在Django的
AppConfig中初始化全局的ClientSession和Semaphore,不要在每个视图或请求中重复创建:# apps.py from django.apps import AppConfig import aiohttp import asyncio class MyAppConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'myapp' session = None semaphore = None def ready(self): self.semaphore = asyncio.Semaphore(20) self.session = aiohttp.ClientSession( connector=aiohttp.TCPConnector( limit=100, limit_per_host=20, enable_cleanup_closed=True ) ) - 隔离Django上下文:异步任务中避免直接引用
request对象或ORM实例,若需操作数据库,用sync_to_async封装并确保上下文被销毁:from django.utils.decorators import sync_to_async @sync_to_async def get_db_data(): return list(MyModel.objects.all()) - 控制并发数:用全局
Semaphore限制并发请求数,避免同时创建过多任务导致内存暴涨:async def fetch_data(session, url, semaphore): async with semaphore: async with session.get(url) as resp: return await resp.json() - 及时清理临时数据:处理完批量请求后,清空存储结果的临时列表、字典;用生成器代替列表存储中间数据,减少内存占用。
- 精准触发GC:在批量请求完全处理完成、所有资源释放后,再调用
gc.collect(),不要频繁触发GC(会影响性能)。 - 定位泄漏点:用
tracemalloc跟踪内存分配,找到持续增长的对象类型:import tracemalloc tracemalloc.start() # 执行批量请求 snapshot = tracemalloc.take_snapshot() top_stats = snapshot.statistics('lineno') for stat in top_stats[:10]: print(stat)
内容的提问来源于stack exchange,提问作者Andrey Tarasov
相关产品推荐
相关产品推荐

