Artikelformat

Jobs, Worker, Clients und der Gearman (Teil 2)

Letzte Woche wurden die Grundlagen von Gearman betrachtet und die Installation auf einem Debian System gezeigt. Heute schauen wir uns einige Beispiele an, die verschiedene Aspekte der PHP-API aufzeigen. Als Beispiel-Berechnung wird strtoupper benutzt. Sowohl Client auch als Worker sind in PHP implementiert. In einem Kommentar wurde bereits erwähnt, dass Client und Worker-Ebene auch in verschiedenen Sprachen programmiert werden kann.

Beispiel 1:
Zuerst erstellt man einen Worker. Hierzu erzeugt man sich eine Instanz des GearmanWorker-Objekts. Dieses verbindet man mit einem Server und verbindet eine Callback-Funktion mit einer Gearman-Funktion. Wie man eine Callback-Funktion nutzt, kennt man ja von verschiedenen PHP-Funktionen. Als Eingabe erhält die Funktion ein GearmanJob Objekt. der Workload kann aus dem Objekt gelesen werden, und wird per return nach der Bearbeitung mit strtoupper zurückgegeben.
Im Code sieht die Beschreibung so aus:

$worker = new GearmanWorker();
$worker->addServer();
$worker->addFunction("upper", "upper_fun");

while ($worker->work());

function upper_fun(GearmanJob $job) {
    return strtoupper($job->workload());
}

Nachdem alles definiert wurde kann man die While-Schleife sehen, die den Worker am Leben erhält.

Auf der anderen Seite benötigt man nun nur noch einen simplen Client. Dieser ist grundsätzlich gleich aufgebaut. Man instanziiert ein GearmanClient Objekt, verbindet es mit dem Server und ruft nur noch die do-Methode auf diesem Client auf. Diese Methode erwartet die Gearman-Funktion und den Workload.
Die do-Methode gibt nun den Wert zurück, den der Worker errechnet. D.h. wenn man im Client do aufruft, wird die Information (also welche Methode wird mit welchem Workload aufgerufen) an den Job-Server übergeben und dieser leitet die Anfrage an einen freien Worker weiter. Das Ergebnis wird vom Job-Server entgegen genommen und an den aufrufenden Client zurückgegeben. Der Aufruf erfolgt insgesamt synchron, also wird der Client solange blockiert, bis der Job-Server das Ergebnis zurückliefert.
Im Code sieht dies recht einfach aus:

$client = new GearmanClient();
$client->addServer();
echo $client->do("upper", "Visit PHPMonkeys.de") . "\n";

der erste Aufruf:
Wie startet man das ganze nun? Das ist ganz einfach. Der Worker funktioniert wie ein Dämon und läuft im Hintergrund. Dieser wird also ganz einfach so gestartet:

# php worker.php &
[1] 811

Wie man sehen kann wird die (zumindest bei mir) die PID rausgeschrieben und somit kann man den Prozess später mit einem kill beenden.
Der Client wird wie ein normales PHP-Skript auf der Kommandozeile gestartet. Also so:

# php client.php

Und schon wird der Worker aktiv und der Client kann das Ergebnis bald rausschreiben.

Beispiel 2:
Jetzt wollen wir das Beispiel etwas aufbohren und gleichzeitig noch weitere Funktionen kennenlernen. Hierfür wird der Worker angepasst und die schöne PHP-Funktion durch eine eigene Implementierung ersetzt. Dies ist nur deshalb notwendig, damit man während der Umwandlung des Strings noch Status-Meldungen senden kann. Würden wir eine komplexe Berechnung durchführen, so könnte man den Client über den Zustand informieren. Wir laufen so über den String und bauen zeichenweise das Resultat zusammen. Nach jedem Schritt warten wir auch noch 1 Sekunde. Somit wird die Operation weiter künstlich in die Länge gezogen.
Der wichtige Punkt ist die sendStatus-Methode der GearmanJob Klasse. Diese bekommt zwei Werte. Den Numerator (Zähler) und den Denominator (Nenner). Dadurch lässt sich bspw ein Prozentsatz berechnen wieweit ein Job fertiggestellt wurde. Wie die Werte verarbeitet werden ist die Aufgabe des Clients.
Insgesamt sieht der Worker nun so aus:

$worker = new GearmanWorker();
$worker->addServer();
$worker->addFunction("upper", "upper_fun");

while ($worker->work());

