Too Cool for Internet Explorer

gearman job queue server


Die letzten Wochen habe ich für die Arbeit einen Workflow Prozessor entwickelt, über den wir zukünftig unsere Medien Uploads verarbeiten werden. Unter anderem gehört zu seinen Aufgaben die Erzeugung von Still Images aus Videos, Wave- und Spektrum-Analyse aus Audiodateien, Erzeugung von Thumbnails und Previews für die Darstellung auf der Web Seite. Der Workflow Prozessor arbeitet von der Console bzw. als Hintergrundprozess (Dämon) und ist in PHP entwickelt. Wird er auf einem Server gestartet, kann der Hauptprozess beliebig viele Worker-Prozesse forken.

Ein Problem bei der Entwicklung war die Verteilung der Prozesse an die Worker. Hier habe ich zunächst auch auf eine reine PHP basierte Lösung gesetzt und die Verteilung der Aufgaben vom Hauptprozess übernehmen lassen, der über Socket-Pairs mit den Worker-Prozessen kommunizieren kann. Bei Tests während der Entwicklung im kleinen Rahmen und mit wenigen Jobs hat das auch wunderbar funktioniert. Bei umfangreicheren Tests traten dann allerdings die Probleme zu Tage.

Einmal abgesehen davon, dass die ganze Sache nicht wirklich der Performance-bringer war, war sie dank der direkten Kommunikation Client->Worker auch fehleranfällig und schlecht bzw. gar nicht skalierbar, denn wie soll man mit dieser Methode mehrere bzw. beliebige Worker-Server transparent ansprechen?

Zum Glück habe ich schon seit einiger Zeit gearman in meinen Lesezeichen. Gearman ist eine Serversoftware, die -- einfach ausgedrückt -- den Zweck erfüllt Jobs zu verteilen. Mögliche Einsatzgebiete sind z.b. die Verteilung von Jobs an Systeme, die besser für die jeweilige Aufgabe geeignet sind, die Lastverteilung auf beliebige Server oder um Jobs parallel abarbeiten zu können. Hierfür wird seitens gearman eine (persistente) Warteschlange implementiert, an die ein Client einen Job senden kann. Ein Worker kann sich einen Job aus der Warteschlange holen und diesen abarbeiten.

Gearman wurde ursprünglich von Brian Fitzpatrick (livejournal, memcached, ...) in Perl entwickelt und vor einiger Zeit von Brian Aker und Eric Day nach C portiert. Der Entwicklungsstand des C Zweiges von gearman ist seiner Version (0.3) zu schliessen noch relativ jung. Insgesamt aber scheint sich das ganze schon derart bewährt zu haben, dass gearman produktiv und unter hoher Last z.b. von digg und yahoo eingesetzt wird.

Zum erfolgreichen Kompilieren von gearman unter OSX muss sichergestellt sein, dass man eine aktuelle Version der libevent Bibliothek installiert hat. Danach lässt sich auch gearman problemlos unter OSX bauen und installieren. Sowohl für libevent wie auch für gearman funktioniert das wie üblich:

$ ./configure
$ make
$ make install

Anschliessend kann man gearman starten. Der Parameter "-d" bewirkt dabei, dass gearman als Hintergrundprozess losgelöst vom Terminal gestartet wird:

/usr/local/bin/gearman -d

Per default lauscht gearman auf Port 4370. Dies ist der offizielle Port, der von der IANA für gearman reserviert wurde.

Client/Worker APIs zur Kommunikation mit gearman gibt es bereits für C (enthalten im gearman Download), Perl, MySQL (als UDF) und PHP. Zum Ansprechen von gearman unter PHP gibt es ein pear package, net_gearman, sowie seit kurzem auch eine native Erweiterung für PHP, die allerdings noch experimentell ist und dementsprechend mit Vorsicht zu geniessen ist. Unter OSX konnte ich sie z.b. zwar einwandfrei kompilieren und installieren, die ersten Versuche scheiterten dann aber an segfaults. Nicht weiter tragisch, da das pear paket bisher einwandfrei seine Arbeit macht.

