Supervisor不能直接管理CodeIgniter Web进程,因其基于PHP-FPM/CGI短生命周期模式,而Supervisor仅守护长期运行的CLI进程(如队列消费者脚本);错误做法是尝试用它托管Apache/Nginx+PHP-FPM,应交由systemctl处理。

CodeIgniter 本身不支持长运行队列进程,Supervisor 不是用来“集成进 CI”的工具,而是用来守护你手动写的 CLI 脚本——比如轮询 jobs 表的 cron::process_queue 或自定义的 spark queue:work(CI4)包装脚本。
为什么不能直接用 supervisor 管理 CodeIgniter Web 进程
CI 是传统 PHP-FPM/CGI 模式,每次 HTTP 请求启动新进程、执行完即销毁;supervisor 守护的是长期存活的独立进程(如 Python 服务、Node.js 后台、或你自己写的 PHP CLI 循环)。试图让 supervisor 去“守护 Apache/Nginx + PHP-FPM”是方向错误——那是系统服务管理范畴(systemctl 干的事),不是 supervisor 的使用场景。
常见误操作包括:
- 把
php index.php home index丢给 supervisor —— 这个命令只响应一次请求就退出,supervisor 会不断重启它,日志刷屏 - 没加
autostart=true和autorestart=true,导致机器重启后任务脚本完全不运行 - 用 root 用户运行但脚本里硬编码了 /home/www 的路径,而 supervisor 环境下 $HOME 不等于 web 用户家目录
CI3 中用 Supervisor 守护「伪队列」消费者
前提是你已按 CI3 风格建好 jobs 表,并写好一个 CLI 控制器方法(例如 application/controllers/Cron.php 中的 process_queue()),它每次只取并处理一条 pending 任务。
这时你需要一个**死循环包装脚本**(别直接丢控制器方法进去):
#!/usr/bin/env php
<?php
// scripts/queue_worker.php
define('FCPATH', __DIR__.'/../');
require_once FCPATH.'index.php';
<p>while (true) {
// 调用 CI3 的 CLI 方式执行单次任务
exec('php '.FCPATH.'index.php cron process_queue', $output, $return_code);</p><pre class="brush:php;toolbar:false;">// 避免高频空转,也防止数据库锁堆积
sleep(2);}
对应 Supervisor 配置(/etc/supervisor/conf.d/ci3-queue.conf):
[program:ci3-queue-worker] command=/usr/bin/php /var/www/myapp/scripts/queue_worker.php directory=/var/www/myapp user=www-data autostart=true autorestart=true startretries=3 redirect_stderr=true stdout_logfile=/var/log/ci3-queue-worker.log stopwaitsecs=10 environment=PATH="/usr/bin:/bin"
关键点:
-
stopwaitsecs=10:给脚本留出主动释放数据库连接、清理临时文件的时间,否则 supervisor 强杀会导致locked_at滞留 - 必须用绝对路径调用
php(/usr/bin/php),不能只写php——supervisor 的 PATH 往往很窄 - 不要在脚本里用
exec('nohup ... &'),supervisor 自己负责后台化,嵌套会导致失控
CI4 中用 Supervisor 守护 spark 命令
CI4 自带 spark CLI 工具,可封装成类 Laravel 风格的循环命令。先写一个自定义命令:
php spark make:command QueueWorker
编辑 app/Commands/QueueWorker.php,核心逻辑类似:
public function handle()
{
while (true) {
$job = $this->jobModel->where('status', 'pending')->first();
if (! $job) {
sleep(3);
continue;
}
<pre class="brush:php;toolbar:false;"> $this->jobModel->update($job->id, ['status' => 'running']);
$result = $this->doJob($job);
$this->jobModel->update($job->id, [
'status' => $result ? 'done' : 'failed',
'finished_at' => date('Y-m-d H:i:s')
]);
}}
然后 Supervisor 配置直接调用 spark:
[program:ci4-queue-worker] command=/usr/bin/php /var/www/myapp/spark queue:worker directory=/var/www/myapp user=www-data autostart=true autorestart=true startretries=3 stdout_logfile=/var/log/ci4-queue-worker.log stderr_logfile=/var/log/ci4-queue-worker.err.log stopwaitsecs=15
注意:
- CI4 的
spark命令依赖APP_NAMESPACE和环境变量,确保environment里设好CI_ENVIRONMENT=production -
stopwaitsecs要比 CI4 默认的max_execution_time(通常 30s)更长,否则 stop 时命令还没执行完就被 SIGKILL - 别在
handle()里用exit()或die(),这会让 supervisor 误判为异常退出
Supervisor 日志和信号处理容易被忽略的细节
Supervisor 不转发 Unix 信号到子进程,默认情况下 Ctrl+C、SIGTERM 都不会传给你的 PHP 脚本。这意味着:
- 你在
queue_worker.php里写的pcntl_signal(SIGTERM, ...)默认收不到信号 -
supervisorctl stop xxx实际发的是 SIGTERM,但 PHP 脚本若无信号注册,就会等stopwaitsecs超时后被 SIGKILL 强杀 - 日志文件权限不对(比如 www-data 写不了
/var/log/xxx.log)会导致 supervisor 静默失败,supervisorctl status显示 STARTING 却永远不变成 RUNNING
解决办法是显式启用信号转发(supervisord >= 3.0):
[supervisord] nodaemon=false user=root childlogdir=/var/log/supervisor # 加这一行 [unix_http_server] file=/var/run/supervisor.sock <p>[rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface</p><p>[supervisorctl] serverurl=unix:///var/run/supervisor.sock</p><h1>关键:允许子进程接收信号</h1><p>[program:ci4-queue-worker] ... killasgroup=true stopsignal=TERM
再配合 PHP 里的 pcntl_signal() 注册清理逻辑,才能真正实现优雅退出。


