function upper_fun(GearmanJob $job) {

    $workload = $job->workload();
    $workload_size = $job->workloadSize();

    echo "Workload: $workload ($workload_size)\n";

    $result = "";

    for ($x = 0; $x < $workload_size; $x++) {
        echo "Sending status: " . ($x + 1) . "/$workload_size complete\n";
        $job->sendStatus($x + 1, $workload_size);
        $result .= strtoupper(substr($workload, $x, 1));
        sleep(1);
    }

    echo "Result: $result\n";

    return $result;
}

Interessant ist, das wir jetzt einfach den obigen Client nutzen könnten, denn der Worker ist austauschbar. Das eröffnet ganz spannende Aspekte im Deployment. Wir wollen aber auch einen neuen Client nutzen, der den Status verwendet und der auch noch einen weiteren Aspekt der Gearman-API nutzt.

Dieser Client übergibt den Job in den Hintergrund was an doBackground zu erkennen ist. Das bedeutet, dass die Aufgabe asynchron erfüllt wird. Der Client muss also aktiv nachfragen, wie der Fortschritt ist und ob die Aufgabe schon erfüllt wurde. Man könnte auch eine Fire-And-Forget-Aufgabe losschicken. Will man bspw einen Benutzer per Email informieren. Dadurch wird der Ablauf der Applikation beschleunigt, wenn man nicht auf den Mailserver warten muss.
Die doBackground-Methode gibt kein Ergebnis zurück, sondern ein Handle. Mit diesem kann man den Status beim Gearman erfragen. Diese Funktion nutzen wir in einer while-Schleife. In dem Beispiel gehen wir natürlich vom Happy-Path aus. Wir erhalten im Status einige Informationen in einem Array. Neben den im Worker angesprochenen Numerator und Denominator Werten gibt es noch einen booleschen Wert, der besagt, ob der Job überhaupt noch läuft und ob das Handle beim Job-Server überhaupt bekannt ist.

$status[0] // bekannt ja/nein
$status[1] // läuft gerade ja/nein
$status[2] // Numerator
$status[3] // Denominator

Zu beachten ist, dass der Denominator bei abgeschlossenen Jobs 0 ist, was nach „Division durch 0„-Fehlern schreit. Also hier aufpassen.

Damit wir nicht dauernd den Job-Server anfragen gibt es noch einen sleep von 2 Sekunden. Wenn der Job läuft wird der Fortschritt – wie angekündigt – als Prozentsatz ausgegeben. Dieser ist noch schön formatiert, was aber nicht der spannende Punkt an der Sache ist. Wenn der Job abgearbeitet wurde, vergißt der Job-Server das Handle und somit wird die While-Schleife beendet.
Im Code haben wir nun folgendes stehen:

$client = new GearmanClient();
$client->addServer();

$handle = $client->doBackground("upper", "Visit PHPMonkeys.de");

if ($client->returnCode() !== GEARMAN_SUCCESS) {
    die("Bad return code");
}

$done = false;
do {
    sleep(2);
    $status = $client->jobStatus($handle);
    if (!$status[0]) {
        $done = true;
    }
    
    $num = $status[2];
    $den = $status[3];
    $perc = 100;
    if ($den > 0) {
        $perc = $num * 100 / $den;
    }

    echo "Just finished " . number_format($perc, 2) . "%\n";
} while (!$done);

Wer genau aufpasst hat auch schon den ReturnCode entdeckt. Dieser gibt zurück, ob der Job an den Server übergeben werden konnte – in unserem Beispiel.

Die beiden neuen Komponenten werden wie im ersten Beispiel ausgeführt.

Soviel für heute und zu den Jobs. Im nächsten Teil schauen wir uns die Tasks an. Das Thema ist sehr spannend und führt den letzten Codestand noch einen Level weiter – wie ich finde.

Update [28.04.2011]:
Das in den Kommentaren erwöhnte Type-Hinting wurde eingefügt, thx an nk für den Hinweis

4 Kommentare

  1. function upper_fun($job) {
    return strtoupper($job->workload());
    }

    Wäre nicht wenigstens ein Type hint angebracht? Kann gar nicht hinsehen..

    Antworten
    • Einverstanden. Das übergebene Objekt sollte ja vom Typ GearmanJob sein. Ich ändere das gerne sobald ich an meinem Rechner sitze.

Schreibe einen Kommentar

Pflichtfelder sind mit * markiert.