守護進程 是脫離終端、在後台持續運行的長生命周期進程。Nginx、MySQL、Redis、PHP-FPM 本質上都是守護進程。本文通過完整代碼示範如何用 PHP 編寫健壯的守護進程,涵蓋雙重 Fork、PID 文件鎖、權限降級、信號處理、ZeroMQ 通訊等生產級要素。
<?php
class DaemonService {
// 配置常量
const LISTEN_ADDR = "tcp://0.0.0.0:5555";
const MAX_WORKERS = 100;
const PID_FILE = '/var/run/daemon_service.pid';
const RUN_UID = 80; // 非 root 用戶
const RUN_GID = 80;
private $pool;
private $zmq;
private $pidFile;
public function __construct() {
$this->pidFile = self::PID_FILE;
}
/** 雙重 Fork 脫離終端 */
private function daemonize() {
if (file_exists($this->pidFile)) {
die("PID 文件已存在,進程可能正在運行\n");
}
$pid = pcntl_fork();
if ($pid === -1) die('Fork 失敗');
if ($pid > 0) exit(0); // 父進程退出
// 子進程:創建新會話,脫離控制終端
if (posix_setsid() === -1) die('Setsid 失敗');
// 二次 Fork 確保無法重新獲得控制終端
$pid = pcntl_fork();
if ($pid === -1) die('二次 Fork 失敗');
if ($pid > 0) exit(0);
// 寫入 PID,降級權限
file_put_contents($this->pidFile, getmypid());
posix_setuid(self::RUN_UID);
posix_setgid(self::RUN_GID);
return getmypid();
}
/** 啟動服務主循環 */
private function start() {
$this->daemonize();
// Worker 池(需 pthreads 擴展)
$this->pool = new Pool(self::MAX_WORKERS, Worker::class, []);
// ZeroMQ REP 模式:同步請求-回覆
$ctx = new ZMQContext();
$this->zmq = new ZMQSocket($ctx, ZMQ::SOCKET_REP);
$this->zmq->bind(self::LISTEN_ADDR);
while (true) {
$msg = $this->zmq->recv(); // 阻塞等待請求
$this->pool->submit(new Task($msg)); // 分發任務
$this->zmq->send('ACK'); // 即時回覆
}
}
/** 優雅停止 */
private function stop() {
if (file_exists($this->pidFile)) {
$pid = (int)file_get_contents($this->pidFile);
posix_kill($pid, SIGTERM); // 發送終止信號
unlink($this->pidFile);
}
}
public function main($argv) {
$cmd = $argv[1] ?? 'help';
match($cmd) {
'start' => $this->start(),
'stop' => $this->stop(),
default => echo "Usage: php daemon.php start|stop\n"
};
}
}
// 入口
$service = new DaemonService();
$service->main($argv);
| 環節 | 關鍵點 | 作用 |
|---|---|---|
| 雙重 Fork | pcntl_fork() × 2 + posix_setsid() | 完全脫離終端,防止獲得控制終端 |
| PID 文件鎖 | 啟動檢查/寫入,停止讀取/刪除 | 單實例保護,防止端口衝突 |
| 權限降級 | posix_setuid/gid 切換到非 root 用戶 | 最小權限原則,降低被攻擊風險 |
| 信號處理 | pcntl_signal(SIGTERM, fn() => $shutdown = true) | 優雅退出,釋放資源,持久化狀態 |
| 僵屍進程回收 | pcntl_signal(SIGCHLD, fn() => pcntl_wait($status)) | 避免子進程退出未回收佔用進程表 |
| 任務併發 | Pool + pthreads / Swoole / ReactPHP | 多線程/協程處理高併發業務邏輯 |
PHP 守護進程開發已進入「框架化」時代:Swoole、Workerman、Laravel Octane 封裝了上述底層細節,建議生產項目直接使用成熟框架,專注業務邏輯。理解底層原理,是為了在框架失效時能精準定位、在極限場景下做深度優化。