Category: Allgemein


Libevent Woes in PHP

Mai 20th, 2012 — 1:44pm

Seit ein paar Wochen arbeite ich zum Spaß an einem STOMP Client, der libevent benutzt um dann Listener einer Queue per Symfony Event Dispatcher zu benachrichtigen sobald neue Messages verfügbar sind. Dazu benutze ich die PHP bindings für libevent. Wer libevent nicht kennt: das ist eine Bibliothek in C geschrieben, die es einfacher macht mit Streams (z. B. Sockets) zu arbeiten, in dem man sich auf bestimmte Events bindet die auf dem Stream passieren (z. B. read/write/timeout) und dann von libevent das gewünschte callback aufgerufen wird. Intern beutzt libevent die für die Umgebung performanteste Polling Implementierung um nachzuschauen ob neue Daten verfügbar sind im Stream.

Grunsätzlich funktioniert die Extension super, ich musste nur (damals) die neueste Variante aus dem pecl svn installieren (kann man sich holen mit svn checkout http://svn.php.net/repository/pecl/libevent/trunk libevent-trunk), ansonsten wollte die Extension nicht mit PHP 5.4. kompilieren. Ist aber in der neueren Version gefixed.

Da der STOMP Client noch nicht fertig ist, gibts dazu später einen extra Artikel, aber ein paar Dinge sind mir beim arbeiten mit libevent aufgefallen, die ich gerne festhalten möchte.

Event loops are hard to kill

In diesem Beispiel pollen wir STDIN nach neuen Daten, nur um dann im read callback eine Exception zu werfen:

$base = event_base_new();
$eb = event_buffer_new(
    STDIN,
    function ($buf, $arg) use(&$base) {
        print 'read callback' . PHP_EOL;
        throw new Exception();
    },
    NULL,
    function ($buf, $what, $arg) {
        print 'error callback' . PHP_EOL;
    },
    $base
);

event_buffer_base_set($eb, $base);
event_buffer_enable($eb, EV_READ);

event_base_loop($base);

Da ich event loops ja aus node.js schon kenne, hätte ich gedacht, die Exception würde die event loop beenden und das script somit auch und den Stacktrace auf der Konsole anzeigen. Falsch. Es passiert nämlich gar nix:

$ php loop.php
test
read callback triggered
(hängt für immer)

Die Exception wird nirgends ausgegeben, gar nix. Möglicherweise ändert sich das in künfigen Versionen der PHP Extension, aber bis dahin hat mir nur geholfen die event loop explizit zu beenden und danach die Exception zu werfen (hier der geänderte Aufruf für einen neuen buffered event):

$eb = event_buffer_new(
    STDIN,
    function ($buf, $arg) use(&$base, &$eb) {
        print 'read callback' . PHP_EOL;
        // loop beenden, cleanup macht die extension zum Glück für uns
        event_base_loopbreak($base);
        throw new Exception();
    },
    NULL,
    function ($buf, $what, $arg) {
        print 'error callback' . PHP_EOL;
    },
    $base
);

Error handling

Die PHP Extension gleicht vom Abstraktionslevel der C Implementierung von libevent ziemlich genau, nur das cleanup/freigeben von Resourcen bleibt einem erspart. Ansonsten kann man sich genauso wie in C mit Fehlercodes rumplagen:

$some_remote_socket = fsockopen(...);

$base = event_base_new();
$eb = event_buffer_new(
    $some_remote_socket,
    function ($buf, $arg) {
        // ...
    },
    NULL,
    function ($buf, $what, $arg) {
        // $what enthält den Fehlercode
       if ($what & EVBUFFER_EOF) print 'unerwartetes EOF' . PHP_EOL;
       else if ($what & EVBUFFER_ERROR) print 'allgemeiner fehler (sehr hilfreich, ja)' . PHP_EOL;
       else if ($what & EVBUFFER_TIMEOUT) print 'timeout' . PHP_EOL;
    },
    $base
);

event_buffer_timeout_set($eb, 10, 10);
event_buffer_base_set($eb, $base);
event_buffer_enable($eb, EV_READ);

event_base_loop($base);

Exceptions kann die Extension ja aber auch schlecht werfen, siehe vorheriger Punkt.

Event loop unterbrechen

In meinem STOMP Client muss ich ab und an die event loop unterbrechen, was zwischendrin machen und dann die event loop wieder starten. Der Zeitraum zwischen unterbrechen und neustarten ist SEHR kurz, aber: wenn nach der Unterbrechung und VOR dem erneuten Starten der event loop Daten in den Socket geschrieben wurden vom Server, dann habe ich diese Daten verpasst und mich gewundert wieso. Folgendes Pattern hat sich dabei bewährt:

  1. Event loop stoppen mit event_base_loopbreak($base)
  2. Schon mal neues event base erzeugen mit $base = event_base_new()
  3. Read event erzeugen, event base zuordnen, event enablen event_buffer_new() usw.
  4. Event loop NOCH NICHT wieder starten
  5. Das tun, was zwischendrin getan werden muss in der app
  6. Jetzt event loop wieder starten mit event_base_loop($base)
  7. Sollten von einem sehr schnellen Server Daten zwischendrin in den Socket geschrieben worden sein, wird der read event dispatched von libevent

Schön ist was anderes, aber hat geholfen einen fiesen Bug zu fixen.

Unit Testing

Unit Testing ist generell ein schwieriges Unterfangen wenn PHP Funktionen im Spiel sind, dazu kommt noch die Abhängigkeit von externe Resourcen wie Sockets. Ich hab das Problem größtenteils umgangen in dem ich auf Unit Tests für die Teile, die libevent Funktionen benutzen, verzichtet habe und setze lieber eine Integration Test Suite auf. In meinem Fall des STOMP Clients verwende ich dazu einem (Dummy) STOMP Server in node.js geschrieben. Ich teste lieber den Output den ich vom Server bekomme anstatt zu prüfen ob eine Methode event_base_loop aufgerufen hat.

Aber sonst…

Insgesamt bin ich mit libevent mehr als zufrieden und bin schon sehr gespannt auf den STOMP client, sodann ich dessen TODO Liste endlich mal runtergearbeitet habe…

Comment » | Allgemein, Development, PHP

Der LAMP Stack ist nicht tot, aber riecht… anderst

Mai 25th, 2011 — 4:48pm

Was den aktuellen Stand des LAMP Stacks angeht habe ich mir schon länger Gedanken gemacht. Dieser Tage hatte ich dann eine entsprechende Unterhaltung über genau das Thema mit meinem Kollegen, dem Karsten.

Gehen wir doch die Bestandteile mal durch:

L für Linux

Ich glaube, darüber brauchen wir uns nicht lange unterhalten. Die ein oder andere Linux Distribution wird die Grundlage für fast alle erfolgreichen Web Projekte sein.

Allerdings gibt es auch Bespiele für High Traffic Webseiten, welche auf dem Microsoft Stack basieren, wie etwa Stackoverflow. Allerdings kommt man auch bei Stackoverflow nicht ganz ohne das L aus – sei es zum Beispiel ein Caching Server auf dem Redis läuft. Auch wenn es natürlich schwer fällt, etwas gegen die sehr erfolgreiche Website zu sagen, habe ich meine Bedenken, was so eine Mischform angeht. Und sei es nur, weil man Developer und DBA braucht, die in beiden doch sehr unterschiedlichen Welten, Microsft und Unix, zu Hause sind. Als Hipster-Startup hat man da natürlich nochmal andere Möglichkeiten. Auch ist mein Eindruck, dass das horizontale Scaling mit dem klassischen LAMP Stack einfacher fällt. Stackoverflow scheint tendenziell eher vertikal skalieren zu wollen (größere/teurere statt “mehr” Hardware), zumindest wenn man sich die Entwicklung momentan ansieht.

Für mich allerdings, obwohl oder gerade weil ich auch Projekte mit IIS/C# gemacht habe, scheint ein ein UNIX basierendes System aber die bessere Wahl zu sein. Das L ist also nicht wegzudenken aus LAMP.

A für Apache

Die Wahl des Webservers war eingentlich lange Zeit keine. Apache oder… Apache. Ganz so sieht es mittlerweile nicht mehr aus.

Nginx – ein leichtgewichtiger, high-Performance http Server, welcher in den Features, die man so braucht für seine Anwendung dem Apache in wenig nachsteht. Das Modell, wie Requests im Nginx abgearbeitet werden, nämlich über eine Event Loop, unterscheidet sich dabei erheblich von dem des Apache (soviel Threads aufmachen wie nötig). Mittlerweile ist Nginx auch in der Version 1.0 stable verfügbar, wird aber schon seit längerem von diversen high Traffic Websites eingesetzt.

node.js – serverseitiges Javascript, basierend auf Goolges V8 Engine. Auch wenn das Projekt noch recht jung ist, hat node.js bereits viel Aufmerksamkeit bekommen in letzter Zeit. Zusammen mit den Pub/Sub Funktionalitäten eines Redis beispielsweise lassen sich sehr interessante Problemlösungen mit node.js finden für spezifische technische Probleme. Ich glaube nicht, dass ich meine Domain-Logik in node implementieren möchte, aber aufgrund des hohen Durchsatzes bieten sich zum Beispiel File-Upload und File-Auslieferung an, oder wie vorher erwähnt mit Redis Pub/Sub lässt sich Realtime Updating einer Website realisieren. Oder auch sehr Javascript lastige Anwendungen könnten von node als Backend profitieren.

Das A hat also Gesellschaft (node.js) oder Ersatz (Nginx) bekommen.

M für mySQL

Im Bereich der Storage Engine hat sich wohl am meisten getan in den letzten Jahren/Monaten. Eine klassische relationale Datenbank wie mySQL war lange Zeit alternativlos, obwohl sich gewisse Daten schlecht im Paradigma von RDBMS speichern lassen bzw. gewisse Funktionen wie komplexere Suchabfragen zumindest in mySQL schwer von der Hand gehen.

Für mich ist mySQL als Haupt-Storage Engine weitestgehend alternativlos, immer noch. Da es mit Doctrine 2 endlich einen brauchbaren ORM für PHP gibt, können auch komplexere Objekt-Graphen in einem RDBMS abgelegt und abgefragt werden.

Trotzdem bieten sich eben eine Vielzahl an Projekten an, um etwa gewisse Schwachstellen von RDBMS auszubügeln:

Redis – ein high-performance Datastructure Server. Man kann es sich vorstellen wie memcache on steroids. Daten können nicht nur als key-value pairs abgelegt werden, in Redis ist es auch möglich sortierte Sets, Listen mit push/pop Funktion z. B. für Queues und Hashes mit Zugriff auf die einzelnen Felder abzulegen. Sehr interessant sind auch die Pub/Sub Funktionen von Redis, die beispielsweise mit node.js in Kombination mit Websockets sehr interessante Möglichkeiten für realtime Updates in einer Website bieten. Als Bonus oben drauf gibt es seit neuestem die Möglichkeit Lua Scripte in Redis ausführen zu lassen. Da Redis auch als persistenter Daten Storage konfiguriert werden kann, ist das Projekt nicht nur als Caching Server interessant.

Solr – Apache Solr ist ein in Java geschriebener Such-Server basierend auf Lucene. Die mySQL Fulltext Search ist ja ab einer gewissen Anzahl an rows nicht mehr zu gebrauchen. Als Alternative bietet sich hier z. B. Solr an. Millionen von Dokumenten können mit hervorragender Performance durchsucht werden. Komplexe Suchabfragen werden mit der Lucene Query Language formuliert. Solr wird bequemerweise über eine REST Schnittstelle angefragt. Durch einen automatisch laufenden Daten-Import aus mySQL mit Solrs dataimport-Handler lassen sich die Dokumente im Suchindex recht einfach auf dem aktuellen Stand halten.

Riak – Riak ist eine noch recht junge noSQL Datenbank, welche auf Amazons Dynamo Paper basiert. Daten werden unter einem key in Buckets gespeichert. Abgefragt werden die Daten genau wie bei Solr über eine REST API. Die interessante Idee an Riak bzw. an der in dem Amazon Dynamo Paper beschrieben Technik: es gibt keinen Master Server. In einem Riak Cluster, das aus mindestens 3 Nodes bestehen sollte, hat jede Node alle Daten bzw. weiß genau bei welcher Node die angefragen Daten liegen. Zur Laufzeit können problemlos Nodes aus dem Cluster entfernt werden oder bei Traffic Peaks zum Cluster hinzugefügt werden. Darüber hinaus kann man über die REST API Map/Reduce Jobs über die gespeicherten Daten laufen lassen. Die Performance der Map/Reduce Jobs konnte mich zwar noch nicht überzeugen, weswegen ich Riak weniger als Daten-Store sehe, auf den Realtime Abfragen gemacht werden. Allerdings kann ich mir Riak sehr gut als eine Art verteiltes Dateisystem vorstellen, in dem Daten auf mehreren Nodes abgelegt werden und z. B. über node.js dann wieder ausgeliefert.

Vor allem auch den noSQL Trend hat sich viel getan im Bereich Datenbanken. mySQL hat denke ich immer noch seinen festen Platz in den allermeisten Projekten, und das auch zu Recht. Aber man darf die oben genannten Projekte nicht vergessen, die meistens eine sinnvolle Ergänzung darstellen können und in manchen Bereichen mySQL tatsächlich ersetzen können.

P für PHP

Im Bereich der tatsächlichen Programming Language in der Webapplikationen geschrieben werden haben sich aus meiner Sicht die wenigsten Änderungen ergeben. PHP ist für mich nach wie vor die Sprache der Wahl, wenn es um dynamische Web Anwendungen geht. PHP bietet nicht die beste Performance, nicht die stringenteste API und nicht die allerbesten Bibliotheken – aber es funktioniert einfach und es gibt genügend Leute, die damit auch sehr gut programmieren können. Das kann man ja nicht von allen Sprachen behaupten. Eine mögliche Alternative könnte vielleicht ein anderes P darstellen – Python. Die Zeit wird zeigen ob Python einen ähnlichen Verbreitungsgrad wie PHP finden kann, auch gibt es gewisse Ansätze in Python, über die sich diskutieren lässt. Ducktyping ist nur eins davon.

Zusammenfassung

Diese kleine Übersicht zeigt, dass Webdevelopment mittlerweile weit über den bis vor gar nicht allzu langer Zeit klar definierten LAMP Stack hinausgeht. Viele Technologien bieten sich als Ergänzung zu Bewährtem an (Redis als Caching Server, mySQL immer noch als Haupt-Storage Engine) oder auch als Ersatz (nginx statt Apache).

Es ist auf jeden Fall eine aufregende Zeit für Developer und viele Applikationen, die bisher nicht denkbar waren im Webumfeld sind mit den erwähnten oder ähnlichen Technologien machbar – wenn man die Alternativen kennt und sich genau überlegt, wie man seinen LAMP Stack sinnvoll ergänzen kann.

Comment » | Allgemein, Development

Back to top