2009-12-07 28 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退出,该文件将被删除。