Python Asyncio内存泄漏:Docker化grpc服务内存持续增长被内核终止
Asyncio 异步 gRPC 服务内存泄漏排查与解决方案
第一步:快速定位泄漏类型
- 首先排查后台任务泄漏:你使用
asyncio.ensure_future创建的持续运行任务如果没有回收逻辑,是 Asyncio 场景下最高发的泄漏原因。先在代码中加入任务数监控逻辑,确认运行时任务数是否持续上涨:
import asyncio async def monitor_running_tasks(): while True: all_tasks = asyncio.all_tasks() # 排除监控任务自身 valid_task_cnt = len(all_tasks) - 1 print(f"当前运行中Asyncio任务数:{valid_task_cnt}") await asyncio.sleep(10) # 服务启动时和其他任务一同启动该监控
- 排查gRPC上下文残留:如果任务数没有上涨,大概率是请求上下文、业务对象没有被GC回收,可以用gc模块统计核心对象数量变化:
import gc from grpc import ServicerContext async def monitor_grpc_objects(): while True: context_cnt = sum(1 for obj in gc.get_objects() if isinstance(obj, ServicerContext)) print(f"当前残留gRPC上下文数量:{context_cnt}") await asyncio.sleep(60)
第二步:常见泄漏场景修复
1. ensure_future创建的任务无回收逻辑
你用return ensure_future(self._continuous_function)的方式返回后台任务时,如果没有绑定完成回调,任务运行中抛出的异常栈、临时变量会被Event Loop隐式持有,永远不会被回收,修复方式如下:
def clean_task_callback(task): try: # 主动拉取任务结果/异常,避免栈内存残留 task.result() except Exception as e: # 此处可加入你自己的异常日志逻辑 pass # 创建任务时绑定清理回调 bg_task = asyncio.ensure_future(self._continuous_function) bg_task.add_done_callback(clean_task_callback) return bg_task
2. 异步迭代器/生成器未主动关闭
如果_continuous_function中用到了异步生成器、异步迭代器,中途退出时没有调用aclose()主动关闭,生成器持有的所有变量都会被常驻内存,需要在任务退出逻辑中补全关闭逻辑。
3. 无限制的全局/类属性数据堆积
如果业务逻辑中存在将请求数据、临时变量追加到全局列表、字典或者类属性的逻辑,没有过期淘汰机制,也会导致内存线性上涨,可以用Python内置的tracemalloc模块精准定位内存分配点:
import tracemalloc # 服务启动时开启内存跟踪 tracemalloc.start(30) # 运行10分钟后打印内存占用Top10代码行 snapshot = tracemalloc.take_snapshot() top_memory = snapshot.statistics('lineno') print("===== 内存占用Top10 =====") for stat in top_memory[:10]: print(stat)
第三步:Docker环境验证
修复完成后本地启动容器,通过docker stats监控24小时内存走势,确认内存不再持续上涨后再上线。同时可以在docker run时添加--memory=2G参数限制容器最大内存,避免异常时影响宿主节点。
内容的提问来源于stack exchange,提问作者wiseboar
相关产品推荐
相关产品推荐

