Hyperf定时任务进程卡死通常源于协程阻塞或不可退出循环,而非任务执行过久;需区分PHP死循环与协程调度失常,通过CPU占用、内存增长、日志输出及协程ID等特征快速定位,并禁用阻塞调用、投递重逻辑至TaskWorker、加超时兜底、避免静态变量累积来解决。

Hyperf 定时任务进程卡死,通常不是任务本身“执行太久”,而是陷入不可退出的循环或协程调度失常,导致 crontab-dispatcher 或 worker 进程持续占用资源、内存暴涨、甚至被系统 OOM Kill。核心要区分:是 PHP 层死循环,还是 协程阻塞导致调度器离线。
确认是否真死循环:看进程状态和日志特征
先快速判断类型:
- 若 CPU 持续 100%(单核打满)、
ps aux显示进程 PID 长期存在但无日志输出、swoole_get_local_cid()在该进程内返回 -1 → 大概率是协程卡死或同步 I/O 阻塞,非传统死循环 - 若 内存缓慢但持续上涨(如每小时 +100MB)、worker PID 序号飙升(如到 78 万)、且卡在整点/固定间隔前后 → 高概率是定时任务逻辑中存在未释放的引用、静态变量累积或闭包捕获了大对象
- 若 进程直接退出,日志出现
check_worker_exit_status、Fatal error: Maximum function nesting level或段错误 → 很可能是构造器循环依赖或递归过深触发栈溢出
检查定时任务闭包中的阻塞调用
Hyperf 的 Swoole\Timer::tick 和 crontab 均运行在 Worker 协程中。一旦回调里写了阻塞操作,整个协程就卡住,后续所有 tick 都被拖垮:
- 禁用
sleep()、usleep()、file_get_contents()、无超时curl_exec() - 改用协程安全替代:
Co::sleep()、HttpClient::get()、Redis::get()(需开启async => true) - 重逻辑务必投递到 TaskWorker:
$this->container->get(TaskExecutor::class)->execute(new HeavyTask($data)) - 加超时兜底:
Timer::tick(60000, fn() => {...}, ['timeout' => 50000])
排查定时任务引发的内存泄漏
常见泄漏点不在 SQL,而在 PHP 变量生命周期管理:
- 避免在定时任务闭包中使用
static $cache = []或全局数组累积数据 - 检查是否误将 DB 查询结果、大文件内容、JSON 解析后的数组长期存于静态属性或闭包引用中
- 确认没有在
onWorkerStart外重复注册相同定时器(句柄泄漏会导致协程资源不释放) - 启用 GC 日志辅助定位:
gc_enable(); gc_collect_cycles();放在任务末尾临时观察回收效果
强制终止与快速恢复策略
当已确认进程异常且无法响应时,需安全干预:
- 查出 crontab-dispatcher 进程:
ps aux | grep 'crontab-dispatcher',记录 PID - 优雅终止:
kill -15 <pid>;若无响应,再kill -9 <pid> - 重启前清理共享内存:
rm -f /dev/shm/swoole-*(防止 task 参数序列化残留干扰) - 临时关闭问题任务:在
config/autoload/crontab.php中注释掉对应配置,或通过动态任务表设status = 0
不复杂但容易忽略。关键在早发现——给定时任务加轻量心跳日志(含 worker ID 和时间戳),配合 task_wait_queue_len 和内存 RSS 监控,就能把卡死拦在影响业务之前。


















