首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 實作進程守護:自動重啟、狀態監控與過載保護

     編輯:PHP 相關知識

前言:為什麼需要 PHP 進程守護?

在生產環境中,Nginx、PHP-FPM、Worker 等長期運行的進程可能因程式錯誤、資源耗盡、收到信號等原因意外退出。傳統做法依賴 systemd、supervisord 或 monit 等外部工具,但若環境受限(如無 root 權限、不想引入額外依賴),一個純 PHP 實作的守護腳本同樣能勝任。

核心功能特性

  • 非 Daemon 模式即時重啟: 子進程異常退出(status > 0)立即拉起,無延遲。
  • Daemon 模式心跳檢測: 每秒掃描 /proc/$pid/stat 確認進程存活,掛掉 1 秒內重啟。
  • 指數級重啟保護: 預設 60 秒內重啟超過 5 次即停止嘗試,防止故障風暴拖垮系統。
  • 支援任意可執行檔: 不限於 PHP 腳本,可守護 Nginx、Go、Python 等任何前景執行的程式。
  • 用戶身份切換: 可指定執行用戶(需 root 權限啟動守護腳本)。

使用方式

php supervisor.php /usr/local/nginx/sbin/nginx nobody &

參數說明:

  1. 本腳本路徑(supervisor.php)
  2. 被守護程式絕對路徑
  3. 執行用戶名
  4. 後續參數透傳給被守護程式

建議: 被守護程式**務必以前景(非 daemon)模式運行**,否則父進程無法正確捕獲退出狀態。

核心程式碼解析

1. 進程分叉與執行

$pid = pcntl_fork();
if ($pid == 0) {
    // 子進程:替換映像執行目標程式
    pcntl_exec($path, $args, $_SERVER);
    write_log("execute file failed");
    exit(0);
}

使用 pcntl_fork() 建立子進程,子進程呼叫 pcntl_exec() 將自身替換為目標程式,保留環境變數。

2. 父進程等待與狀態判斷

$endpid = pcntl_waitpid($pid, $status, WUNTRACED);

if ($status == 0) {
    // 可能是 daemon 化,檢查 /proc/$pid/stat 是否存在
    if (file_exists("/proc/" . ($pid+1) . "/stat")) {
        // 進程存活,進入心跳檢測迴圈
        for (;;) {
            if (file_exists("/proc/" . ($pid+1) . "/stat")) {
                usleep(1000000); // 1 秒
            } else {
                write_log("program die");
                break; // 跳出心跳迴圈,觸發重啟邏輯
            }
        }
    } else {
        write_log("program start failed");
        exit(0);
    }
} elseif ($status > 0) {
    // 非 daemon 異常退出,直接記錄並準備重啟
    write_log("program die");
}

3. 重啟頻率限流器

function chk_need_start(&$times_arr, $duration, $max_times) {
    // 清理超過時間窗口的記錄
    foreach ($times_arr as $k => $v) {
        if ($k < time() - $duration) unset($times_arr[$k]);
    }
    // 判斷是否超過閾值
    if (count($times_arr) >= $max_times) return false;
    $times_arr[time()] = 1;
    return true;
}

以時間戳為鍵記錄每次重啟,滑動窗口算法實現「N 秒內最多 M 次」限制。

完整程式碼(supervisor.php)

<?php
// author: tipboy@qq.com
$_retry_times = 5;
$_retry_times_duration = 60;
$_times_arr = array();

$args = $_SERVER['argv'];
if (count($args) < 3) {
    write_log("args num error!");
    write_log("usage: php supervisor.php /path/to/bin user [args...]");
    exit();
}

$path = $args[1];
$username = $args[2];
// 移除前三個參數(腳本名、程式路徑、用戶)
array_shift($args); array_shift($args); array_shift($args);

while (true) {
    $pid = pcntl_fork();
    if ($pid == 0) {
        pcntl_exec($path, $args, $_SERVER);
        write_log("execute file failed");
        exit(0);
    } elseif ($pid > 0) {
        $endpid = pcntl_waitpid($pid, $status, WUNTRACED);
        
        if ($status == 0) {
            if (file_exists("/proc/" . ($pid+1) . "/stat")) {
                write_log("program start success");
                for (;;) {
                    if (file_exists("/proc/" . ($pid+1) . "/stat")) {
                        usleep(1000000);
                    } else {
                        write_log("program die");
                        break;
                    }
                }
            } else {
                write_log("program start failed");
                exit(0);
            }
        } elseif ($status > 0) {
            write_log("program die");
        } else {
            exit(0);
        }

        if (chk_need_start($_times_arr, $_retry_times_duration, $_retry_times)) {
            continue;
        } else {
            write_log("retry limit exceeded, stop guardian");
            break;
        }
    }
}

function write_log($msg) {
    echo date('Y-m-d H:i:s') . ' ' . $msg . "\n";
}

function chk_need_start(&$times_arr, $duration, $max_times) {
    foreach ($times_arr as $k => $v) {
        if ($k < time() - $duration) unset($times_arr[$k]);
    }
    if (count($times_arr) >= $max_times) return false;
    $times_arr[time()] = 1;
    return true;
}
?>

部署建議

  • 將腳本放置於 /usr/local/bin/supervisor.php,賦予執行權限。
  • 使用 nohup php supervisor.php ... > /var/log/supervisor.log 2>&1 & 後台運行並記錄日誌。
  • 配合 crontab 或 systemd 實現開機自啟。
  • 生產環境建議整合 logrotate 切割日誌。

總結

這個約 100 行的 PHP 腳本,利用 pcntl 擴展實現了企業級進程守護的核心功能:即時重啟、心跳監控、頻率限流。它零依賴、輕量級、易於定製,極適合中小型專案或受限環境下的服務穩定性保障。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved