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

PHP 計劃任務防重複執行:進程鎖機制實現與 ps aux 檢測方案

     編輯:PHP 綜合知識

Linux Crontab 定時任務若執行時間超過間隔周期,或因系統抖動導致重複啟動,會引發嚴重數據不一致(如重複扣款、重複發送郵件)。本文提供基於 ps aux 進程掃描的防重複執行機制,適用於無法使用文件鎖、數據庫鎖、Redis 鎖的輕量級場景。

問題場景

  • 任務執行耗時 3 分鐘,Cron 間隔 1 分鐘 → 並發執行多個實例
  • 網絡抖動導致 Cron 守護進程重複觸發
  • 手動觸發與自動排程衝突

核心實現代碼

<?php
class JobLock {
    /**
     * 驗證是否已有同名任務在運行
     * @param string $jobName 任務標識(對應腳本文件名或業務標識)
     * @param string $scriptPath 入口腳本完整路徑(如 /var/www/html/index.php)
     * @param string $route 路由/參數部分(如 /records/job/syncData)
     * @return void  若檢測到重複則直接 exit()
     */
    public static function verify( $jobName, $scriptPath, $route ) {
        // 構建進程命令特徵字符串
        $cmdPattern = $scriptPath . ' ' . $route;
        
        // 執行 ps aux 並 grep 過濾
        exec("ps aux | grep '" . escapeshellarg($cmdPattern) . "'", $output);
        
        $runningCount = 0;
        foreach ( $output as $line ) {
            // 過濾掉 grep 進程本身
            if ( strpos($line, 'grep') !== false ) continue;
            // 匹配完整命令特徵
            if ( strpos($line, $cmdPattern) !== false ) {
                $runningCount++;
            }
        }
        
        // 當前進程 + 歷史殘留 = 1 為正常;>1 表示重複
        if ( $runningCount > 1 ) {
            $timestamp = date('Y-m-d H:i:s');
            error_log("[$timestamp] Job '$jobName' duplicate detected (count: $runningCount), aborting.");
            exit(1); // 非零退出碼便於監控告警
        }
    }
}

// === 使用示例 ===
// 在任務入口文件最開頭調用
require_once 'JobLock.php';

// 參數說明:
// $jobName: 業務標識,用於日誌
// $scriptPath: PHP 入口腳本絕對路徑
// $route: 框架路由或腳本參數
JobLock::verify(
    'syncUserData',
    '/usr/local/www/scrm/public/index.php',
    '/records/job/syncUserData'
);

// 後續正常業務邏輯...
?>

關鍵技術細節

要點處理方式
命令注入防護escapeshellarg() 轉義傳入 exec() 的參數
grep 進程自匹配過濾包含 grep 關鍵字的行
命令特徵唯一性使用「入口腳本 + 路由」組合,區分不同任務
計數邏輯正常情況 $runningCount == 1(僅當前進程);>1 視為重複
退出碼exit(1) 非零碼,便於 Cron 監控工具識別異常
日誌記錄error_log() 寫入系統/應用日誌,包含時間戳與計數

進階改進:PID 文件鎖(更可靠)

ps aux 方案存在竞態條件(檢查與啟動間隙)且依賴進程命令行格式。生產環境強烈建議使用 PID 文件鎖:

class PidLock {
    private $pidFile;
    private $fp;

    public function __construct( $jobName ) {
        $this->pidFile = sys_get_temp_dir() . "/job_lock_{$jobName}.pid";
    }

    public function acquire(): bool {
        $this->fp = fopen($this->pidFile, 'c+');
        if ( ! $this->fp ) return false;
        
        // 非阻塞排他鎖
        if ( ! flock($this->fp, LOCK_EX | LOCK_NB) ) {
            fclose($this->fp);
            return false; // 已有進程持有鎖
        }
        
        // 寫入當前 PID
        ftruncate($this->fp, 0);
        fwrite($this->fp, getmypid());
        fflush($this->fp);
        return true;
    }

    public function release() {
        if ( $this->fp ) {
            flock($this->fp, LOCK_UN);
            fclose($this->fp);
            @unlink($this->pidFile);
        }
    }

    public function __destruct() {
        $this->release();
    }
}

// 使用
$lock = new PidLock('syncUserData');
if ( ! $lock->acquire() ) {
    error_log("[JobLock] syncUserData already running, exit.");
    exit(1);
}
// 業務邏輯...
$lock->release(); // 或依賴析構函數自動釋放

方案對比選型

方案可靠性複雜度跨機器適用場景
ps aux 掃描中(有竞態)低否單機、輕量、無文件寫入權限
PID 文件鎖 (flock)高(內核級原子操作)低否單機標準生產環境首選
數據庫行鎖 (SELECT FOR UPDATE)高中是分布式、已有 DB 連接池
Redis SETNX 鎖高中是分布式、高併發、需自動過期
系統工具 flock(1)高極低否Crontab 直接包裝:flock -n /tmp/job.lock -c 'php job.php'

Crontab 直接使用 flock(1) 最佳實踐

# /etc/crontab
* * * * * www-data flock -n /tmp/sync_user_data.lock -c 'php /var/www/html/index.php /records/job/syncUserData >> /var/log/sync_user_data.log 2>&1'
  • -n:非阻塞,鎖被佔用時直接退出(不等待)
  • -c:執行命令字符串
  • 無需修改 PHP 代碼,運維層面解決防重問題

根據項目架構選擇合適方案:單機首選 flock(1) 或 PID 文件鎖;分布式環境使用 Redis/數據庫鎖;ps aux 僅作為無文件寫入權限環境的備選。

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