PHP后台任务必须由Supervisor等外部进程管理器托管,不能依赖pcntl_fork()在Web环境运行;CLI守护脚本需循环处理、超时保护、异常捕获、定期GC及独立数据库连接。

PHP后台任务不能靠“自动运行”,必须由外部进程管理器长期托管,否则一断电、一重启、一报错就停摆。
为什么 pcntl_fork() 在 Web 请求里根本跑不起来
很多开发者看到 pcntl_fork() 就想在 Apache 或 PHP-FPM 里直接 fork 子进程处理耗时任务——这基本会失败。原因很实在:Web SAPI 的生命周期极短,请求结束时整个进程会被回收,子进程可能被强制 kill,或者变成孤儿进程后被 init 收养,但无法保证执行完成。
-
pcntl扩展默认在 PHP-FPM/Apache 中被禁用,即使开启也容易引发内存污染或信号混乱 - 子进程若没调用
posix_setsid()脱离会话,会继承父进程的终端控制,导致意外挂起 - 没有
pcntl_waitpid()或信号处理器,子进程退出后变成僵尸进程,持续占用系统 PID 表项
真正能用 pcntl_fork() 的地方,只有 CLI 模式下写的守护脚本,且必须自己处理进程生命周期。
Supervisor 是目前最省心的 PHP 后台进程托管方案
它不依赖 PHP 自身稳定性,而是把你的 PHP CLI 脚本当普通进程看待,崩溃就拉起,退出就重试,日志全记录——这才是生产环境该有的容错逻辑。
立即学习“PHP免费学习笔记(深入)”;
- 脚本必须是循环结构,比如
while (true) { handleJob(); sleep(1); },不能执行完就 exit - 配置里关键三项:
autostart=true(开机自启)、autorestart=true(异常重启)、startretries=3(启动失败最多试 3 次) - 务必设置
redirect_stderr=true和stdout_logfile=/var/log/myworker.log,否则出问题时连日志都找不到
别用 nohup php worker.php & 这类野路子——没健康检查、没资源限制、没统一日志路径,线上出事只能靠猜。
队列消费者脚本怎么写才不容易崩
Redis list 做队列看着简单,但实际跑起来常因一个细节挂掉:没做超时保护或错误隔离,导致单个失败任务卡死整个消费者。
- 每次
rPop()建议加超时,如$redis->brPop(['job_queue'], 5),避免空轮询吃满 CPU - 每个任务必须包裹在
try/catch里,防止未捕获异常终止主循环 - 用
set_time_limit(30)限制单个任务最大执行时间,防第三方接口 hang 住 - 每处理 N 个任务后手动
gc_collect_cycles(),缓解长期运行下的内存缓慢增长
更关键的是:不要在消费者里做数据库连接复用。每次任务都新建连接、用完即关,比共用一个连接然后默默失效要可靠得多。
supervisorphp/supervisor 库不是万能的,别在关键路径上依赖它
这个库通过 XML-RPC 调用 Supervisor 的 API,听起来很酷,但实际部署中容易踩两个硬坑:
- Supervisor 默认只监听
localhost:9001,远程 PHP 调用需改配置开 bind,但暴露端口有安全风险 - 如果 Supervisor 进程本身卡死或配置加载失败,
supervisorphp/supervisor的调用会直接超时,而你没法从 PHP 侧感知到底层发生了什么 - 它适合做“运维看板”里的启停按钮,不适合做任务调度核心逻辑(比如根据队列长度动态扩缩 consumer 数量)
真正需要编程控制的场景,建议用 Supervisor 管理固定数量的 worker,再用 Redis 的 llen() + 定时脚本做粗粒度扩缩,比强依赖 XML-RPC 更稳。



















