僵尸进程是已终止但未被父进程回收的子进程,仅占用pid资源、状态为z或defunct;需通过向父进程发送sigchld或sigterm触发回收,或增强其sigchld信号处理机制来防控与清理。

Hyperf 定时任务在使用 Swoole 多进程模型时,若子进程(尤其是 task 进程)异常退出而父进程未及时回收,容易产生僵尸进程(状态为 Z)。这类僵尸本身不占资源,但积累过多会耗尽 PID 表,导致新 task 无法 fork,定时任务卡死或整个服务启动失败。
定位当前僵尸及其父进程
先确认是否存在僵尸,再锁定 Hyerf 相关的父进程:
- 查所有僵尸:
ps -eo pid,ppid,state,comm | awk '$3 ~ /Z/' - 过滤 Hyerf 相关项:
ps aux | grep hyperf | grep -E '(Z|defunct)' - 对任一僵尸 PID(如
12345),查其父进程:ps -o ppid= -p 12345—— 通常返回的是主 Worker 进程或 Manager 进程 PID - 再查该 PPID 对应的命令:
ps -p [PPID] -o pid,comm,args,确认是否为php bin/hyperf.php start
临时清理与快速恢复
不重启服务的前提下释放僵尸,优先温和操作:
- 向父进程发送
SIGTERM:kill -TERM [PPID],多数情况下主进程会优雅退出,init 自动收编子僵尸 - 若父进程是 systemd 管理的服务,用:
sudo systemctl restart hyperf-app,更安全且自动清理残留 - 慎用
kill -9 [PPID],仅当父进程无响应且非核心业务时使用;此时 init 会立即接管并清理全部子进程 - 避免直接对僵尸进程执行
kill -9—— 它已终止,无效
代码层预防:Task 进程回收加固
Hyperf 默认启用 task 进程,若业务中频繁 fork 子进程(如调用 shell、生成 PDF、处理大文件),需主动回收:
- 在
Task类的handle()方法末尾,显式调用pcntl_waitpid(-1, $status, WNOHANG)(需开启pcntl扩展) - 注册
SIGCHLD信号处理器,在全局初始化中添加:pcntl_signal(SIGCHLD, function($sig) { while (pcntl_waitpid(-1, $status, WNOHANG) > 0); });
并在事件循环中定期调用pcntl_signal_dispatch() - 若 task 逻辑简单、无需长期驻留,可临时禁用 task 进程启动:
php bin/hyperf.php start --task-worker-num=0,排除 task 卡死干扰
运维侧长期防控
靠手动清理不可持续,建议落地自动化机制:
- 加启动前自检脚本
bin/start-safe:运行前执行lsof -i :9501 &>/dev/null || true+ps -eo pid,ppid,state | awk '$3~/Z/{print $2}' | xargs -r kill -TERM 2>/dev/null - 部署定时任务每 5 分钟清理一次孤儿僵尸:
*/5 * * * * /usr/bin/bash /path/to/cleanup_zombies.sh >> /var/log/zombie-cleanup.log 2>&1 - 监控项接入 Prometheus + Grafana,采集
node_procs_zombie指标,阈值设为 >5 即告警 - Docker 部署时,在
entrypoint.sh中加入trap 'kill $(jobs -p) 2>/dev/null' EXIT,确保容器退出时清理子进程











