Artikelformat

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

In den letzten Beiträgen wurde der Gearman installiert, die PHP Extension installiert und es gab 2 Beispiele zu bestaunen, wie man von einem Client einen Job an einen Worker übergibt. Heute geht es um die Tasks und wie diese verwendet werden. Und natürlich gibt es wieder ein Beispiel.

Der Client kann neben der do-Methode Aufgaben auch mit addTask an den Gearman-Server übergeben. Hierbei verhält es sich so, dass man Tasks erst sammeln kann, bevor diese zur Ausführung gelangen. Dadurch können einige Aufgaben parallel ausgeführt werden. Jetzt fragt man sich natürlich, wo denn hier der Vorteil gegenüber einer Ausführung mit doBackground ist. Vom Client aus gesehen sieht beides relativ ähnlich aus, aber mit addTask kann man auch die Ergebnisse aller Tasks erhalten. DoBackground hatte hierfür ja eine wiederholte Anfrage am Job-Server erfordert, addTask arbeitet ganz geschickt mit Callbacks.

Dadurch kann man gerne ein paar Aufgaben in einem Client sammeln, diese parallel verarbeiten lassen und auf jedes Ergebnis individuell reagieren. Um die Individualität zu erreichen sollte man einen unique identifier spezifizieren. Dieser wird dem Task übergeben und somit kann später der Client die Antwort einem Task wieder zuordnen.

Beispiel 3:
In dem Beispiel gehen wir nun so vor, dass wie gewohnt eine Instanz erzeugt wird und diese mit dem Server verbunden wird. Dann werden 2 Callbacks mit dem Client verknüpft. Ich habe mich hier für den Complete und den Status-Callback entschieden. Diese werden mit setCompleteCallback bzw setStatusCallback verknüpft. die Callback-Funktionen dürfen wieder PHP-typisch definiert werden. Die Funktion complete gibt einfach den unique identifier und das Ergebnis der Berechnung aus und der Status entspricht dem Beispiel aus Teil 2. Hier wird der Prozentsatz der Fertigstellung unserer Berechnung ausgegeben. Da mit mehr als einem Task gearbeitet wird, gibt es auch hier den unique identifier. Jetzt werden auch 2 Tasks dem Client übergeben. Beide nutzen wie schon im Teil 2 die „upper“-Methode. Der Kontext ist in diesem Fall null und die Identifier sind einfach die Zahlen 1 und 2.

Die Tasks werden natürlich nicht einfach von alleine ausgeführt und darum muss explizit die runTasks-Methode des Clients aufgerufen werden. Der gesamte Code sieht in PHP so aus:

$client = new GearmanClient();
$client->addServer();
$client->setCompleteCallback("complete");
$client->setStatusCallback("status");
$client->addTask("upper", "Visit PHPMonkeys.de", null, 1);
$client->addTask("upper", "Hello World!", null, 2);

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

$client->runTasks();

function complete(GearmanTask $task) {
    echo "Complete (" . $task->unique() . ") with " . $task->data() . "\n";
}

function status(GearmanTask $task) {

    $num = $task->taskNumerator();
    $den = $task->taskDenominator();

    echo "Progress (" . $task->unique() . "): ";
    echo number_format((($den > 0) ? $num * 100 / $den : 100),2) . "%\n";
}

Kommen wir nun zum Worker. Der ist schon bekannt, da es sich um den Worker aus Beispiel 2 von letzter Woche handelt. Interessant ist jetzt aber wie die Tasks abgearbeitet werden. Ruft man den Client einfach auf, dann wird erst ein Task und dann der zweite erledigt. Nichts von Parallelisierung zu sehen. Der Trick hier ist nun, dass man soviele Worker benötigt, wie man Tasks parallelisieren möchte. In unserem Beispiel sind 2 Worker ausreichend. Also wird der bekannte Worker einfach ein zweites Mal gestartet und schon werden die beiden Tasks parallel bearbeitet.
Konzeptionell bedeutet dies nun für den Entwickler, dass der Client eine Aufgabe erfüllen soll. Diese wird in mehrere Tasks aufgeteilt und diese werden dann dem Job-Server übergeben, der – wenn möglich – diese Tasks parallel durch Worker erledigen lässt. Die Ergebnisse führt der Client wieder zusammen und man kann somit rechenintensive Aufgabe schneller verarbeiten.

Beispiel 4:
Nun kommen wir zu dem oben angesprochenen Kontext. Diesen kann man nutzen um einfach Daten direkt weiterzuverarbeiten. Als Beispiel habe ich einen Aggregator für die beiden Strings angedacht. Sicher nicht das beste Beispiel, aber das Prinzip sollte dadurch klar werden. Der Worker bleibt wiederum unangetastet und der Client wird nun geändert. Da uns der Status im Moment nicht interessiert, kann man den Status-Callback entfernen. Als Neuerung wird die Klasse Aggregator definiert, die eine Methode zum Hinzufügen eines Strings und eine zum Auslesen des Ergebnisses hat. Die Ausgabe entspricht den konkatenierten Eingabewerten mit einem “ and „ dazwischen.

Man erzeugt sich nun eine neue Instanz dieser Klasse und gibt diese als Kontext bei addTask mit. Zu beachten ist hierbei, dass Klassenobjekte by-reference übergeben werden. Wollte man ein bspw ein Array übergeben, muss man daran denken die Referenz auf dieses Array im addTask-Aufruf zu nutzen. Den unique identifier lassen wir auch weg, da wir damit nichts tun. Nach dem Aufruf von runTasks wird aus dem Aggregator noch das Ergebnis ausgelesen und auf die Kommandozeile geschrieben. Dieser Code sieht so aus:

$client = new GearmanClient();
$client->addServer();
$client->setCompleteCallback("complete");

$myResults = new Aggregator();

$client->addTask("upper", "Visit PHPMonkeys.de", $myResults);
$client->addTask("upper", "Hello World!", $myResults);

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

$client->runTasks();

echo $myResults->getResult() . "\n";

function complete(GearmanTask $task, $context) {
    $context->add($task->data());
}

class Aggregator {

    private $data = array();

    public function add($input) {
        $this->data[] = $input;
    }

    public function getResult() {
        return join(' and ', $this->data);
    }

}

Der Kontext liefert eine weitere Herangehensweise um die Tasks zusammen zu führen.

Fazit:
Gearman ist ein sehr spannendes Framework. Obwohl die Funktionsweise doch sehr überschaubar ist, gibt es viele Anwendungsfälle, die sich durch die API abbilden lassen.

1 Kommentar

Schreibe einen Kommentar

Pflichtfelder sind mit * markiert.