Eine Client Anwendung, die Jobs an den gearman Server übergibt, ist mit dem pear Paket schnell entwickelt:

#!/usr/bin/env php
<?php

require_once('Net/Gearman/Client.php');

$set = new Net_Gearman_Set();

function result($resp) {
    print_r($resp);
}

$files = array(
    '/Users/harald/Movies/00001.mov'
);

foreach ($files as $file) {
    print "adding task $file\n";
    
    $task = new Net_Gearman_Task('test', array(
        'filename'  => $file
    ));

//    $task->attachCallback('result');
    $set->addTask($task);
}

print "running set ...\n";

$client = new Net_Gearman_Client(array('127.0.0.1:4730'));
$client->runSet($set);

?>

Der erste Parameter beim Instanzieren der Klasse Net_Gearman_Task spezifiziert übrigens den Namen des Jobs, der vom Worker registriert werden muss, damit dieser entsprechende Jobs annehmen und verarbeiten kann. Normalerweise wartet der Client, bis alle Jobs abgearbeitet sind und kann wenn eine Callback Methode registriert wurde, mögliche Ergebnisse verarbeiten, die die Worker zurückliefern. Dieses Verhalten kann möglicherweise unerwünscht sein, z.b. wenn viele Jobs eingereicht werden und der Client während der Abarbeitung nicht blockiert sein soll. Hierfür implementiert die Net_Gearman_Task Klasse ein öffentliches Property:

public $type = self::JOB_NORMAL;

Um das Verhalten zu ändern, sind folgende Typen definiert:

  • Net_Gearman_Task::JOB_NORMAL -- Standardverhalten: Client blockiert während der Abarbeitung der Jobs
  • Net_Gearman_Task::JOB_BACKGROUND -- Client reicht Jobs an gearman weiter und blockiert nicht
  • Net_Gearman_Task::JOB_HIGH -- selbes Verhalten wie JOB_NORMAL, allerdings wird die queue übersprungen und ein Job kann sofort von einem Worker verarbeitet werden. Sinnvoll z.b. für höher priorisierte Jobs

Eine einfacher Klasse, die die Aufgabe 'test' implementiert, könnte folgendermassen aussehen:

<?php

class test extends Net_Gearman_Job_Common {
    function run($args) {
        print_r($args);
        
        print "sleeping 5 seconds ...\n";
        sleep(5);
        print "done!\n";
        
        return "OK!";
    }
}

?>

Der Worker selbst wird folgendermassen aufgebaut:

#!/usr/bin/env php
<?php

define('NET_GEARMAN_JOB_CLASS_PREFIX', '');
define('NET_GEARMAN_JOB_PATH', '.');

require_once('Net/Gearman/Worker.php');

$worker = new Net_Gearman_Worker(array('127.0.0.1:4730'));
$worker->addAbility('test');
$worker->beginWork();

?>

Normalerweise erwartet Net_Gearman Klassen, die Aufgaben implementieren unterhalb eines Verzeichnisses, das in der PEAR Verzeichnisstruktur angesiedelt ist. Weil man dies normalerweise nicht möchte, gibt es folgende Konstanten, die man mit eigenen Werten füllen kann um so alternative Verzeichnisse zu setzen:

// spezifiziert ein Prefix für den Namen der Klasse
define('NET_GEARMAN_JOB_CLASS_PREFIX', '');

// spezifiziert das Verzeichnis, in dem Klassen gesucht werden
define('NET_GEARMAN_JOB_PATH', '');

Unschön ist, dass für einzubindende Klassen Dateien mit folgendem Namensschema erwartet werden, z.b: test.php. Da ich meine Klassen immer z.B. -- test.class.php -- benenne, ist dies also leider eine kleine Unschönheit, mit der man offenbar leben muss.

Zum Abschluss sei noch zu sagen, dass gearman in den ersten Tests mit mehreren 1000 Jobs in der Queue seine Arbeit tadellos und sehr zufriedenstellend erledigt hat.

