У меня есть сценарии php, которые я должен запускать как на Linux, так и на серверах Windows. Я хочу использовать те же сценарии без каких-либо изменений для этих двух сред.
Тезисы будут запланированы с помощью cron (on 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 variable). Я бы хотел, чтобы Mysql сразу сказал мне, что моя строка заблокирована, не затрагивая всю базу данных. Это связано с тем, что переменная innodb_lock_wait_timeout является глобальной (а не сеансовой). Есть ли другая переменная, которая имитирует предложение NO_WAIT, доступное в Oracle?
Или я должен позволить сценарию повесить 50 секунд без каких-либо проблем?
Каков наилучший подход к этому, поскольку я новичок php, и я не хочу создавать проблемы на сервере.
Может быть, у меня есть другой вариант, который я не видел.
Я решил эту проблему, используя … сокеты. Это вы можете включить расширение 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
-порте – безопасная операция для выполнения сочетанного выполнения. Операционная система будет следить за тем, чтобы не было другого процесса, связанного сокетам с одним и тем же портом. Вам просто нужно проверить возвращаемое значение.
Если скрипт выйдет из строя, операционная система автоматически отвяжет порт.
Это также можно использовать на любом языке. Я тестировал его на проектах на основе perl и php. Он остановил параллельное выполнение, даже если мы дважды добавили скрипт в crontab по ошибке.
Почему бы не использовать старомодный семафор, это просто сделано именно для этого. Я уверен, что существуют также версии для 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 и смешанном. В случае, если процесс неожиданно завершится, семафор недействителен, и фургон снова будет приобретен. Семафоры реализованы «атомарными», поэтому предотвращают условия гонки во время блокировки.
Хорошее описание, основанное на туалетах, здесь
Проверьте файл блокировки (например, «script_running.lock») псевдокод:
if file exists exit else create the file run the rest of the script unlink the file when script is done
Современный год 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 (ie // when the script ends). $locker->unlock(); } else { echo time() . ": failed to get lock...\n"; }
Вы можете изменить TRUE на FALSE в тестовом скрипте, если вы не хотите, чтобы ваши другие сценарии подождали в очереди, чтобы блокировка была выпущена.
Таким образом, выбор за вами:
В качестве альтернативы вы можете использовать LOCK-файлы. Идея проста: если выполняется скрипт S, он сначала проверяет наличие определенного (уникального) файла, например S.lock
:
Если файл существует, S завершается.
В противном случае это создаст его. Если S выйдет, файл будет удален.