Asynchron, wo es zählt

Das neue PHP-Framework Resonance

Das neue PHP-Framework Resonance

Asynchron, wo es zählt

Das neue PHP-Framework Resonance


Resonance ist ein neues PHP-Framework, das von Grund auf entwickelt wurde, um Interoperabilität und Messaging zwischen Diensten in Ihrer Infrastruktur und darüber hinaus zu erleichtern. Ich habe es auf Basis von Swoole gebaut, was ihm asynchrone Fähigkeiten und Unterstützung für WebSockets, RPC, parallele GraphQL-Feldauflösung und andere PHP-eigene Technologien verleiht.

In diesem Artikel werde ich auf den aktuellen Status von PHP und die asynchrone Programmierung eingehen. Anschließend erkläre ich, welche Vorteile Resonance für dieses Ökosystem bringen kann.

Modernes PHP – zentrale Infrastruktur

PHP ist beliebt, weil es so einfach ist, neue Funktionen zu erstellen, Prototypen zu entwickeln und APIs bereitzustellen. Es ist nicht mehr nur ein Hypertext-Prozessor oder eine REST API Engine. Mit der Einführung moderner asynchroner Funktionen wie Coroutines, Fibres und Ereignisschleifen kann es ein zentraler Kommunikationsknotenpunkt und Einstiegspunkt in Ihre Infrastruktur sein.

Da es einfach ist, Prototypen zu erstellen und Funktionen in PHP zu entwickeln, ist es oft eine natürliche Wahl, Projekte damit zu beginnen. Die meisten Projekte beginnen mit einem zentralen Dienst, der nach und nach erweitert wird und immer mehr Funktionen enthält. Gleichzeitig kann man mit so einem Monolithen fast unbegrenzt weitermachen. In der Regel verwandelt sich die Anwendung jedoch in eine Reihe von Microservices, die oft in mehreren spezialisierten Sprachen geschrieben sind. Danach haben wir einen zentralen PHP-Dienst, von dem all das Genannte ausgeht, der viele API- und RPC-Aufrufe tätigen muss – die allerdings immer noch synchron und in der Regel weniger performant und wartbar sind.

Was ist, wenn Sie Teile Ihrer Anwendung durch Microservices ersetzen oder zwei Dienste zusammenführen möchten? Können wir Funktionen je nach Bedarf mischen und anpassen? Ist Ihr PHP-Markdown-Parser zu langsam? Warum verwenden Sie nicht einige Rust-Anwendungen und machen einen gRPC-Aufruf? Möchten Sie eine bestimmte Route an einen anderen Microservice weiterleiten? Leiten Sie die Anfrage einfach von einem Controller weiter. Das sind die Arten von Aufgaben, die die meiste Zeit und die meisten Ressourcen verbrauchen – den Code neu verdrahten, erweitern, zusammenführen, faktorisieren –, und eben nicht neue Features schreiben. Was wäre, wenn wir unseren Workflow so optimieren könnten, dass er das bewältigt? Wir brauchen alle Konnektivitätsfunktionen – GraphQL, RPC, WebSockets usw.

Asynchrones und langlaufendes PHP

Manchmal stoße ich auf PHP-Entwickler:innen, die Bedenken äußern, dass asynchroner Code PHP unnötig komplex und komplizierter zu lesen und zu warten macht, und dass er zu einigen Problemen führen kann, die wir bei synchronem Code nicht haben. Es ist richtig, dass es neue Probleme gibt, mit denen wir uns in PHP früher nicht auseinandersetzen mussten – Speicherlecks, Garbage-Collector-Overhead und Pooling von Verbindungen (anstatt nur eine Verbindung beim Start eines Skripts zu öffnen). Es würde keinen Sinn machen, diese Probleme ohne große Vorteile zu lösen.

Unter langlaufenden Skripten verstehe ich PHP mit einer eingebauten Server- oder Ereignisschleife, die auf mehrere Anfragen reagieren kann. Im klassischen PHP-Modell startet ein Webserver, wenn er eine HTTP-Anfrage erhält, ein PHP-Skript, wartet darauf, dass es die HTTP-Antwort generiert, und beendet dann das Skript. Dieser Vorgang wiederholt sich bei jeder eingehenden Anfrage. Natürlich gibt es viele Optimierungen wie JIT Compiling, OPCache und FPM Worker Pools, aber die allgemeine Idee bleibt dieselbe.

Bei jeder Anfrage muss das von Ihnen verwendete Framework jedes Mal seine Dienste booten – in der Regel, um Konfigurationsdateien zu lesen, Klassen automatisch zu laden, den Dependency-Injection-Container zu erstellen und Controller zu instanziieren. Das ist der Grund, warum Caching für viele PHP-Entwickler:innen zur zweiten Natur geworden ist – es gibt einfach keinen performanten Weg, das anders zu handhaben, als zu cachen, was immer möglich ist, um die Bootstrap-Phase der Anwendung so schnell wie möglich zu machen. Aber egal, was wir tun, der Overhead ist immer da.

Wenn wir stattdessen ein Long-Running-Skript verwenden, können wir zwei Dinge erreichen:

  1. Wir müssen keine Dienste, Einstellungen oder irgendetwas anderes zwischenspeichern, um den Bootvorgang der Anwendung zu beschleunigen. Selbst wenn es eine Sekunde dauert, das Skript zu starten (was z. B. mit FPM auf keinen Fall machbar ist, denn das würde bedeuten, dass jede HTTP-Anfrage an diese Anwendung eine Sekunde länger dauert), macht das keinen wirklichen Unterschied – wir können unseren Code viel unkomplizierter gestalten.

  2. Es gibt keinen Bootstrap-Overhead bei der Verarbeitung von HTTP-Anfragen, WebSocket- oder RPC-Nachrichten – Ihre Anwendung kann nur einmal booten und dann eingehende Verbindungen verarbeiten.

Wenn man also bedenkt...