Empfohlene weiterführende Links:



Trackbacks

Keine Trackbacks

Kommentare
Ansicht der Kommentare: (Linear | Verschachtelt)

Wow, danke für den nützlichen Tipp! Auch wenn der Beitrag schon etwas älter ist wird er mir sehr nützlich sein :-)

Grüße
Fabian
#1 Fabian Becker (Link) am 03.06.2009 08:34 (Reply)

freut mich! wobei ich im moment auch noch beanstalkd evaluiere: http://xph.us/software/beanstalkd/

ab der kommenden version 1.4 soll das teil auch persistenz unterstützen.
#1.1 harald am 03.06.2009 08:59 (Reply)

Hi Harald,

Gibt es schon was neues? Ich bin ein wenig irritiert, dass in jeder Docu die ich zu Gearman finden kann, der Worker mit einer Endlosschleife am leben gehalten wird:

while (1) gearman_worker_work(&worker);

in deinem Beispiel ist dies allerdings nicht der Fall. Kannst du dazu was sagen?


Sonst sei noch angemerkt, Gearman soll bei Unternehmen wie LiveJournal, Yahoo! oder Digg eingesetzt werden. Digg soll unter anderem über 300.000 Jobs am Tag ohne Probleme über Gearman abwickeln.
#2 Pascal am 22.06.2009 08:53 (Reply)

hallo pascal,

ja, das ist so ... im beispiel verwende ich ja die PEAR bibliothek zum ansprechen von gearman und hier kümmert sich die methode beginWork um das laufverhalten des workers und darum, dass er lange genug am leben bleibt, bis die jobs abgearbeitet sind.

die einzige möglichkeit die dir alternativ bei PHP bleibt, wäre das ganze über die prozess-kontroll-funktionen als daemon zu implementieren.

übrigens habe ich mir sagen lassen, dass auch xing für verschiedene dinge auf gearman setzt.

gruss,
harald
#2.1 harald am 22.06.2009 11:42 (Reply)

Hi Harald,

Gibts denn mitlerweile schon ein Fazit? Leider findet man nicht all zu viel über Gearman.

Mit den Prozesskontroll Funktionen habe ich mich - als unstudierter - noch nie ausseinander setzen müssen. Kannst du mir eventuell mal deinen Dämonen zukommen lassen?
#2.1.1 Pascal am 22.06.2009 19:17 (Reply)

hi pascal,

leider ist mein daemon recht stark an das framework meiner firma gebunden, sodass er rausgelöst praktisch nicht nutzbar wäre. ich habe aber gehört, dass "sonic" ganz gut sein soll:

http://dev.pedemont.com/sonic/

fazit zu gearman reiche ich nach :-)

gruss,
harald
#2.1.1.1 harald am 23.06.2009 09:32 (Reply)

Hi Harald,

Danke für den Link. Wie setzt du den Worker ein? Als Deamon?
#2.1.1.1.1 Pascal am 23.06.2009 16:47 (Reply)

hi,

ja ... bei uns läuft der worker als deamon. bzw. haben wir mehrere maschinen, auf denen daemons laufen, die den gearman server abfragen. das funktioniert eigentlich recht gut.

für den sonic könntest du ein worker-plugin schreiben und dieses dann über sonic als daemon laufen lassen.

gruss,
harald
#2.1.1.1.1.1 harald am 25.06.2009 10:58 (Reply)


Kommentar schreiben

Umschließende Sterne heben ein Wort hervor (*wort*), per _wort_ kann ein Wort unterstrichen werden.
Standard-Text Smilies wie :-) und ;-) werden zu Bildern konvertiert.

Um maschinelle und automatische Übertragung von Spamkommentaren zu verhindern, bitte die Zeichenfolge im dargestellten Bild in der Eingabemaske eintragen. Nur wenn die Zeichenfolge richtig eingegeben wurde, kann der Kommentar angenommen werden. Bitte beachten Sie, dass Ihr Browser Cookies unterstützen muss, um dieses Verfahren anzuwenden.
CAPTCHA