Archive for the ‘Parallel’ tag
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?
Taugt Zend_Queue etwas?
In einem älteren Artikel schrieb ich bereits über asynchrone Aufgaben. Heute möchte ich Zend_Queue vorstellen, womit man Aufgaben speichern kann, die später erledigt werden sollen.
Eine solche Message-Queue hat zwei Aufgaben: Aufgaben sollen erstellt werden können inklusive Details was zu tun ist, sowie Möglichkeiten bereitstellen, diese Aufgaben später wieder auslesen zu können, um sie abzuarbeiten.
Diese Aufgaben können wir nun in verschiedenen Backends speichern, Zend_Queue bietet einige Adapter an, wie beispielsweise eine Datenbank, ZendPlatformJobQueue, Apache ActiveMQ und MemcacheQ (bald wohl auch Amazon SQS).
Diese asynchrone Abarbeitung hat viele Vorteile. Für den Webseiten-Besucher erscheint die Webseite schneller, da einige Aufgaben später (wenige Sekunden, vielleicht auch sehr viel später) abgearbeitet werden. Für uns als Entwickler und Betreiber des Services besteht die Möglichkeit, Aufgaben in lastarmen Zeiträumen zu erledigen, sie auf mehrere Server aufzuteilen, die Last von den Frontendservern zu nehmen usw. Beim ZendPlatformJobQueue-Adapter kann man Jobs auch priorisieren, zu bestimmten Zeiten erledigen lassen, Abhängigkeiten zwischen den Jobs definieren und einiges mehr.
Mit einer Queue können wir auch regelmäßige Aufgaben abarbeiten lassen, beispielsweise das Aufräumen der Datenbank jede Nacht um 3 Uhr, pausieren von Aufgaben und spätere Wiederaufnahme der Tätigkeit, oder das Aufschieben von Aufgaben um x Stunden, beispielsweise soll immer exakt 2 Stunden nach dem Eintreffen eines Ereignisses eine Email versendet werden, wenn der Nutzer bis dahin nicht auf der Webseite war. Das geht allerdings wie gesagt nicht mit jedem Adapter.
Allgemein lässt sich sagen, dass wir alles offline abarbeiten sollten was nicht für ein Feedback des Besuchers benötigt wird.
Ein gutes Beispiel wäre ein Forum. Wenn ein Besucher einen neuen Beitrag schreibt, gibt es viel zu tun: Der Beitrag muss in der Datenbank gespeichert werden, der neue Inhalt muss in den Index gepumpt werden, Themenabonenten müssen benachrichtigt werden, RSS-Feeds müssen aktualisiert werden, und am Ende soll dem Benutzer sein fertiger Beitrag gezeigt werden. Das schreit doch nach Parallelität bzw. Queuing:
Wie funktioniert nun Zend_Queue? Hier ein einfaches Beispiel, wo wir eine Datenbank als Backend nutzen und einen Job eintragen. Zuerst müssen wir die Tabellen anlegen, die dazu benötigt werden. Das entsprechende SQL-Script befindet sich unter Zend/Queue/Adapter/Db/mysql.sql
Die entsprechenden PHP-Scripte zum Eintragen von Jobs und zum Auslesen von Jobs sehen so aus:
initQueue.php
<?php include('Zend/Loader/Autoloader.php'); $autoloader = Zend_Loader_Autoloader::getInstance(); $config = array( 'name' => 'myqueue', 'driverOptions' => array( 'host' => 'localhost', 'port' => '3306', 'username' => 'root', 'password' => '', 'dbname' => 'Queue', 'type' => 'pdo_mysql' ), 'options' => array( // use Zend_Db_Select for update, not all databases // can support this feature. Zend_Db_Select::FOR_UPDATE => true ) ); // Create a database queue $queue = new Zend_Queue('Db', $config); $queue->createQueue('myqueue');
insert.php
<?php require('initQueue.php'); $queue->send(serialize(array( 'action' => 'new article', 'articleId' => 13)) );
receive.php
<?php require('initQueue.php'); $messages = $queue->receive(5); foreach($messages as $msg) { $jobInfo = unserialize($msg->body); // do the work var_dump($jobInfo); $queue->deleteMessage($msg); }
Die Datenbank unterstützt nur einfache Jobs, es ist also nur für einfache Entkopplung zu gebrauchen, aber hier soll es als Beispiel reichen. Es werden aktuell keine Prioritäten, Ausführungszeiten oder ähnliches unterstützt, es ist eine reine Message-Queue. Der ZendPlatformJobQueue-Adapter unterstützt beispielsweise diese Features, sodass darauf zurückgegriffen werden sollte bei komplexeren Aufgaben. Soweit ich das bisher gelesen habe wird die Job Queue aber nur im kostenpflichtigen ZendServer enthalten sein, nicht in der Community Edition. Das ist echt schade, denn von den Features her ist das echt spitze was da geboten wird.
Wer also eine einfache Message-Queue benötigt, kommt mit Zend_Queue super klar, wer mehr braucht, sollte den ZendPlatformJobQueue-Adapter und den ZendServer nutzen (und vorher kaufen), oder sich bei Projekten wie Gearman oder Dropr umschauen, die ich in naher Zukunft auch noch vorstellen möchte.