2010-11-10 14 views
8

Ich habe ein Cron-Skript, das alle 10 Minuten ein PHP-Skript ausführt. Das Skript überprüft eine Warteschlange und verarbeitet die Daten in der Warteschlange. Manchmal verfügt die Warteschlange über genügend Daten, um über 10 Minuten der Verarbeitung zu dauern, wodurch das Potenzial von zwei Skripts entsteht, die versuchen, auf dieselben Daten zuzugreifen. Ich möchte feststellen können, ob das Skript bereits ausgeführt wird, um zu verhindern, dass mehrere Kopien des Skripts gestartet werden. Ich dachte darüber nach, ein Datenbank-Flag zu erstellen, das besagt, dass ein Skript verarbeitet wird, aber wenn das Skript jemals abstürzen würde, würde es im positiven Zustand bleiben. Gibt es eine einfache Möglichkeit festzustellen, ob das PHP-Skript bereits mit einem PHP- oder Shell-Skript ausgeführt wird?Wie erkennt man, ob ein PHP-Skript bereits läuft?

+2

vielleicht etwas mit '.pid' Dateien? – drudge

+0

ja, in error_handler hinzufügen, um die pid zu löschen, und nach getan, den Job löschen Sie die pid – ajreal

+0

Verwenden Sie, was Sie wollen, IMHO gibt es keine crash-freie Lösung. Verwenden Sie Semaphoren? .pid Dateien? Sie alle werden im "positiven" Zustand gelassen, sollte es zu einem schweren Unfall kommen. Aber einige von ihnen sind robuster als andere. –

Antwort

5

Wenn Sie es absolut crashsicher benötigen, sollten Sie semaphores verwenden, die automatisch freigegeben werden, wenn php die spezifische Anforderungsbehandlung beendet.

Ein einfacherer Ansatz wäre, einen DB-Datensatz oder eine Datei zu Beginn der Ausführung zu erstellen und am Ende zu entfernen. Sie können immer das "Alter" dieses Datensatzes/Datei überprüfen, und wenn es älter ist als sagen 3 Mal die normale Skriptausführung, angenommen, dass es abgestürzt ist und es entfernen.

Es gibt keine "Wunderwaffe", es kommt nur auf Ihre Bedürfnisse an.

+0

... Ich glaube nicht, dass Semaphore-Erweiterungen für Win – Milan

3

Ein gängiger Weg für * nix-Daemons (obwohl nicht unbedingt PHP-Skripte, aber es wird funktionieren) ist die Verwendung einer .pid-Datei.

Wenn das Skript startet, prüfen Sie, ob eine .pid-Datei existiert, die nach dem Skript benannt ist (normalerweise in/var/run/gespeichert). Wenn es nicht existiert, erstellen Sie es, indem Sie seinen Inhalt auf die PID des Prozesses setzen, der das Skript ausführt (unter Verwendung von getmypid), und fahren Sie mit der normalen Ausführung fort. Wenn es existiert, lesen Sie die PID aus und sehen Sie, ob dieser Prozess noch läuft, wahrscheinlich indem Sie ps $pid ausführen. Wenn es ausgeführt wird, beenden Sie es. Andernfalls überschreiben Sie den Inhalt mit Ihrer PID (wie oben) und fahren mit der normalen Ausführung fort.

Wenn die Ausführung beendet ist, löschen Sie die Datei.

+2

