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

PHP 定時任務實現:忽略用戶斷開、無限執行、固定間隔循環

     編輯:PHP 綜合知識

核心需求

PHP 預設為請求-響應模式,無原生定時器。需實現:用戶關閉瀏覽器後繼續執行、突破 30 秒限制、固定間隔循環執行任務(如定時生成靜態、發送郵件、數據同步)。

基礎實現:ignore_user_abort + set_time_limit + sleep

<?php
// 1. 忽略用戶斷開連線(關閉瀏覽器仍繼續執行)
ignore_user_abort(true);

// 2. 移除執行時間限制(預設 30 秒)
set_time_limit(0);

// 3. 設定間隔(秒)
$interval = 24 * 60 * 60; // 每天執行一次

// 4. 無限循環
do {
    // 等待指定間隔
    sleep($interval);
    
    // 執行任務
    $sql = "UPDATE blog SET updated_at = NOW() WHERE status = 1";
    // $db->execute($sql);
    
    // 其他操作:生成報表、發送郵件、清理快取...
    
} while (true);

關鍵函數解析

函數作用注意事項
ignore_user_abort(true)客戶端斷開不終止腳本需在輸出前調用;CLI 模式預設 true
set_time_limit(0)移除 max_execution_time 限制安全模式下無效;需 php.ini 允許
sleep($seconds)暫停執行指定秒數接收信號會提前返回;usleep 微秒級
connection_aborted()檢測客戶端是否斷開配合 ignore_user_abort 判斷

進階版:帶鎖定、日誌、優雅退出

<?php
class CronTask
{
    private $lockFile;
    private $logFile;
    private $interval;
    private $running = true;
    
    public function __construct($interval, $name = 'cron')
    {
        $this->interval = $interval;
        $this->lockFile = sys_get_temp_dir() . "/{$name}.lock";
        $this->logFile = __DIR__ . "/logs/{$name}.log";
        
        // 信號處理:優雅退出
        declare(ticks = 1);
        pcntl_signal(SIGTERM, array($this, 'signalHandler'));
        pcntl_signal(SIGINT, array($this, 'signalHandler'));
    }
    
    public function run()
    {
        // 單實例鎖
        if (!$this->acquireLock()) {
            $this->log("另一實例正在運行,退出");
            return;
        }
        
        ignore_user_abort(true);
        set_time_limit(0);
        
        $this->log("任務啟動,間隔: {$this->interval} 秒");
        
        while ($this->running) {
            $start = microtime(true);
            
            try {
                $this->executeTask();
            } catch (Exception $e) {
                $this->log("任務異常: " . $e->getMessage());
            }
            
            $elapsed = microtime(true) - $start;
            $sleepTime = max(1, $this->interval - $elapsed);
            
            $this->log("本次耗時: {$elapsed}s, 休眠: {$sleepTime}s");
            
            // 分段睡眠,定期檢查退出信號
            $this->interruptibleSleep($sleepTime);
        }
        
        $this->releaseLock();
        $this->log("任務正常結束");
    }
    
    protected function executeTask()
    {
        // 子類實現具體業務邏輯
    }
    
    private function acquireLock()
    {
        $fp = fopen($this->lockFile, 'w');
        if (!flock($fp, LOCK_EX | LOCK_NB)) {
            return false;
        }
        fwrite($fp, getmypid());
        return true;
    }
    
    private function releaseLock()
    {
        @unlink($this->lockFile);
    }
    
    private function interruptibleSleep($seconds)
    {
        $end = time() + $seconds;
        while (time() < $end && $this->running) {
            sleep(min(10, $end - time())); // 每 10 秒檢查一次信號
        }
    }
    
    public function signalHandler($signo)
    {
        $this->log("收到信號 $signo,準備退出...");
        $this->running = false;
    }
    
    private function log($msg)
    {
        $line = date('Y-m-d H:i:s') . " [$msg]\n";
        file_put_contents($this->logFile, $line, FILE_APPEND);
        error_log($line); // 同時寫入 PHP 錯誤日誌
    }
}

// 使用:繼承實現具體任務
class BlogUpdateTask extends CronTask
{
    protected function executeTask()
    {
        // 實際業務邏輯
        $db = new PDO('mysql:host=localhost;dbname=blog', 'user', 'pass');
        $db->exec("UPDATE posts SET view_count = view_count + 1 WHERE featured = 1");
    }
}

// 執行
$task = new BlogUpdateTask(3600, 'blog_update'); // 每小時
$task->run();

部署方式比較

方式優點缺點適用場景
Web 觸發 + ignore_user_abort無需 CLI 權限、共享 Web 環境佔用 FPM 進程、不穩定、難以管理簡單任務、無 CLI 環境
Crontab + CLI 腳本系統級調度、穩定、標準化最小粒度 1 分鐘、無內建防重複大多數定時任務
Systemd Timer秒級精度、依賴管理、日誌整合需 root 權限配置生產環境推薦
Supervisor + 長運行進程進程管理、自動重啟、日誌輪轉需編寫常駐腳本高頻/長任務
消息隊列 Worker解耦、可擴展、失敗重試架構複雜度高異步任務、高併發
Swoole Timer / PHP 8.1+ Fiber進程內毫秒級、高效能需擴展/新版本高頻即時任務

Crontab 部署範例(推薦生產方式)


# /etc/crontab 或 crontab -e

# 每天凌晨 2 點執行
0 2 * * * www-data /usr/bin/php /var/www/html/cron/daily.php >> /var/log/cron/daily.log 2>&1

# 每小時執行(需防重複鎖)
0 * * * * www-data /usr/bin/php /var/www/html/cron/hourly.php

# 每 5 分鐘檢查隊列
*/5 * * * * www-data /usr/bin/php /var/www/html/cron/queue_worker.php

# 開機自啟長運行進程
@reboot www-data /usr/bin/php /var/www/html/cron/daemon.php

防重複執行鎖(Crontab 必備)


#!/usr/bin/php
<?php
$lockFile = '/tmp/my_cron.lock';
$fp = fopen($lockFile, 'w');
if (!flock($fp, LOCK_EX | LOCK_NB)) {
    exit("已有實例運行\n");
}

// 業務邏輯...
flock($fp, LOCK_UN);

常見坑與解決

  • Web 方式佔用 FPM:長任務導致 worker 耗盡,網站無響應 → 改用 CLI
  • sleep 被信號中斷:sleep 返回剩餘秒數,需迴圈補足 → 使用 `interruptibleSleep`
  • 記憶體洩漏:長運行循環中物件未釋放 → 定期 `gc_collect_cycles()`、避免全域變數累積
  • 資料庫連線斷開:長閒置超過 wait_timeout → 定期重連或使用持久連線
  • 時區問題:cron 執行時區與 PHP 時區不一致 → 腳本開頭 `date_default_timezone_set('Asia/Taipei')`

生產環境強烈建議使用 **Crontab + CLI 腳本 + 鎖機制 + 日誌監控**,或 **Systemd Timer**,避免 Web 觸發方式的不可靠性。

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