必须用aiofiles.open()配await f.write(chunk)实时落盘,否则chunk在内存堆积致oom;iter_chunked()仅分块不防溢出,超时重试续传等需自行实现。

因为没做流式落盘,async for chunk in resp.content.iter_chunked(n) 只是“分块吐数据”,不是“自动写磁盘”——chunk 拿到后若不立刻 await f.write(chunk),就会在内存里堆积,直到 OOM。
async for resp.content.iter_chunked() 本身不防溢出
这个方法只控制每次 yield 多大字节块,不控制你后续怎么处理。常见错误写法:
- 把所有
chunk收进一个list再拼接:all_data = b''.join(chunks) - 在循环里做耗时解析(比如整个 JSON 解码),导致 chunk 积压
- 用同步
open()+f.write(),阻塞事件循环,让后续 chunk 堆在缓冲区
哪怕设 chunk_size=8192,只要没立刻落盘,内存照样线性涨。
真正防溢出必须配 aiofiles.open() + await write
核心是保持异步链路完整,不让 chunk 在内存中滞留:
- 必须用
aiofiles.open('out.bin', 'wb'),不能用内置open() -
await f.write(chunk)要在每次async for迭代内完成,不能延迟 - 别加额外缓存层(比如先写进
BytesIO再 dump)
示例关键片段:
async with session.get(url) as resp:
resp.raise_for_status()
async with aiofiles.open('out.bin', 'wb') as f:
async for chunk in resp.content.iter_chunked(8192):
await f.write(chunk)
iter_chunked() 和 iter_any() 别混用,也别乱调参
两者都只是读取策略,不解决落盘问题,但选错会增加调试成本:
-
iter_chunked(n):按固定大小切块,最后 chunk 可能更小;语义清晰,推荐默认用 -
iter_any():底层 TCP buffer 有多少吐多少,无边界;性能略高但难 debug,别用来手动解析 chunked 编码 - 别把
chunk_size设成1024*1024*10之类的大值——单次分配太大可能触发 GC 延迟或失败
超时、重试、断点续传得自己补,aiohttp 不管
aiohttp 只负责把 socket 字节搬出来,业务逻辑全靠你填:
- 下载中断后想续传?得自己读
Content-Range、维护已写 offset、发Range请求 - 网络不稳定要重试?得包一层带指数退避的
retrying或自定义循环 - 没设
timeout参数?连接卡住时整个协程挂起,chunk 堆积风险更高
最易被忽略的一点:你以为 chunk 写完就释放了,其实 Python 对象引用、GC 触发时机、甚至文件系统缓存都可能让 RSS 持续居高不下——别只盯着 async for 循环本身,用 tracemalloc 定位真实 bytes 持有者。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











