我有php脚本,我必须在Linux和Windows服务器上运行。 我想使用相同的脚本而不对这两个环境进行任何修改。
这些脚本将使用cron(在linux上)和Windows调度程序(或其他,我现在不关心)安排在我的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 :(得分:10)
我使用...套接字解决了这个问题。你可以启用php_sockets
扩展然后尝试它。这是代码示例:
$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;
}
特定$port
上的绑定套接字是concurent执行的安全操作。操作系统将确保没有其他进程将套接字绑定到同一端口。您只需要检查返回值。
如果脚本崩溃,则操作系统将自动取消绑定端口。
这也可以用于任何语言。我已经在perl和基于php的项目上测试了它。即使我们错误地在crontab中添加了两次脚本,它也会停止并行执行。
答案 1 :(得分: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中并且是混合的。如果进程意外死亡,则信号量无效并且再次获取van。 信号量是“原子”实现的,因此可以防止锁定期间的竞争条件。
答案 2 :(得分: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
答案 3 :(得分:1)
2017年现代答案:
在PHP中实现锁的方法有很多种。
<强> 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。
所以你的选择是:
答案 4 :(得分:0)
或者你也可以使用LOCK文件。这个想法很简单:如果执行脚本S,它将首先检查某个(唯一)文件是否存在,比如S.lock
:
如果文件存在,S将终止。
否则,它会创建它。如果S退出,则文件将被删除。