Was hat es mit synchron auf sich?
In den mittleren 1990er Jahren, als das Internet noch in den Kinderschuhen steckte, waren Server und die Programmiersprachen für Webseiten in ihrer Essenz synchron. Dies war die goldene Ära von PHP und den Apache-Servern. Die Übertragungsgeschwindigkeit tänzelte damals zwischen bescheidenen 56 kbit/s und 128 kbit/s – dank der Nutzung zweier zusammengeschlossener ISDN-Leitungen. In dieser Zeit, wo die Kosten einer Internetverbindung noch nach Minuten oder nach Traffic berechnet wurden, war es essenziell, so schnell wie möglich online zu gehen und genauso rasch das Netz wieder zu verlassen. Daher kam die minimalistische Bereitstellung von Webseiten eine besondere Bedeutung zu. PHP, als eine Sprache, die serverseitig ihr Können entfaltet, spielte hierbei eine Schlüsselrolle, indem es dynamisch HTML-Seiten erzeugte, um auf die Anfragen der Nutzer zu antworten. Trotz des rasanten Fortschritts der Technologie ist dies ein Aspekt, der bis heute Bestand hat.
Mit der Zeit, genau genommen von 1995 bis zu unserem heutigen Jahr 2024, hat sich die Internetgeschwindigkeit drastisch erhöht. Bereits Ende der 90er Jahre wurden die ersten Flatrates angeboten. Damit wurde das Konzept, online zu sein, zu einem festen Bestandteil unseres Lebens.
Man hielt sich immer länger im Internet auf. Die meistbesuchten Webseiten waren zu Anfang Chats, Foren im Allgemeinen alles, was mit Kommunikation zu tun hat. Fazit, die Anfragen häuften sich und die Belastung der Server stieg stetig an. Man musste etwas tun.
Die Server-Technologie
Ein spannender Wendepunkt in der Server-Technologie war die Einführung des Event MPM (Event Multi-Processing Module) bei Apache 2.2 im Dezember 2005, welches sich mit Apache 2.4 im Februar 2012 fest etablierte. Dieses Modul revolutionierte die Effizienz von Keep-Alive-Verbindungen durch die Nutzung eines dedizierten Listener-Threads, wodurch die Anzahl benötigter Prozesse für jede Verbindung drastisch reduziert wurde.
Dann gibt es da noch Nginx Server. Berühmt für seine hohe Leistung, Stabilität, und einfache Konfiguration, die von Anfang an auf einem asynchronen, ereignisgesteuerten Modell fußte. Seit seiner Veröffentlichung im Oktober 2004 meistert Nginx die Herausforderung, Tausende von gleichzeitigen Verbindungen auf einer einzigen Maschine zu bewältigen, und das mit minimalem Speicherverbrauch – ein deutlicher Gegensatz zum traditionellen Modell von Apache vor der Einführung des Event MPM.
Die Evolution von synchronen zu asynchronen Serverarchitekturen hat zwar keinen direkten Einfluss auf die Natur von PHP, aber sie zeigt die dynamische Entwicklung der Webtechnologien.
Die Art und Weise, wie PHP mit synchronem Verhalten auf einem Apache- und einem Nginx-Server umgeht, unterscheidet sich hauptsächlich aufgrund der unterschiedlichen Architekturen und Standardkonfigurationen dieser Webserver. Der Hauptunterschied liegt in der Art, wie Anfragen an PHP weitergeleitet und von PHP verarbeitet werden.
Mit Apache kann PHP über das mod_php-Modul eingebunden werden. PHP wird dann als Teil des Apache-Prozesses ausgeführt. Dies bedeutet, dass für jede Client-Anfrage ein separater Apache-Prozess gestartet wird, in dem der PHP-Code synchron ausgeführt wird. Das führt zu einem Modell, bei dem jede Anfrage isoliert, von den anderen bearbeitet wird. Es kann jedoch besonders unter Last zu einem hohen Ressourcenverbrauch führen. Jeder Prozess oder Thread benötigt nun mal zusätzlichen Speicher und CPU-Zeit.
Nginx unterstützt nativ kein mod_php und kann PHP-Code nicht direkt innerhalb seiner Prozesse ausführen. Stattdessen wird PHP über FastCGI und speziell über PHP-FPM (FastCGI Process Manager) ausgeführt. PHP-FPM ist ein separater Daemon, der PHP-Anfragen über das FastCGI-Protokoll empfängt. Nginx leitet eingehende Anfragen an PHP-FPM weiter, welcher dann den PHP-Code ausführt und das Ergebnis zurück an Nginx sendet, das schließlich die Antwort an den Client ausliefert.
Diese Trennung ermöglicht es Nginx, seine Anfragen effizient und asynchron zu verwalten, da Nginx selbst nicht blockiert wird, während PHP-FPM die Anfragen verarbeitet. PHP-FPM kann konfiguriert werden, um eine dynamische Anzahl von Worker-Prozessen zu verwalten, die je nach Bedarf PHP-Code synchron ausführen können. Diese Konfiguration bietet eine bessere Skalierbarkeit und Effizienz im Vergleich zur direkten Integration von PHP in den Webserver, wie es bei Apache der Fall ist.
Obwohl PHP in beiden Fällen synchronen Code ausführt, ermöglicht die Architektur von Nginx mit PHP-FPM eine effizientere Nutzung der Systemressourcen, insbesondere unter hoher Last. Der Hauptunterschied liegt also nicht in der Art und Weise, wie PHP selbst arbeitet, sondern in der Art und Weise, wie Anfragen an PHP übergeben werden und wie die Ergebnisse verwaltet werden.
Es ist auch wichtig zu beachten, dass sowohl Apache als auch Nginx durch die Verwendung von zusätzlichen Technologien oder speziellen Konfigurationen angepasst werden können. Die Leistung und Effizienz kann so weiter optimiert werden, einschließlich der Nutzung von asynchronen Features oder der Implementierung von Caching-Lösungen. Kurz um, Apache und Nginx waren ab 2005 asynchron aufgestellt.
Hallo, asynchrones Node.js!
Einen bemerkenswerten Punkt in der Geschichte der Backend-Programmierung markierte die Einführung von Node.js im Jahr 2009. Es war die erste Technologie, die speziell für asynchrone Operationen entworfen wurde, und eröffnete damit neue Horizonte in der Entwicklung von Webanwendungen.
2012 wurde dann ReactPHP vorgestellt, ein Werkzeug, das es PHP ermöglicht, in die Fußstapfen von Node.js zu treten und die Türen für ereignisgetriebene, nicht-blockierende I/O-Operationen weit aufzustoßen. Obwohl es keine spezifischen CMS oder Frameworks für ReactPHP gibt, findet es seinen Platz in der Entwicklung von hochperformanten Netzwerkanwendungen und in der Nutzung von AJAX oder GraphQL, ähnlich wie bei Node.js mit seinen vielfältigen Frameworks.
Node.js läuft im Kern mit der V8 Javascript Engine und verwendet ein nicht-blockierendes, ereignisgesteuertes I/O-Modell (Input/Output-Modell), das es ideal für skalierbare und leistungsfähige Netzwerkanwendungen macht. Die Anfragen basieren auf der Event Loop (Ereignisschleife). Ereignisse (Events) werden abgehört und entsprechende Callbacks (Rückruffunktionen) ausgeführt. So können Operationen wie Netzwerkanfragen, Dateizugriffe oder Datenbankabfragen nicht-blockieren behandelt werden.
Gehen wir den Prozess genauer durch. Node.js empfängt beispielsweise eine HTTP-Anfrage. Handelt es sich um eine I/O-Operation, wird die Anfrage an den Worker Pool übergeben, und eine Callback-Funktion wird definiert. Währen die I/O-Operation im Hintergrund läuft, bleibt die Event-Loop aktiv und kann weitere Anfragen bearbeiten. Ist die I/O-Operation abgeschlossen, wird die entsprechende Callback-Funktion in die Event-Loop eingereiht und erst dann ausgeführt, wenn sie dazu bereit ist. Das Ergebnis der Operation wird verarbeitet und eine Antwort wird an den Client gesendet.
I/O-Anfragen werden also sofort ausgeführt, ohne den Hauptthread zu blockieren. Eine Callback-Funktion wird registriert, sobald die Operation abgeschlossen wurde. Währenddessen kann die Event-Loop andere Ereignisse und Abfragen bearbeiten.
Was hat es mit Hauptthread und Multithreading auf sich?
In der Computertechnik ist ein Thread der kleinste Teil eines Prozesses, der unabhängig verwaltet und vom Betriebssystem scheduler ausgeführt werden kann. Ein Prozessor mit mehreren Kernen kann mehrere Threads gleichzeitig ausführen, wodurch die Ausführungsgeschwindigkeit für bestimmte Arten von Aufgaben verbessert wird.
Node.js verwendet die Single-Thread-Architektur. Trotz dessen verwendet Node.js intern einen Pool von Worker-Threads (über die libuv-Bibliothek), um bestimmte asynchrone I/O-Operationen im Hintergrund auszuführen. Diese Worker-Threads können für Operationen genutzt werden, die nicht direkt vom Haupt-Event-Loop verarbeitet werden können, wie z. B. komplexe Berechnungen oder blockierende I/O-Operationen. Dies hält den Hauptthread frei, um schnell auf neue Ereignisse zu reagieren.
Diese nicht-blockierende, ereignisgesteuerte Architektur ermöglicht es eine hohe Anzahl von gleichzeitigen Verbindungen effizient zu verwalten, ohne dabei an Leistung zu verlieren und ist besonders geeignet für Echtzeit-Anwendungen, SPAs und APIs macht.
Trotz des synchronen Verhaltens hat sich PHP, durch den Einsatz in mächtigen Projekten wie Wikipedia, Facebook (wenn auch nur zu Anfang) und Etsy, als eine robuste Sprache für große und skalierbare Projekte bewährt. Diese Plattformen beweisen, dass mit der richtigen Architektur und einem soliden Einsatz PHP unglaubliche Ergebnisse erzielt werden können.
Best Practice
Ob man sich für eine synchrone, asynchrone oder beiden entscheiden sollte, liegt am Anwendungsfall oder an der Skalierung des Projektes.
Synchrone Programmierung (z. B. PHP mit Apache)
Eignet sich für Projekte mit:
- geringen gleichzeitigen Anfragen.
- serverseitigen Rendering-Modellen.
Optimierung der Performance eignet sich die Benutzung von Caching-Mechanismen wie OpCode-Chaching (wie z. B. APCu, Zend OPchache)
Asynchrone Programmierung (z. B. Node.js mit Nginx)
Eignet sich für Projekte mit:
- hoher Last
- vielen gleichzeitigen Verbindungen
- Anwendungen wie Chats, Websockets und APIs
Um Blockierungen zu vermeiden und die Skalierbarkeit erhöhen zu können, sollten man eine ereignisgesteuerte Architektur verwenden.
Meine Meinung
Da ich die letzten 4 Jahrezente mit PHP und Senior Programmierung beschäftigt war, bezieht sich mein Kommentar zur Zeit auf PHP und SQL.
Vermeidung unnötiger Serveranfragen
Eine der Schlüsselstrategien in meiner Karriere war stets, die Anzahl der Serveranfragen so gering wie möglich zu halten. Auch wenn moderne Server und Technologien enorme Lasten bewältigen können, bleibt es eine Best Practice, effizient zu arbeiten.
Caching von SQL-Ergebnissen
Obwohl MySQL bereits eigene Cache-Mechanismen bietet, habe ich oft SQL-Anfragen optimiert, indem ich deren Ergebnisse als JSON oder XML speicherte. Diese Vorgehensweise hatte mehrere Vorteile:
- Effizienz: Wiederholte Anfragen konnten schnell bedient werden, indem die gespeicherten Daten direkt an den Client gesendet wurden.
- Performance: Die Belastung der Datenbank wurde minimiert, was besonders bei hohem Traffic spürbare Performance-Gewinne brachte.
Statisches HTML-Rendering
Ein weiterer bewährter Ansatz war das Speichern von gerenderten PHP-Seiten als statische HTML-Dateien. Dies hatte den Vorteil, dass die Seiten bei wiederholten Aufrufen direkt vom Server heruntergeladen werden konnten, ohne erneut gerendert werden zu müssen. Diese Technik reduzierte die Serverlast erheblich und beschleunigte die Ladezeiten.
Fazit
Diese Ansätze haben sich in meiner langjährigen Karriere als äußerst effektiv erwiesen. Sie haben mir geholfen, performante und skalierbare Webanwendungen zu entwickeln, die auch unter hoher Last zuverlässig arbeiten. Durch das Caching von Daten und das Rendern statischer HTML-Seiten konnte ich die Effizienz meiner Anwendungen deutlich steigern und den Nutzern ein besseres Erlebnis bieten.