Category: Development


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

AOP in PHP durch weaven des source codes

März 28th, 2012 — 8:49am

Mein heimliches Steckenpferd ist ja immer noch AOP. Seit ich damit bei Spring.NET in Berührung gekommen bin fasziniert mich die Idee source code dynmaisch Funktionalität hinzufügen zu können. Ich schwanke meistens zwischen “find ich total super” und “intransparent, mag kein Mensch haben”. In PHP gibt es nur ein Projekt, dass sich ernsthaft daran gemacht hat, AOP zu implementieren, und das ist natürlich FLOW3 (was mittlerweile zu einem sehr spannenden Projekt herangereift ist).

Proxy-Childclasses

Vor einigen Wochen habe ich mir gewohnheitsmäßig mal wieder die FLOW3 Dokumentation aus Interesse angeschaut und war positiv überrascht, dass ich Objekte einfach mit new MyStuff(); erstellen kann, das $this->objectManager->create('MyStuff'); ist verschwunden. Und trotzdem kann ich Dependency Injection und AOP haben? Obwohl ich die Objekte selbst erstelle? Wie geht das denn?

Der von FLOW3 angewendete “Trick” kann sich sehen lassen. Beim Aufruf der Seite (im development context) wird die Klasse, die geweaved werden soll umbenannt (_Original suffix), es wird eine Proxy-Klasse erstellt, welche die originale Klasse extended und den Namen der original Klasse bekommt und in den überschriebenen Methoden des Proxies werden die dafür vorgesehenen Aspects eingefügt. Die Klassen werden dann im cache Verzeichnis abgelegt, damit die original Dateien unberührt bleiben. Selbstverständlich ist der Prozess wie das genau abläuft um einiges aufwendiger und komplizierter (Stichwort: change detection) aber das Prinzip welches mich fasziniert hat wird glaube ich deutlich. Im Prinzip ist dieses Vorgehen nicht unähnlich zu Doctrine2 – auch dort werden Proxy Klassen erstellt und die original Methoden in der abgeleiteten Klasse überschrieben um lazy loading zu ermöglichen.

Ein Nachteil dieses Ansatzes ist, dass ich, auch wenn ich den Klassennamen beibehalte auf einmal eine weitere Ebene in der Klassenhierarchie habe, die mir vom Framework eingefügt wurde. So wird zum Beispiel aus:

MyController extends ActionController

Die Hierarchie:

MyController_Original extends ActionController
MyController extends MyController_Original

Proxying mit source code Manipulation

In Java oder C# hingegen gibt es ein paar AOP Frameworks welche die Aufrufe direkt in den generierten Bytecode weaven nachdem das Projekt kompiliert wurde. Sowas funktioniert natürlich in PHP nicht so recht.

Meine Idee war zu sagen ich parse die source files die geweaved werden sollen (also den PHP code der Klassendefinition), benenne die Methoden um, füge Proxy Methoden mit dem original Namen ein, speichere die PHP Datei in ein TMP Verzeichnis und sage dem autoloader, dass er die geweavte Klasse aus dem TMP Verzeichnis laden soll wenn die Klasse instanziert wird. Symfony2 zum Beispiel dumped (im development context) auch jedes mal den DI container bevor die eigentliche Anwendung läuft. Einfach, oder? Dachte ich auch und hab mich sofort mit PHPs tokenizer Extension auf den Weg gemacht. Leider ist PHP source code parsen doch unangenehmer als ich mir das vorgestellt hatte. Auch wenn mein naiver Ansatz mit tokenizer funktioniert hat – der Code wurde unmaintainable.

PHPParser to the rescue

Zu ungefähr der gleichen Zeit bin ich auf HackerNews über einen in PHP geschriebeben PHP Parser gestolpert, der genau das Level an Abstraktion bietet, das ich für meine Idee brauche. Mit dem PHPParser kann man recht bequem PHP source code einlesen und manipulieren in dem man sich einen eigenen NodeVisitor registriert. Mit etwas Ausprobieren konnte ich damit bei größeren/komplexeren Projekten wie z. B. symfony components die Klassen weaven.

Proof of concept

Auch wenn die Bibliothek natürlich noch weit (sehr weit) von fertig entfernt ist, habe ich meine bisherigen Fortschritte zusammenfassend ein Repository unter dem Namen ClassWaver auf github angelegt. Viel passiert nicht, außer dass die symfony HttpKernel Komponente geweaved wird und Methodenaufrufe geproxied werden. An der Stelle an der momentan ein einfaches print ausgegeben wird, werden Methodenargumente abgefangen, der return value der originalen Methode abgefangen und an den Client zurückgegeben etc. pp. Das wäre also die Stelle, an der AOP magic oder Dependency Injection in mit new instanzierten Objekte passieren kann.

Damit der ClassWeaver überhaupt mal für production Projekte in Frage kommt, muss noch einiges passieren. Das größte Problem ist sicher die Performance. Source trees, wie z. B. ein ausgewachsenes Symfony2 Projekt dauern sehr, sehr lange zu weaven. Change detection wie FLOW3 das macht wäre evtl. eine Lösung. Über die Syntax, wie AOP Definitionen angelegt werden damit diese vom Weaver berücksichtigt werden habe ich mir selbstverständlich auch noch keine Gedanken gemacht und noch vieles mehr.

Im Grunde ist der ClassWeaver eher ein Gedankenexperiment als etwas anderes. Trotzdem finde ich den Ansatz direkt über einen Parser Aufrufe im Source Code zu platzieren auf eine gewisse Art sehr interessant – und auch bedenklich. So wie ich AOP insgesamt sehe.

Comment » | AOP, 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