2009-12-07 41 views
2

我有我的Linux腳本以及Windows服務器上運行的PHP腳本。 我想使用相同的腳本,而不對這兩種環境進行任何修改。如何防止腳本的多個實例?

這些腳本將與我的Windows環境計劃與cron(在Linux上)和Windows調度程序(或其他,我不在乎)。

但是,其中一些腳本可能需要幾分鐘才能完成。我只是想阻止調度程序(cron或windows的)啓動相同的腳本,然後才能啓動它。

我不知道該怎麼做.. 我想確保在執行過程中出現問題時釋放「鎖定」,所以下次不需要人工干預就可以重新啓動。

也許與一個虛擬文件的羊羣會做的伎倆,但我不知道該怎麼做。

我在這些服務器上也有一個MySQL數據庫。我想也許使用數據庫端的鎖。

1- Start a transaction 
2- Insert script name in a table. 
3- execution of the script. 
4- If successful then delete the row and commit the transaction or simply rollback; 

如果腳本名稱在表中,我可以阻止它運行。 如果腳本執行失敗,則Mysql將自動回滾該事務,以便在下次調用該腳本時不會出現該行。

但是,在一個事務中,有沒有辦法讓其他連接看到未公開的數據?如果是的話,怎麼樣?

我也使用該行的鎖,如果它是不可能使用回滾事以爲,

1- Insert script name in a table if it doesn't already exists. 
2- Start a transaction. 
2- Select * from Table where script_name FOR UPDATE. 
3- execution of the script. 
4- If successful then release the lock (rollback or commit). 

但是我在這裏主要的問題是與MySQL。選擇FOR UPDATE掛起直到上一個鎖被釋放,或者如果經過了50秒超時(innodb_lock_wait_timeout變量)。我希望Mysql在現場告訴我,我的行在不影響整個數據庫的情況下被鎖定。這是因爲innodb_lock_wait_timeout變量是全局變量(不是會話之一)。 是否有另一個模仿Oracle中可用的NO_WAIT子句的變量?

或者我應該讓劇本掛50秒沒有任何問題?

什麼是最好的辦法,因爲我是一個PHP新手,我不想在服務器上造成任何問題。

也許我還有另外一個選擇,我沒有看到..

+0

您的腳本在任何情況下都使用MySQL數據庫嗎?或者其他一些資源? – VolkerK

+0

其中有些是有的,有些則不是......這些腳本是可配置的,我希望在所有這些腳本都會繼承的基類上實現鎖定。所以可以使用數據庫。 – Pmax

回答

10

我使用解決了這個問題...插座。你可以啓用php_sockets然後再試一下。下面是代碼示例:在特定$port

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 
if (false === $socket) { 
    throw new Exception("can't create socket: ".socket_last_error($socket)); 
} 
## set $port to something like 10000 
## hide warning, because error will be checked manually 
if (false === @socket_bind($socket, '127.0.0.1', $port)) { 
    ## some instanse of the script is running 
    return false; 
} else { 
    ## let's do your job 
    return $socket; 
} 

綁定插座是concurent執行安全操作。操作系統將確保沒有其他進程將套接字綁定到同一端口。你只需要檢查返回值。

如果腳本崩潰,操作系統會自動解除綁定端口。

這也可以用於任何語言。我在perl和基於php的項目上測試過它。即使我們在crontab中錯誤地添加了腳本兩次,它也停止了並行執行。

+0

好(簡單)解決方案 – robjmills

+0

好,簡單的解決方案的確如此。謝謝 – Pmax

1

檢查是否鎖定文件 僞代碼(即「script_running.lock」。):

if file exists exit 
else 
create the file 
run the rest of the script 
unlink the file when script is done 
+0

如果腳本崩潰,這不會失敗嗎? IE瀏覽器。 'file'永遠不會'取消鏈接' –

+1

你可以設置一個關閉處理程序/錯誤處理程序清理。仍然,上面的套接字選項更好:) – jlb

+0

這仍然不能解決這種情況,因爲你不能保證處理程序運行(崩潰的程序不會運行處理程序)。你應該打開這個文件,如果它還不存在,創建它,並且非阻塞鎖定它(羣集或類似的)。如果鎖定失敗,那麼你的程序已經在運行。如果程序崩潰,操作系統會釋放鎖。如果操作系統在重新啓動時崩潰,則該文件不會被鎖定。保持文件打開並鎖定,直到腳本退出。 – steveayre

2

爲什麼不使用老式的信號燈,它只是爲了這個。我敢肯定,有實現的Windows可用的一樣好,甚至乾脆PHP可以兼容的:

if ($theSemaphore = sem_get("123456",1)) { // this "1" ensures that there is nothing parallel 
    if (sem_acquire($theSemaphore)) { // this blocks the execution until other processes or threads are finished 
    <put your code to serialize here> 
    sem_release($theSemaphore); // This should be called only if sem_acquire() succeeds 
    } 
} 

在Apache的線程環境,這是工作的罰款,也是在PHP-CLI和混合。如果某個過程意外死亡,則信號量無效,範圍再次被獲取。 信號量實現「原子」,以防止鎖定期間的競爭條件。

