Facebook
Twitter
Google+
Kommentare
0

Mehrere Scripte via Cronjob parallel aufrufen

Heute ein kleines Problemchen mit Cronjobs und Parallelität. Normalerweise empfehle ich Gearman wenn es darum geht, mehrere Scripte im Hintergrund laufen zu lassen, aber nehmen wir an dass wir es mit normalen Cronjobs ohne Gearman lösen wollen.

Wir haben also das Script work.php. Wir möchten alle 5 Minuten die Datenbank prüfen ob es Arbeit gibt, und wenn dem so ist, dann soll die Arbeit erledigt werden. Das geht relativ einfach mit einem Cronjob

*/5 * * * * /usr/bin/php /data/work.php

und in der work.php findet dann die Datenbankabfrage statt. Wenn X Ergebnisse in der Datenbank gefunden werden, wird eine Schleife X mal durchlaufen um alles abzuarbeiten. So weit so gut.

Nun sei die eigentliche Arbeit aber relativ zeitaufwändig, sodass ein Schleifendurchlauf 2 Minuten dauert, und bei 5 Aufträgen dauert es also 10 Minuten (wir arbeiten ja seriell in einer Schleife), der Aufruf von work.php überlappt und wir bekommen ein Problem. Angenommen die Aufgabe ist parallelisierbar, d.h. wenn man alle 5 Aufgaben zeitgleich starten würde gäbe es keine Probleme, und die ganze Arbeit wäre nach 2 Minuten erledigt. Wir könnte man soetwas einfach realisieren?

Wir teilen einfach das Script in 2 Teile. Das erste Script dispatcher.php befragt die Datenbank, und startet dann weitere PHP Prozesse parallel, die die eigentliche Arbeit erledigen. Wenn nur 2 Aufgaben anstehen werden 2 Prozesse gestartet, bei 15 Aufgaben sind es 15 Prozesse. Der Cronjob sähe so aus:

*/5 * * * * /usr/bin/php /data/dispatcher.php

Der Dispatcher:

<?php

// connect to database
$dbhandle = mysql_connect('mysqlserver', 'username', 'pass');
$db = mysql_select_db('App1', $dbhandle);

// get work
$result = mysql_query('SELECT id, data FROM work');

while ($row = mysql_fetch_array($result)) {
    $param = escapeshellarg($row['data']);
    exec('/usr/bin/php /data/work.php '. $param .' >> /var/log/worker.out &');
    mysql_query("DELETE FROM work WHERE id=".$row['id']);
}

(Beim Schreiben dieses Quelltextes ist mir aufgefallen dass ich schon lange keine direkten mysql_* Funktionen mehr benutzt habe, Zend Framework sei Dank…)

Man beachte das angehängte &, das den Befehl gibt, in den Hintergrund zu verschwinden. Und die work.php nimmt einfach den ihr übergebenen Parameter und erledigt die Arbeit:

<?php

$data = $argv[1];

// start to work here
echo $data."\n";
sleep(10);
echo $data."\n";

In der Logdatei sieht man dass die fünf work.php parallel laufen:

data1
data2
data3
data4
data5
data1
data2
data3
data4
data5

Man könnte auch Kindprozesse forken mit den pcntl_* Funktionen, aber die sind nicht überall verfügbar. Falls man übrigens vorher keine Datenbank befragen muss und weiß, was und wieviele Arbeiten parallel erledigt werden sollen, kann man natürlich auch einfach X Crontab-Einträge machen. So in der Art:

*/5 * * * * /usr/bin/php /data/work.php 1
*/5 * * * * /usr/bin/php /data/work.php 2
*/5 * * * * /usr/bin/php /data/work.php 3
*/5 * * * * /usr/bin/php /data/work.php 4
*/5 * * * * /usr/bin/php /data/work.php 5

oder aber ein Wrapper-Script, dann hat man nur einen Crontab-Eintrag:

*/5 * * * * /usr/bin/php /data/wrapper.php

und in diesem Fall ein PHP Script, das die Prozesse startet. Es könnte natürlich auch ein Shellscript sein.

<?php

exec("/usr/bin/php /data/work.php 1 >> /var/log/php.out &");
exec("/usr/bin/php /data/work.php 2 >> /var/log/php.out &");
exec("/usr/bin/php /data/work.php 3 >> /var/log/php.out &");
exec("/usr/bin/php /data/work.php 4 >> /var/log/php.out &");
exec("/usr/bin/php /data/work.php 5 >> /var/log/php.out &");

Es gibt also viele Wege ans Ziel, es muss für einfache Aufgaben nicht immer gleich eine Gearman-Umgebung sein.

flattr this!

Über den Autor

PHP Gangsta

Der zweitgrößte deutsche, eher praxisorientierte PHP-Blog von Michael Kliewe veröffentlicht seit Mitte 2009 Artikel für Fortgeschrittene.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen