長期運行的後台服務(如消息隊列消費者、WebSocket 服務、定時任務)需以守護進程形式運行。PHP 本身無內置 daemon() 函數,但可通過系統工具或 PCNTL 擴展實現。
# 後台運行,輸出重定向到日誌 nohup php worker.php >> storage/logs/worker.log 2>&1 & # 查看進程 ps aux | grep worker.php # 停止進程 kill -TERM
| 優點 | 缺點 |
|---|---|
| 無需修改代碼,零侵入 | 無法在代碼中控制守護化邏輯 |
| 適合臨時/簡單腳本 | 標準輸入/輸出/錯誤需手動重定向 |
| 系統級進程管理 | 進程管理依賴外部工具(systemd/supervisor) |
<?php
/**
* 將當前進程轉為守護進程
* 需啟用 pcntl、posix 擴展
*/
function daemonize(): void {
// 第一次 fork:父進程退出,子進程成為新會話組長
$pid = pcntl_fork();
if ($pid === -1) {
die("fork #1 failed\n");
} elseif ($pid > 0) {
exit(0); // 父進程退出
}
// 脫離控制終端,創建新會話
if (posix_setsid() === -1) {
die("setsid failed\n");
}
// 第二次 fork:防止重新獲得控制終端
$pid = pcntl_fork();
if ($pid === -1) {
die("fork #2 failed\n");
} elseif ($pid > 0) {
exit(0); // 第一子進程退出,第二子進程成為最終守護進程
}
// 可選:切換工作目錄到根目錄,避免掛載點卸載問題
chdir('/');
// 可選:重設文件創建掩碼
umask(0);
// 可選:關閉標準流並重定向(生產環境建議)
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
$null = fopen('/dev/null', 'r');
fopen('/dev/null', 'w');
fopen('/dev/null', 'w');
}
// 業務邏輯入口
daemonize();
// 模擬長期任務
while (true) {
// 處理業務...
sleep(10);
}
用戶終端啟動
│
▼
┌─────────────┐
│ 父進程 │── fork() ──▶ 父進程 exit(0) 返回終端
└─────────────┘
│
▼ 子進程(會話組長)
┌─────────────┐
│ 子進程1 │── setsid() 脫離終端
└─────────────┘
│
▼
┌─────────────┐
│ 子進程1 │── fork() ──▶ 子進程1 exit(0)
└─────────────┘
│
▼ 子進程2(非會話組長,無法重獲終端)
┌─────────────┐
│ 守護進程 │◀── 最終運行實體
└─────────────┘
雖然 PHP 可自守護,但生產環境強烈建議使用 Supervisor 等進程管理器:
; /etc/supervisor/conf.d/worker.conf [program:php-worker] command=php /var/www/app/worker.php autostart=true autorestart=true user=www-data numprocs=4 process_name=%(program_name)s_%(process_num)02d stdout_logfile=/var/log/supervisor/worker.log redirect_stderr=true
| 場景 | 推薦方案 |
|---|---|
| 臨時測試、簡單腳本 | nohup + & |
| 需在代碼中控制守護化 | PHP 雙 Fork |
| 生產環境長期服務 | Supervisor + 普通 CLI 腳本 |
理解守護進程原理,選擇合適的部署方式,是 PHP 後台服務穩定運行的基礎。