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

PHP 定時排程任務實作全攻略:Web 觸發、CLI 模式、守護進程與 ignore_user_abort() 詳細比較

     編輯:PHP 相關知識

    核心挑戰:PHP 原生無持久進程

    PHP 設計為「請求-回應」模式,腳本執行完即結束。要實現定時任務,必須引入外部觸發機制或讓腳本長期運行。主流方案對比:

    方案原理優點缺點適用場景
    系統 Crontab + CLI作業系統排程器定時呼叫 php /path/script.php精準、可靠、標準化、支援並行、記憶體隔離需伺服器 root/Shell 存取權VPS/專用主機、生產環境首選
    Crontab + wget/curl排程器發送 HTTP 請求觸發 Web 端點無需 CLI 權限、共享主機可用受 Web 超時/記憶體限制、需防止重複執行共享主機、無 CLI 權限環境
    Web 觸發(用戶請求驅動)每次用戶訪問檢查任務隊列,到期則執行(Discuz、UCenter Home 模式)零配置、無需系統排程流量低時任務延遲、增加請求延遲、併發風險低流量站點、簡單維護任務
    ignore_user_abort() + 無限循環腳本啟動後忽略用戶斷線,sleep() 間隔執行純 PHP 實現、無外部依賴長期運行記憶體洩漏、單進程阻塞、部署管理困難臨時測試、無任何權限環境
    守護進程nohup php daemon.php >/dev/null 2>&1 & 或 Systemd/Supervisor 管理常駐記憶體、高效能、支援信號控制需 CLI、進程管理知識、記憶體洩漏風險高併發、即時性要求高的任務隊列
    消息隊列 + WorkerRedis/RabbitMQ/Beanstalkd 分發任務,多 Worker 消費解耦、可擴展、支援優先級、重試、監控架構複雜、引入額外基礎設施中大型系統、異步處理、排程任務

    方案一:系統 Crontab + CLI(推薦標準做法)

    # 編輯 crontab:crontab -e
    # 每分鐘執行一次
    * * * * * /usr/bin/php /var/www/html/cron/minute.php >> /var/log/cron_minute.log 2>&1
    
    # 每天凌晨 3:30 執行備份
    30 3 * * * /usr/bin/php /var/www/html/cron/backup.php >> /var/log/cron_backup.log 2>&1

    腳本範本:

    <?php
    // cron/minute.php
    define('CRON_RUN', true);
    require_once __DIR__ . '/../bootstrap.php'; // 載入框架/設定
    
    // 防止重複執行(單實例鎖)
    $lockFile = '/tmp/cron_minute.lock';
    $fp = fopen($lockFile, 'w');
    if (!flock($fp, LOCK_EX | LOCK_NB)) {
        exit("另一實例正在運行\n");
    }
    
    // 任務邏輯
    try {
        // 1. 清理過期快取
        Cache::cleanExpired();
        // 2. 發送待發郵件
        EmailQueue::process(50);
        // 3. 統計每日 PV/UV
        Stats::dailyRollup();
    } catch (Throwable $e) {
        error_log("[CRON] " . $e->getMessage());
    } finally {
        flock($fp, LOCK_UN);
        fclose($fp);
    }
    ?>

    方案二:Web 觸發(Discuz/UCenter Home 風格)

    資料表 cron 儲存任務(nextrun、interval、filename)。每次請求掛載 cron.php:

    <?php
    // common.php 或框架入口處引入
    if (defined('IN_CRON')) return; // 避免重複
    
    $now = time();
    $tasks = DB::fetch_all("SELECT * FROM cron WHERE nextrun <= $now AND available = 1");
    foreach ($tasks as $task) {
        // 更新下次執行時間,防止併發重複執行
        DB::query("UPDATE cron SET nextrun = nextrun + interval WHERE cronid = {$task['cronid']}");
        // 執行任務檔案
        @include_once DISCUZ_ROOT . "./source/cron/{$task['filename']}";
    }
    ?>

    優化:僅在非 AJAX、非靜態資源請求時觸發;使用 fastcgi_finish_request() 先回應用戶,再執行任務。

    方案三:ignore_user_abort() 純 PHP 長期運行(不推薦生產)

    <?php
    ignore_user_abort(true);   // 客戶端斷開也繼續執行
    set_time_limit(0);         // 無限執行時間
    
    $interval = 30;            // 每 30 秒執行一次
    $lockFile = '/tmp/my_daemon.lock';
    $fp = fopen($lockFile, 'w');
    if (!flock($fp, LOCK_EX | LOCK_NB)) die("已有實例運行\n");
    
    register_shutdown_function(function() use ($fp, $lockFile) {
        flock($fp, LOCK_UN);
        fclose($fp);
        @unlink($lockFile);
    });
    
    while (true) {
        // 任務邏輯
        $fpLog = fopen('task_log.txt', 'a');
        fwrite($fpLog, date('Y-m-d H:i:s') . " 執行任務\n");
        fclose($fpLog);
    
        sleep($interval);
        // 可選:檢查停止信號檔案
        if (file_exists('/tmp/stop_daemon')) break;
    }
    ?>

    嚴重缺陷:PHP 非為長期運行設計,擴展記憶體洩漏、無法優雅重啟、單點故障。僅適合臨時測試。

    方案四:守護進程 + Supervisor(生產級進程管理)

    Supervisor 配置 /etc/supervisor/conf.d/worker.conf:

    [program:php-worker]
    command=/usr/bin/php /var/www/html/worker.php
    process_name=%(program_name)s_%(process_num)02d
    numprocs=4
    autostart=true
    autorestart=true
    user=www-data
    stdout_logfile=/var/log/worker.log
    redirect_stderr=true

    Worker 腳本(消費 Redis 隊列):

    <?php
    require 'vendor/autoload.php';
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    
    while (true) {
        // 阻塞式取任務,超時 5 秒
        $job = $redis->blpop('job_queue', 5);
        if (!$job) continue;
    
        $data = json_decode($job[1], true);
        try {
            // 處理任務...
            echo "Processed: " . $data['id'] . "\n";
        } catch (Throwable $e) {
            // 重試邏輯、死信隊列...
            error_log($e->getMessage());
        }
    }
    ?>

    關鍵最佳實踐總結

    1. 優先使用系統 Crontab + CLI,簡單可靠,符合 Unix 哲學。
    2. 共享主機無 CLI 時,用 Crontab + wget 觸發受保護的 Web 端點(驗證密鑰)。
    3. 高併發/即時任務:引入 Redis/RabbitMQ + Supervisor 管理 Worker 池。
    4. 所有長期運行腳本必須:
        li>實作單實例鎖(flock)防止重複啟動。
      • 分批處理、及時 unset()、定期 gc_collect_cycles() 控制記憶體。
      • 捕獲 Throwable、記錄日誌、不讓單個任務失敗導致進程退出。
      • 支援優雅關閉(pcntl_signal 捕獲 SIGTERM)。
    5. 避免 ignore_user_abort() + sleep() 死循環作為生產方案。
    1. 上一篇文章:
    2. 下一篇文章:
    Copyright © 程式碼之家 All Rights Reserved