verfügbar sind. Ich denke, das wird wahrscheinlich auf einem typischen * nix OS gut funktionieren, solange die Zeitlücke zwischen den Läufen Ihres Daemons bescheiden ist, aber hüte dich davor, dass [PIDs recycelt werden ] (http://unix.stackexchange.com/questions/26677/will-process-ids-be-recycled-what-i-f-you-reach-the-maximal-id) und deshalb möchten Sie sie möglicherweise nicht als verwenden Teil eines Schließsystems. Es gibt zumindest einen theoretisch möglichen Fehlermodus, in dem dein Daemon abstürzt, ein völlig unabhängiger lang andauernder Prozess die gleiche PID nimmt und nachfolgende Starts deines Daemons alle fälschlicherweise glauben, dass er bereits läuft und ihn für immer tot lässt. –

3

Ich weiß, das ist eine alte Frage, aber falls jemand anderes hier sucht, werde ich etwas Code posten. Dies habe ich kürzlich in einer ähnlichen Situation gemacht und es funktioniert gut. Setzen Sie diesen Code an den Anfang Ihrer Datei, und wenn das gleiche Skript bereits ausgeführt wird, wird es belassen und das neue beenden.

Ich verwende es, um ein Überwachungssystem zu jeder Zeit laufen zu lassen. Ein Cron-Job startet das Skript alle 5 Minuten, aber wenn der andere nicht aus irgendeinem Grund gestoppt wurde (normalerweise wenn er abgestürzt ist, was sehr selten ist!), Wird der neue sich selbst verlassen.

// The file to store our process file 
define('PROCESS_FILE', 'process.pid'); 

// Check I am running from the command line 
if (PHP_SAPI != 'cli') { 
    log_message('Run me from the command line'); 
    exit; 
} 

// Check if I'm already running and kill myself off if I am 
$pid_running = false; 
if (file_exists(PROCESS_FILE)) { 
    $data = file(PROCESS_FILE); 
    foreach ($data as $pid) { 
     $pid = (int)$pid; 
     if ($pid > 0 && file_exists('/proc/' . $pid)) { 
      $pid_running = $pid; 
      break; 
     } 
    } 
} 
if ($pid_running && $pid_running != getmypid()) { 
    if (file_exists(PROCESS_FILE)) { 
     file_put_contents(PROCESS_FILE, $pid); 
    } 
    log_message('I am already running as pid ' . $pid . ' so stopping now'); 
    exit; 
} else { 
    // Make sure file has just me in it 
    file_put_contents(PROCESS_FILE, getmypid()); 
    log_message('Written pid with id '.getmypid()); 
} 

Es wird nicht ohne Änderung unter Windows funktionieren, sollte aber in Unix-basierten Systemen in Ordnung sein.

+0

-1 aus dem gleichen Grund wie Brentons Antwort oben; Da die meisten Unixes PIDs recyceln, bedeutet die Tatsache, dass es einen laufenden Prozess gibt, dessen PID mit demjenigen übereinstimmt, der durch eine vorherige Inkarnation Ihres Skripts in Ihre PID-Datei geschrieben wurde, NICHT, dass es tatsächlich Ihr Skript ist, das noch läuft. Wenn Ihr Skript abstürzt und dann ein nicht verwandter, lang laufender Prozess gestartet wird und die PID übernimmt, die Ihr Skript vor dem Absturz verwendet hat, wird es sich beim nächsten Ausführen des Skripts fälschlicherweise selbst ausschalten. Dein Skript könnte für immer tot bleiben! Verwenden Sie 'flock()' stattdessen, um sich vor diesem Randfall zu schützen. –

+0

Dies funktioniert auch bei Windows, Sie müssen den '/ proc /' - Haken entfernen. – Radon8472

0

Das funktionierte für mich. Legen Sie einen Datenbankdatensatz mit einem Sperrkennzeichen und einem Zeitstempel fest. Mein Skript sollte auch innerhalb 15 Minuten vervollständigen, so dass als letztes Mittel gesperrt Feild zu überprüfen:

 $lockresult = mysql_query(" 
     SELECT * 
     FROM queue_locks 
     WHERE `lastlocked` > DATE_SUB(NOW() , INTERVAL 15 MINUTE)  
     AND `locked` = 'yes' 
     AND `queid` = '1' 
     LIMIT 1 
     "); 
$LockedRowCount = mysql_num_rows($lockresult); 

if($LockedRowCount>0){ 
    echo "this script is locked, try again later"; 
    exit; 
}else{ 
//Set the DB record to locked and carry on son 
$result = mysql_query(" 
      UPDATE `queue_locks` SET `locked` = 'yes', `lastlocked` = CURRENT_TIMESTAMP WHERE `queid` = 1; 
      "); 

} 

Dann entsperren es am Ende des Skripts:

$result = mysql_query("UPDATE `queue_locks` SET `locked` = 'no' WHERE `queid` = 1;"); 
24

Sie können nur eine Sperrdatei verwenden. PHP flock() Funktion bietet einen einfachen Wrapper für Unix flock Funktion, die beratende Sperren für Dateien bietet.

Wenn Sie sie nicht explizit freigeben, gibt das Betriebssystem diese Sperren automatisch für Sie frei, wenn der Prozess, der sie enthält, beendet wird, auch wenn es abnormal beendet wird.

Sie können auch der lockeren Unix-Konvention folgen, Ihre Sperrdatei zu einer 'PID-Datei' zu machen - das heißt, nachdem Sie eine Sperre für die Datei erhalten haben, lassen Sie Ihr Skript seine PID schreiben. Selbst wenn Sie dies nie in Ihrem Skript lesen, ist es für Sie praktisch, wenn Ihr Skript jemals hängt oder verrückt wird und Sie seine PID finden möchten, um sie manuell zu beenden.

Hier ist ein copy/paste-ready Umsetzung:

#!/usr/bin/php 
<?php 

$lock_file = fopen('path/to/yourlock.pid', 'c'); 
$got_lock = flock($lock_file, LOCK_EX | LOCK_NB, $wouldblock); 
if ($lock_file === false || (!$got_lock && !$wouldblock)) { 
    throw new Exception(
     "Unexpected error opening or locking lock file. Perhaps you " . 
     "don't have permission to write to the lock file or its " . 
     "containing directory?" 
    ); 
} 
else if (!$got_lock && $wouldblock) { 
    exit("Another instance is already running; terminating.\n"); 
} 

// Lock acquired; let's write our PID to the lock file for the convenience 
// of humans who may wish to terminate the script. 
ftruncate($lock_file, 0); 
fwrite($lock_file, getmypid() . "\n"); 

/* 
    The main body of your script goes here. 
*/ 
echo "Hello, world!"; 

// All done; we blank the PID file and explicitly release the lock 
// (although this should be unnecessary) before terminating. 
ftruncate($lock_file, 0); 
flock($lock_file, LOCK_UN); 

einfach den Pfad der Sperrdatei auf, wo immer Sie möchten und Sie werden eingestellt.

+1

Dies sollte die beste Antwort sein. Es macht einfach Sinn. [Symfony 2.6 LockHandler] (http://symfony.com/blog/new-in-symfony-2-6-lockhandler) implementiert auch Sperren mittels Dateisperre. –

+0

Per docs: Auf Versionen von PHP vor 5.3.2 wird die Sperre auch von fclose() freigegeben (die auch automatisch aufgerufen wird, wenn das Skript beendet ist). 5.3.2 \t Die automatische Entsperrung beim Schließen des Ressourcenhandles der Datei wurde entfernt. Das Entsperren muss jetzt immer manuell erfolgen. –

+1

@JustinMcAleer PHP wird nicht automatisch die Sperren für Sie beim Schließen eines Datei-Handle freigeben, aber das Betriebssystem wird immer noch (zumindest unter Unix-Betriebssystemen); Diese manuelle Passage ist grundsätzlich falsch. Und selbst wenn nicht, wird jedes OS hoffentlich die Sperre freigeben, sobald Ihr Prozess beendet ist; Andernfalls wäre es ziemlich kaputt. –

3

können Sie neue verwenden Symfony 2.6 LockHandler

$lock = new LockHandler('update:contents'); 
if (!$lock->lock()) { 
    echo 'The command is already running in another process.'; 
} 
+0

Könnten Sie einen Link, wo ich die einzige LockHandler-Klasse ohne den Rest von Synfony herunterladen können? – Radon8472

+1

https://github.com/symfony/filesystem/blob/master/LockHandler.php – Nicklasos

-1

Ich weiß, dass dies eine alte Frage, aber es ist ein Ansatz, der vor, dass ich glaube nicht erwähnt worden ist, eine Überlegung wert ist.

Eines der Probleme mit einer Lockfile oder Datenbank Flag Lösung, wie bereits erwähnt, ist, dass wenn das Skript aus einem anderen Grund als normale Beendigung fehlschlägt die Sperre nicht freigegeben wird. Daher wird die nächste Instanz nicht gestartet, bis die Sperre entweder manuell gelöscht oder durch eine Bereinigungsfunktion gelöscht wurde.

Wenn Sie jedoch sicher sind, dass das Skript nur einmal ausgeführt werden soll, ist es relativ einfach, innerhalb des Skripts zu überprüfen, ob es beim Starten bereits ausgeführt wird. Hier einige Code:

function checkrun() { 
    exec("ps auxww",$ps); 
    $r = 0; 
    foreach ($ps as $p) { 
     if (strpos($p,basename(__FILE__))) { 
      $r++; 
      if ($r > 1) { 
       echo "too many instances, exiting\n"; 
       exit(); 
      } 
     } 
    } 
} 

Rufen Sie einfach diese Funktion zu Beginn des Skripts, bevor Sie irgendetwas anderes tun (wie zB offene Datenbank-Handler oder eine Importdatei verarbeiten), und wenn das gleiche Skript bereits läuft dann Es erscheint zweimal in der Prozessliste - einmal für die vorherige Instanz und einmal für diese. Also, wenn es mehr als einmal erscheint, einfach verlassen.

Ein potenzieller Fehler hier: Ich gehe davon aus, dass Sie nie zwei Skripte mit dem gleichen Basisnamen haben werden, die möglicherweise gleichzeitig laufen (zB das gleiche Skript läuft unter zwei verschiedenen Benutzern). Wenn dies möglich ist, müssen Sie die Überprüfung auf etwas komplizierteres erweitern als eine einfache Teilzeichenfolge für den Basisnamen der Datei. Aber das funktioniert gut genug, wenn Sie eindeutige Dateinamen für Ihre Skripte haben.

+0

-1 nicht für den Ansatz, den Sie hier vorschlagen, sondern für die Behauptung, dass Unix-Dateisperren nicht freigegeben werden, wenn der Prozess abnormal beendet wird. Das ist falsch. Siehe http://stackoverflow.com/questions/12651068/release-of-lock-in-case-of-errors für eine Demonstration von diesem. –

4

Wenn Sie Linux läuft, sollte dies am Anfang des Skripts arbeiten:

$running = exec("ps aux|grep ". basename(__FILE__) ."|grep -v grep|wc -l"); 
if($running > 1) { 
    exit; 
} 
+1

Ich mag die Einfachheit dieser Lösung. Gibt es irgendwelche Nachteile? Auch wenn jemand anderes es benutzen möchte - wenn Sie es in Crontab verwenden, dann überprüfen Sie, ob die Nummer mehr als 2 statt 1 ist. – LauriK

+0

@LauriK, je nach Ihrer Frage. Das erste Skript wurde möglicherweise intern gestoppt, was aus dem zweiten Skript nicht ersichtlich ist, wenn die PID noch ausgeführt wird. Mit etwas anderem könnte dem zweiten Skript helfen, das erste Skript zu beenden und von vorne zu beginnen. – Xeoncross

-1

Fügen Sie einfach folgende am Anfang des Skripts.

<?php 
    // Ensures single instance of script run at a time. 
    $fileName = basename(__FILE__); 
    $output = shell_exec("ps -ef | grep -v grep | grep $fileName | wc -l"); 
    //echo $output; 
    if ($output > 2) 
    { 
     echo "Already running - $fileName\n"; 
     exit; 
    } 

    // Your php script code. 
?> 
+0

-1; das ist im Grunde die selbe Lösung wie http://stackoverflow.com/a/33045344/1709587, aber hässlicher (warum das auskommentierte Echo? Auch das enge '?> 'tag verletzt PSR-2) und, soweit ich begründen kann, kaputt; Warum checkst du nach '$ output> 2' anstelle von' $ output> 1', wenn du deine grep-Prozesse herausgefiltert hast? Die einzigen Zeilen, die hier an "wc" übergeben werden, sollten tatsächliche Instanzen Ihres Skripts sein, also sollte der Check "> 1' sein, nicht'> 2'. (Zugegebenermaßen habe ich nicht getestet, um dies zu bestätigen.) –

0

Ich habe dies versucht. Funktioniert auch in Windows gut.

$status_file=__DIR__.'/mail_process.txt'; 
    if(file_exists($status_file) && file_get_contents($status_file)=='running'){ 
       echo 'Program running'; 
      exit; 
    } 
    file_put_contents($status_file,'running'); 

     // Code block 
     //........... 

    file_put_contents($status_file,'finished'); 

Danke an @martinodf für seine Idee.

Verwandte Themen