hyperf定时任务处理大文件易致内存缓慢上涨并oom,核心在于协程模型下资源未释放:需游标式读取、禁用pdo缓冲、显式释放变量与句柄、避免闭包强引用、精简日志、主动触发gc及拆分任务。

Hyperf 定时任务在处理大文件(如 CSV 解析、日志归档、Excel 导出等)时,若未控制内存生命周期,极易引发缓慢但持续的内存上涨,最终触发 OOMKilled。这不是单次执行崩溃,而是协程常驻进程内对象累积、资源未释放导致的“慢性泄漏”。关键不在文件大小本身,而在处理方式是否适配协程模型。
关闭缓冲 + 游标式逐行读取
大文件读取最常见误区是:用 file_get_contents 一次性加载全文,或用 fgetcsv 配合全量数组缓存。Hyperf 中更隐蔽的是 ORM 或 DB 查询返回结果集后调用 toArray() 或 json_encode() —— 即使只查 1 行,若底层 SQL 关联了百万级明细,PDO 默认缓冲会把整张结果集载入内存。
- 数据库操作务必禁用 PDO 缓冲:
'options' => [PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false] - 改用
Db::query()->cursor()(Hyperf 3.x+),或底层fetch()循环,确保每次只持有单行数据 - 避免
get()、all()、paginate()等批量获取方法;若必须分页,用游标分页(cursorPaginate)而非 offset 分页
显式释放中间变量与资源句柄
定时任务运行在 Worker 进程内,生命周期与进程一致。PHP GC 不会自动清理未 unset 的大数组、临时对象或打开的文件句柄,尤其在 while 循环中反复追加数据时。
- 每轮处理完一批数据后,主动
unset($batchData)、$batchData = [] - 使用
fopen()读取文件时,循环结束后必须fclose($fp);推荐用yield from+Generator实现流式解析,天然规避内存堆积 - 对大对象(如 Excel Reader 实例、ZipArchive)使用后立即
unset,不要依赖作用域自动销毁
避免闭包捕获与全局容器强引用
定时任务方法内若定义匿名函数并 use 了 $container、$db 或大型数据结构,该闭包会延长这些对象的生命周期,导致整个 Worker 进程无法回收它们。
- 禁止在
@Cron方法中写$fn = function() use ($container) { ... }; - 需要容器服务时,延迟到
handle()或循环体内按需获取:$logger = $this->container->get(LoggerInterface::class) - 日志记录禁止传入
$this、$container或原始数据对象,改用精简数组:['file_size' => $size, 'lines' => $count]
强制触发 GC 与限制单次任务耗时
PHP 默认 GC 触发阈值较高,而定时任务中高频创建小对象(如字符串拼接、临时数组)可能长期不触发回收。同时,单次执行过久会阻塞协程调度,加剧内存压力。
- 在 while 循环每处理 N 行(如 500 行)后调用
gc_collect_cycles() - 为任务设置超时保护:用
Coroutine::sleep(0.001)插入让渡点,避免 CPU 密集型卡死;或用timeout包裹关键段 - 将大任务拆分为多个子任务,通过异步队列分发,由独立消费者执行,天然隔离内存上下文











