hyperf多进程定时任务内存溢出本质是常驻worker中变量累积未释放,需显式清理、分层隔离、主动gc及监控定位。

Hyperf 多进程定时任务内存溢出,本质不是“多进程”本身的问题,而是定时任务长期运行在常驻 Worker 进程中,变量、对象、缓存不断累积却未及时释放。关键不在加内存,而在切断泄漏路径。
定时任务生命周期必须可控
Hyperf 的 @Cron 任务默认跑在常驻 Worker 进程里,每次执行都复用同一进程上下文。如果任务中做了以下操作,内存会持续增长:
- 全局数组(如
$GLOBALS['cache'][] = $item)反复追加不清理 - 闭包
use ($container)或use ($db)捕获整个容器或连接实例,导致整棵树无法回收 - 静态属性缓存数据后未设 TTL 或未主动清除
- 循环内创建大量临时对象(如 JSON 解析后未 unset)且未触发 GC
每次执行后强制清理现场
不能依赖 PHP 自动 GC,尤其在长周期协程中。应在任务逻辑末尾显式释放资源:
- 用
unset()清除中间大数组、结果集、临时对象引用 - 调用
gc_collect_cycles()主动触发垃圾回收(尤其处理完千条以上数据后) - 避免在
@Cron方法内定义匿名函数并捕获大对象;改用参数传入必要数据 - 数据库查询后及时释放结果集:
$statement->closeCursor()(PDO 场景)
拆分重负载任务 + 引入隔离机制
单个定时任务若需处理数万条数据或执行复杂计算,极易拖垮 Worker 内存。推荐分层处理:
- 主定时任务只负责“派发”,把待处理 ID 范围推入 Redis 队列或消息队列
- 由独立的 Consumer Worker(通过 Supervisor 管理)拉取并执行,每个 Worker 执行完自动退出或重启
- 对 CPU 密集型子任务,用
Coroutine::sleep(0.001)插入调度点,防止协程独占线程 - 设置 Worker 最大任务数(如
max_requests = 100),到期自动 reload,切断内存累积链路
监控与快速定位泄漏点
光靠日志很难发现缓慢泄漏。上线前必须配置基础观测能力:
- 启用
memory_get_usage(true)和memory_get_peak_usage(true)在任务前后打点,记录内存波动 - 用
debug_backtrace()或xdebug_get_function_stack()抓取高内存占用时的调用栈(仅限调试环境) - 定期 dump 内存快照(
memprof扩展或php-meminfo),对比两次快照找出新增未释放对象 - 在
onWorkerStart和onWorkerStop中记录内存基线,观察 Worker 生命周期内的增长趋势