A nice description based on toilets is here

0

現代2017年答:

有實現在PHP中的鎖許多方面。

  • 阿呆文件存在檢查,又名「新手的第一個孩子鎖嘗試」:只需創建一個文件,然後刪除它,當你完成。所有其他實例檢查它是否存在。這會造成很大的風險,即完成後文件不會被刪除(例如,如果電源丟失或劇本被強行殺死),這意味着所有將來的腳本運行都會失敗。它也遭受多個同時啓動的實例,看到該文件丟失,並且都試圖獨佔地創建它。這是可怕的
  • 將進程ID寫入文件:上述方法略有改進。但仍然極端 hacky和遭受競爭條件。您將當前PHP實例的進程ID寫入文本文件,然後所有其他實例讀取該文件的內容,並檢查該進程ID是否仍然存在,並且是PHP進程,如果是這樣,我們認爲進程「鎖定」 。如果兩個腳本開始非常接近,彼此讀取相同的文本文件的內容和覺得以前的PHP進程ID不再運行和相信他們有一個排它鎖很容易受到競爭條件。此方法應避免在全部的成本。它的幾乎沒有即使對於基本的Bash shell腳本(其中沒有其他方法可用)也可以接受,但PHP有更先進的鎖定方法可用。
  • 套接字:綁定到本地端口。如果端口正在使用,請將其視爲「已鎖定」。優點:不需要鎖定文件。缺點:端口可能被系統本身使用,或者系統的配置可能不允許進程執行端口綁定,並且它也比鎖定文件慢得多(因爲它調用OS的整個套接字系統並創建一個套接字)。
  • 信號量:非常快和可靠,但Unix-only(Posix)。
  • 獨佔文件鎖定:這是跨平臺(Unix,Linux,Mac,Windows),超級可靠和快速。這就是我在下面乾淨利落地實施的。

locker.inc.php:

<?php 

class Locker 
{ 
    private $_filename; 
    private $_fh = NULL; 

    public function __construct(string $filename) 
    { 
     $this->_filename = $filename; 
    } 

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

    /** 
    * Attempt to acquire an exclusive lock. Always check the return value! 
    * @param bool $block If TRUE, we'll wait for existing lock release. 
    * @return bool TRUE if we've acquired the lock, otherwise FALSE. 
    */ 
    public function lock(bool $block = TRUE) 
    { 
     // Create the lockfile if it doesn't exist. 
     if(! is_file($this->_filename)) { 
      $created = @touch($this->_filename); 
      if(! $created) { 
       return FALSE; // no file 
      } 
     } 

     // Open a file handle if we don't have one. 
     if($this->_fh === NULL) { 
      $fh = @fopen($this->_filename, 'r'); 
      if($fh !== FALSE) { 
       $this->_fh = $fh; 
      } else { 
       return FALSE; // no handle 
      } 
     } 

     // Try to acquire the lock (blocking or non-blocking). 
     $lockOpts = ($block ? LOCK_EX : (LOCK_EX | LOCK_NB)); 
     return flock($this->_fh, $lockOpts); // lock 
    } 

    /** 
    * Release the lock. Also happens automatically when the Locker 
    * object is destroyed, such as when the script ends. Also note 
    * that all locks are released if the PHP process is force-killed. 
    * NOTE: We DON'T delete the lockfile afterwards, to prevent 
    * a race condition by guaranteeing that all PHP instances lock 
    * on the exact same filesystem inode. 
    */ 
    public function unlock() 
    { 
     if($this->_fh !== NULL) { 
      flock($this->_fh, LOCK_UN); // unlock 
      fclose($this->_fh); 
      $this->_fh = NULL; 
     } 
    } 
} 

testlock.php:

<?php 

require_once('locker.inc.php'); 

$locker = new Locker('test.lock'); 

echo time() . ": acquiring lock...\n"; 
$is_locked = $locker->lock(TRUE); // TRUE = blocking 
if($is_locked) { // ALWAYS check this return value 
    echo time() . ": we have a lock...\n"; 
    sleep(10); // hold the lock for 10 seconds 
    // manually unlock again, but we don't have 
    // to do this since it also happens when 
    // the $locker object is destroyed (i.e. 
    // when the script ends). 
    $locker->unlock(); 
} else { 
    echo time() . ": failed to get lock...\n"; 
} 

您可以將TRUE更改爲FALSE的測試腳本,如果你不」不希望你的其他腳本排隊等待釋放鎖。

因此,選擇是你的:

  • TRUE:等待,直到鎖可用。如果你想讓所有的工作都能運行,那就太好了,但是他們必須等待輪到他們完全運行。
  • FALSE:不要等待鎖定不可用。如果您想中止其他腳本(如果它的一個實例已經在運行),這很有用。
0

或者,您可以使用LOCK文件。這個想法很簡單:如果執行腳本S,它會首先檢查某個(唯一的)文件的存在,說S.lock

  • 如果該文件存在,S將終止。

  • 否則,它會創建它。如果S退出,該文件將被刪除。