簡體   English   中英

PHP 鎖定/確保給定腳本在任何給定時間只運行一次

[英]PHP locking / making sure a given script is only running once at any given time

我正在嘗試編寫一個 PHP 腳本,我想確保它在任何給定時間只運行一個實例。 所有這些關於不同鎖定方式、競爭條件等的討論都給了我很大的幫助。

我對鎖文件是要走的路,還是信號量,或使用 MySQL 鎖,等等等等感到困惑。

誰能告訴我:

a) 實現這一點的正確方法是什么?

b) 指向我的 PHP 實現(或易於移植到 PHP 的東西?)

一種方法是將 php 函數flock與一個虛擬文件一起使用,它將充當看門狗。

在我們的工作開始時,如果文件引發了LOCK_EX標志,則可以退出或等待。

PHP 羊群文檔: http : //php.net/manual/en/function.flock.php

對於此示例,必須首先創建名為lock.txt的文件。

示例 1 ,如果另一個孿生進程正在運行,它將正確退出,無需重試,並給出狀態消息。

如果文件lock.txt不可訪問,它將拋出錯誤狀態。

<?php

$fp = fopen("lock.txt", "r+");

if (!flock($fp, LOCK_EX|LOCK_NB, $blocked)) {
    if ($blocked) {

        // another process holds the lock
        echo "Couldn't get the lock! Other script in run!\n"; 

    }
    else {
        // couldn't lock for another reason, e.g. no such file
        echo "Error! Nothing done.";
    }
}
else {
    // lock obtained
    ftruncate($fp, 0);  // truncate file

    // Your job here 
    echo "Job running!\n";
    
    // Leave a breathe
    sleep(3);

    fflush($fp);            // flush output before releasing the lock
    flock($fp, LOCK_UN);    // release the lock

}

fclose($fp); // Empty memory

示例 2FIFO先進先出):我們希望進程在隊列之后等待執行,如果有的話:

<?php

$fp = fopen("lock.txt", "r+");

if (flock($fp, LOCK_EX)) {  // acquire an exclusive lock
    ftruncate($fp, 0);      // truncate file

    // Your job here 
    echo "Job running!\n";

    // Leave a breathe
    sleep(3);

    fflush($fp);            // flush output before releasing the lock
    flock($fp, LOCK_UN);    // release the lock
}

fclose($fp);

通過在腳本結束時創建和刪除文件,它也可以通過fopen進入x模式。

創建和開放僅用於寫作; 將文件指針放在文件的開頭。 如果文件已經存在, fopen() 調用將失敗,返回 FALSE

http://php.net/manual/en/function.fopen.php


但是,在Unix 環境中,為了進行微調,我發現使用getmypid()將每個后台腳本的PID列出到數據庫或單獨的 JSON 文件中更容易。

當一個任務結束時,腳本負責在這個文件中聲明他的狀態(例如:成功/失敗/調試信息等),然后刪除他的PID 在我看來,這允許以更簡單的方式創建管理員工具和守護進程。 並在必要時使用posix_kill()從 PHP 中posix_kill() PID。

微服務是使用類 Unix 管道組成的。 服務可以調用服務。 https://en.wikipedia.org/wiki/Microservices


另請參閱:防止 PHP 腳本在運行時耗盡所有資源?

// borrow from 2 anwsers on stackoverflow
function IsProcessRunning($pid) {
    return shell_exec("ps aux | grep " . $pid . " | wc -l") > 2;
}

function AmIRunning($process_file) {
    // Check I am running from the command line
    if (PHP_SAPI != 'cli') {
        error('Run me from the command line');
        exit;
    }

    // Check if I'm already running and kill myself off if I am
    $pid_running = false;
    $pid = 0;
    if (file_exists($process_file)) {
        $data = file($process_file);
        foreach ($data as $pid) {
            $pid = (int)$pid;
            if ($pid > 0 && IsProcessRunning($pid)) {
                $pid_running = $pid;
                break;
            }
        }
    }
    if ($pid_running && $pid_running != getmypid()) {
        if (file_exists($process_file)) {
            file_put_contents($process_file, $pid);
        }
        info('I am already running as pid ' . $pid . ' so stopping now');
        return true;
    } else {
        // Make sure file has just me in it
        file_put_contents($process_file, getmypid());
        info('Written pid with id '.getmypid());
        return false;
    }
}

/*
 * Make sure there is only one instance running at a time
 */
$lockdir = '/data/lock';
$script_name = basename(__FILE__, '.php');
// The file to store our process file
$process_file = $lockdir . DS . $script_name . '.pid';

$am_i_running = AmIRunning($process_file);
if ($am_i_running) {
    exit;
}

使用信號量:

$key = 156478953; //this should be unique for each script
$maxAcquire = 1;
$permissions =0666;
$autoRelease = 1; //releases semaphore when request is shut down (you dont have to worry about die(), exit() or return
$non_blocking = false; //if true, fails instantly if semaphore is not free

$semaphore = sem_get($key, $maxAcquire, $permissions, $autoRelease);
if (sem_acquire($semaphore, $non_blocking ))  //blocking (prevent simultaneous multiple executions)
{
    processLongCalculation();
}
sem_release($semaphore);

看:

https://www.php.net/manual/en/function.sem-get.php

https://www.php.net/manual/en/function.sem-acquire.php

https://www.php.net/manual/en/function.sem-release.php

如果您在 linux 上使用 php,我認為最實用的方法是:

<?php
 if(shell_exec('ps aux | grep '.__FILE__.' | wc  -l')>3){
    exit('already running...');
 }
?>

另一種方法是使用文件標志和退出回調,退出回調將確保在 php 執行結束的任何情況下文件標志都將重置為 0 也是致命錯誤。

<?php
function exitProcess(){
  if(file_get_contents('inprocess.txt')!='0'){
    file_put_contents('inprocess.txt','0');  
  }
}

if(file_get_contents('inprocess.txt')=='1'){
  exit();
}

file_put_contents('inprocess.txt','1');
register_shutdown_function('exitProcess');

/**
execute stuff
**/
?>

您可以選擇最適合您的項目的解決方案,實現這兩種簡單方法是文件鎖定或數據庫鎖定。

有關文件鎖定的實現,請查看http://us2.php.net/flock

如果您已經在使用數據庫,請創建一個表,為該腳本生成已知標記,將其放在那里,然后在腳本結束后將其刪除。 為避免錯誤問題,您可以使用到期時間。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM