2009-12-07 6 views
2

나는 Windows 서버뿐만 아니라 리눅스에서도 실행해야하는 PHP 스크립트를 가지고 있습니다. 두 환경에서 수정하지 않고 동일한 스크립트를 사용하고 싶습니다.스크립트의 배수 인스턴스를 방지하는 방법은 무엇입니까?

논문 스크립트 (리눅스에서) 및 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과입니다. 이전 잠금이 해제되거나 50 초의 시간 초과 (innodb_lock_wait_timeout 변수)가 경과 할 때까지 FOR UPDATE hang을 선택하십시오. MySQL이 전체 데이터베이스에 영향을 미치지 않고 내 행이 잠겨 있다는 것을 바로 알려주고 싶습니다. 이는 innodb_lock_wait_timeout 변수가 글로벌 변수 (세션이 아닌)이기 때문입니다. Oracle에서 사용할 수있는 NO_WAIT 절을 모방하는 다른 변수가 있습니까?

또는 스크립트를 아무런 문제없이 50 초 동안 멈춰야합니까? 나는 PHP는 초보자와 나는 서버에 문제가 발생하지 않는 한

는에 가장 좋은 방법은 무엇이다.

아마 나는 보지 못했다 다른 옵션 .. 잠금 파일에 대한

+0

어떤 경우에도 MySQL 데이터베이스를 사용하는 스크립트가 있습니까? 아니면 다른 자원들? – VolkerK

+0

그들 중 일부는, 일부는하지 않습니다 ... Theses 스크립트는 구성 할 수 있으며 모든 theses 스크립트가 상속하는 기본 클래스에 잠금을 구현하고 싶습니다. 따라서 데이터베이스 사용이 가능합니다. – 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

스크립트가 충돌하는 경우에도이 오류가 발생하지 않습니까? 예. 'file'은'unlink'를 얻지 못할 것입니다. –

+1

당신은 셧다운 처리기/에러 처리기를 설정할 수 있습니다. 여전히 위의 소켓 옵션은 더 멋지다. – jlb

+0

핸들러가 실행되는 것을 보장 할 수 없으므로 여전히이 케이스를 해결하지 못한다 (충돌하는 프로그램은 핸들러를 실행하지 않는다). 파일을 열어서 파일이 아직 존재하지 않는 경우 파일을 만들어야하며, 비 블로킹 (non-blocking)은 파일을 잠급니다 (무리 등). 잠금이 실패하면 프로그램이 이미 실행 중입니다. 프로그램이 충돌하면 OS가 잠금을 해제합니다. 재부팅 할 때 OS가 충돌하면 파일이 잠기지 않습니다. 스크립트가 종료 될 때까지 파일을 열어 두십시오. – steveayre

2

구식 세마포어를 사용하지 않는 이유는 바로 이것입니다. 나뿐만 아니라 구현 가능한 Windows 용이 확신, 또는 PHP 간단하게 호환되는이 또한 PHP-CLI 및 혼합 내에서 잘 작동 아파치 스레딩 환경에서

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 
    } 
} 

. 프로세스가 예기치 않게 종료되는 경우 세마포어가 유효하지 않으며 다시 수집 할 수 없습니다. 세마포어는 "원자"로 구현되어 잠금 중 경쟁 조건을 방지합니다.

A nice description based on toilets is here

0

현대 2017 년 답 :

는 PHP에서 잠금을 구현하는 방법에는 여러 가지가 있습니다.

  • 벙어리 파일 존재 여부 확인, 일명 "초보자의 첫 아기 잠금 시도"그냥 파일을 생성하고,이 완료되면 다음을 삭제합니다. 다른 모든 인스턴스는 존재하는지 확인합니다. 이것은 완료 될 때 (예 : 전원이 끊어 지거나 스크립트가 강제 종료 될 때) 파일이 삭제되지 않을 위험이 높으므로 모두 이후 스크립트 실행이 실패 함을 의미합니다. 또한 파일이 누락되어 있고 독점적으로 파일을 만들려고 시도하는 여러 개의 동시 시작 인스턴스로 인해 어려움을 겪습니다. 끔찍한입니다.
  • 파일에 프로세스 ID 쓰기 : 위의 방법을 약간 개선했습니다. 그러나 여전히 매우 해키하고 경쟁 조건으로 어려움을 겪고 있습니다. 현재 PHP 인스턴스의 프로세스 ID를 텍스트 파일에 기록한 다음 다른 모든 인스턴스가 해당 파일의 내용을 읽고 해당 프로세스 ID가 여전히 존재하고 PHP 프로세스인지 확인한 후 프로세스가 "잠긴"것으로 간주합니다. . 두 스크립트가 서로 매우 가까이서 시작하고 모두이 동일한 텍스트 파일의 내용을 읽고 모두 일 경우 이전 PHP 프로세스 ID가 더 이상 실행되지 않고 모두은 독점 잠금이 있다고 생각합니다. 이 방법은 모두 비용으로 피해야합니다. 겨우의 기본 Bash 쉘 스크립트 (다른 ​​방법은 사용할 수 없음)에도 사용할 수 있지만 PHP는 보다 훨씬 더 진보 된 잠금 방법을 사용할 수 있습니다.
  • 소켓 : 로컬 포트에 바인딩합니다. 포트가 사용 중이면 "잠긴"것으로 간주하십시오. 장점 : 잠금 파일이 필요하지 않습니다. 단점 : 시스템 자체에서 포트를 사용하고 있거나 시스템의 구성에 따라 프로세스가 포트 바인딩을 수행 할 수 없거나 파일을 잠그는 것보다 훨씬 느립니다 (OS의 전체 소켓 시스템을 호출하고 소켓을 생성하므로).
  • 세마포어 : 매우 빠르고 안정적이지만 Unix-only (Posix).
  • 독점적 인 파일 잠금 : 이것은 크로스 플랫폼 (유닉스, 리눅스, 맥, 윈도우), 매우 안정적이고 빠릅니다. 이것이 제가 아래에 깔끔하고 확실하게 구현 한 것입니다.

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 : 당신이 돈 경우는, 테스트 스크립트에서 FALSE로 TRUE를 변경할 수 있습니다

<?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 : 잠금을 사용할 수있을 때까지 기다립니다. 모든 일자리를 운영하기를 원하지만 모든 일이 독점적으로 돌아갈 때까지 기다려야 만합니다.
  • 거짓 : 잠금을 사용할 수없는 경우 기다리지 마십시오. 인스턴스가 이미 실행중인 경우 다른 스크립트를 중단하려는 경우 유용합니다.
0

또는 LOCK 파일을 사용할 수 있습니다. 아이디어는 간단하다 : 스크립트 (S)가 실행되면, 먼저 특정 (독특한) 파일의 존재를 확인합니다, S.lock 말 : 파일이 존재

  • 경우, S가 종료됩니다.

  • 그렇지 않으면 생성됩니다. S가 종료되면 파일이 삭제됩니다.

관련 문제