將 PHP 腳本作為長期運行的守護進程,配合多進程模型處理持續性任務,是許多後端服務的基石。本文解析一個可直接投產的守護進程框架,涵蓋進程管理、任務調度、優雅退出全生命週期。
<?php
/**
* 入口文件:ProcessOpera.php
* 啟動:/usr/local/php/bin/php ProcessOpera.php &
* 查看:ps aux | grep php
*/
ProcessOpera("runCode", array(), 8);
/**
* 業務邏輯入口
*/
function runCode($opt = array()) {
// 實際業務代碼:數據同步、隊列消費、定時任務等
// 建議:單次執行時間 < 1s,避免阻塞進程池
}
/**
* 進程調度器
* @param string $func 子進程執行函數名
* @param array $opts 函數參數
* @param int $pNum 並發進程數
*/
function ProcessOpera($func, $opts = array(), $pNum = 1) {
$execute = 0; // 當前運行子進程數
while (true) {
$pid = pcntl_fork();
if ($pid == -1) {
exit("pid fork error");
}
if ($pid) {
// === 父進程邏輯 ===
$execute++;
// 達到併發上限時,阻塞等待任意子進程結束
if ($execute >= $pNum) {
pcntl_wait($status); // 阻塞模式回收
$execute--;
}
} else {
// === 子進程邏輯 ===
// 忽略父進程的信號處理器,建立獨立處理
pcntl_signal(SIGTERM, SIG_DFL);
pcntl_signal(SIGINT, SIG_DFL);
while (true) {
// 執行業務邏輯
$func($opts);
// 關鍵:睡眠釋放 CPU,防止空轉佔滿資源
sleep(1);
}
exit(0);
}
}
}
$execute 計數器精確控制存活子進程數pcntl_wait() 阻塞等待實現「生產者-消費者」節流
pcntl_signal(SIGTERM, SIG_DFL);
pcntl_signal(SIGINT, SIG_DFL);
重置為默認處理,確保 kill PID 能直接終止子進程,不受父進程邏輯干擾。
強制 sleep(1) 防止忙等待,生產環境建議根據任務頻率調整(如 100ms-5s)。
<?php
declare(ticks=1);
class DaemonMaster {
private $workers = [];
private $maxWorkers = 8;
private $shutdown = false;
private $taskFunc;
private $taskArgs;
public function __construct($func, $args, $maxWorkers) {
$this->taskFunc = $func;
$this->taskArgs = $args;
$this->maxWorkers = $maxWorkers;
// 註冊信號處理
pcntl_signal(SIGTERM, [$this, 'signalHandler']);
pcntl_signal(SIGINT, [$this, 'signalHandler']);
pcntl_signal(SIGCHLD, [$this, 'reapChildren']);
}
public function run() {
while (!$this->shutdown) {
// 維持進程數
while (count($this->workers) < $this->maxWorkers && !$this->shutdown) {
$this->forkWorker();
}
// 非阻塞檢查子進程狀態
pcntl_wait($status, WNOHANG);
sleep(1);
}
// 優雅關機:發送 SIGTERM 給所有子進程
foreach ($this->workers as $pid) {
posix_kill($pid, SIGTERM);
}
// 等待所有子進程退出
while (!empty($this->workers)) {
pcntl_wait($status);
}
echo "Daemon stopped gracefully\n";
}
private function forkWorker() {
$pid = pcntl_fork();
if ($pid == -1) return;
if ($pid) {
$this->workers[$pid] = true;
} else {
// 子進程初始化
pcntl_signal(SIGTERM, function() { exit(0); });
$this->workerLoop();
}
}
private function workerLoop() {
while (true) {
call_user_func($this->taskFunc, $this->taskArgs);
usleep(500000); // 500ms
}
}
public function signalHandler($signo) {
$this->shutdown = true;
}
public function reapChildren($signo) {
while (($pid = pcntl_wait($status, WNOHANG)) > 0) {
unset($this->workers[$pid]);
// 可記錄退出碼、重啟策略等
}
}
}
// 使用示例
$daemon = new DaemonMaster('runCode', [], 8);
$daemon->run();
| 能力 | 實現方式 |
|---|---|
| 平滑重啟 | 發送 SIGUSR1 觸發配置重載,子進程完成當前任務後退出,新進程加載新配置 |
| 健康檢查 | 子進程定期寫入心跳文件/Redis,監控系統檢測存活 |
| 日誌分離 | 子進程啟動時 openlog() 設置 ident,或重定向 stdout 到旋轉日誌 |
| 內存洩漏防護 | 子進程處理 N 個任務後主動 exit(0),由父進程重新 fork |