RoadRunner: PHP ist nicht dazu gemacht, zu sterben, oder Golang kommt zur Hilfe

RoadRunner: PHP ist nicht dazu gemacht, zu sterben, oder Golang kommt zur Hilfe

Hallo, Habr! Bei Badoo arbeiten wir aktiv an der Performance von PHP, da wir ein ziemlich großes System in dieser Sprache haben und die LeistungsfĂ€higkeit eine Frage der Kosteneffizienz ist. Vor ĂŒber zehn Jahren haben wir dafĂŒr PHP-FPM entwickelt, das zunĂ€chst eine Sammlung von Patches fĂŒr PHP war und spĂ€ter in die offizielle Distribution aufgenommen wurde.

In den letzten Jahren hat sich PHP stark weiterentwickelt: der Garbage Collector wurde verbessert, und die StabilitĂ€t ist gestiegen – heute ist es problemlos möglich, Daemons und langlebige Skripte in PHP zu schreiben. Das hat es Spiral Scout ermöglicht, einen Schritt weiter zu gehen: Im Gegensatz zu PHP-FPM rĂ€umt RoadRunner den Speicher zwischen den Anfragen nicht auf, was einen zusĂ€tzlichen Leistungsgewinn bringt (obwohl dieser Ansatz den Entwicklungsprozess komplizierter macht). Wir experimentieren derzeit mit diesem Tool, haben aber noch keine Ergebnisse, die wir teilen könnten. Um das Warten angenehmer zu gestalten, veröffentlichen wir die Übersetzung der RoadRunner-AnkĂŒndigung von Spiral Scout.

Der Ansatz in dem Artikel ist uns Ă€hnlich: Bei der Lösung unserer Aufgaben verwenden wir ebenfalls hĂ€ufig die Kombination aus PHP und Go, wodurch wir die Vorteile beider Sprachen nutzen können, ohne eine fĂŒr die andere aufzugeben.

Viel Spaß!

In den letzten zehn Jahren haben wir Anwendungen sowohl fĂŒr Unternehmen auf der Liste der Fortune 500, als auch fĂŒr GeschĂ€ftsmodelle mit weniger als 500 Nutzern entwickelt. WĂ€hrend dieser Zeit haben unsere Ingenieure hauptsĂ€chlich den Backend-Bereich mit PHP gestaltet. Vor zwei Jahren haben wir jedoch eine bedeutende VerĂ€nderung erlebt, die nicht nur die Leistung unserer Produkte, sondern auch deren Skalierbarkeit erheblich beeinflusste – wir haben Golang (Go) in unseren Technologiestack integriert.

Fast sofort stellten wir fest, dass Go es uns ermöglicht, grĂ¶ĂŸere Anwendungen zu erstellen und die Leistung um das 40-Fache zu steigern. Damit konnten wir bestehende Produkte, die in PHP geschrieben waren, erweitern und sie durch eine Kombination der Vorteile beider Sprachen verbessern.

Wir werden erlĂ€utern, wie die Verbindung von Go und PHP uns hilft, reale Entwicklungsprobleme zu lösen und wie sie fĂŒr uns zu einem Werkzeug geworden ist, das uns von Teilen der Probleme befreien kann, die mit dem „Sterben“ von PHP.

Ihrer alltÀglichen PHP-Entwicklungsumgebung

Bevor wir erlĂ€utern, wie Go das Modell des „Sterbens“ von PHP wiederbeleben kann, lassen Sie uns zunĂ€chst Ihre Standard-PHP-Entwicklungsumgebung betrachten.

In den meisten FĂ€llen starten Sie Ihre Anwendung mit einer Kombination aus dem Webserver nginx und dem PHP-FPM-Server. Der erstgenannte bedient statische Dateien und leitet spezifische Anfragen an PHP-FPM weiter, wĂ€hrend PHP-FPM den PHP-Code ausfĂŒhrt. Möglicherweise verwenden Sie eine weniger verbreitete Kombination aus Apache und mod_php. Auch wenn sie etwas anders funktioniert, bleiben die Prinzipien die gleichen.

Betrachten wir, wie PHP-FPM den Anwendungscode ausfĂŒhrt. Wenn eine Anfrage eingeht, initialisiert PHP-FPM einen Kindprozess fĂŒr PHP und ĂŒbergibt die Details der Anfrage als Teil seines Zustands (_GET, _POST, _SERVER usw.).

Der Zustand kann wĂ€hrend der AusfĂŒhrung des PHP-Skripts nicht verĂ€ndert werden, daher gibt es nur einen Weg, um einen neuen Satz von Eingabedaten zu erhalten: den Arbeitsspeicher des Prozesses zu leeren und ihn neu zu initialisieren.

Dieses AusfĂŒhrungsmodell bietet viele Vorteile. Sie mĂŒssen sich keine großen Sorgen um den Speicherverbrauch machen, alle Prozesse sind vollstĂ€ndig isoliert, und wenn einer von ihnen 'stirbt', wird er automatisch neu erstellt, ohne die anderen Prozesse zu beeintrĂ€chtigen. Allerdings gibt es auch Nachteile, die sich bei der Skalierung der Anwendung zeigen.

Nachteile und Ineffizienz einer herkömmlichen PHP-Umgebung

Wenn Sie professionell mit PHP entwickeln, wissen Sie, wo Sie bei einem neuen Projekt anfangen mĂŒssen – bei der Auswahl eines Frameworks. Es handelt sich um Bibliotheken fĂŒr Dependency Injection, ORM, Übersetzungen und Templates. Und natĂŒrlich können Sie alle benutzereingaben bequem in ein einziges Objekt (Symfony/HttpFoundation oder PSR-7) unterbringen. Frameworks sind großartig!

Aber alles hat seinen Preis. Bei jedem Enterprise-Level-Framework mĂŒssen Sie fĂŒr die Verarbeitung einer einfachen Benutzeranfrage oder einer Datenbankabfrage mindestens Dutzende von Dateien laden, zahlreiche Klassen erstellen und mehrere Konfigurationen parsen. Noch schlimmer ist, dass Sie nach Abschluss jeder Aufgabe alles zurĂŒcksetzen und von vorne anfangen mĂŒssen: Der Code, den Sie gerade initialisiert haben, ist dann nutzlos und kann fĂŒr eine weitere Anfrage nicht verwendet werden. ErzĂ€hlen Sie das einem Programmierer, der in einer anderen Sprache arbeitet, und Sie werden das Erstaunen auf seinem Gesicht sehen.

PHP-Entwickler haben jahrelang nach Lösungen fĂŒr dieses Problem gesucht und durchdachte Methoden wie "Lazy Loading", Microframeworks, optimierte Bibliotheken, Caching usw. verwendet. Letztendlich bleibt jedoch oft keine andere Wahl, als die gesamte Anwendung zurĂŒckzusetzen und von vorne zu beginnen, immer wieder. (Anmerkung des Übersetzers: Teilweise wird dieses Problem mit der EinfĂŒhrung preload in PHP 7.4) durch die neuen Funktionen gelöst.

Kann PHP mit Go mehr als eine Anfrage gleichzeitig verarbeiten?

Es ist möglich, PHP-Skripte zu schreiben, die lĂ€nger als nur einige Minuten laufen (bis zu Stunden oder sogar Tagen): zum Beispiel Cron-Jobs, CSV-Parser oder Queue-Manager. All diese Tools funktionieren nach dem gleichen Prinzip: Sie ziehen eine Aufgabe, fĂŒhren sie aus und warten auf die nĂ€chste. Der Code bleibt dauerhaft im Speicher, was wertvolle Millisekunden spart, da das Laden des Frameworks und der Anwendung zahlreiche zusĂ€tzliche Schritte erfordert.

Doch das Entwickeln von langlebigen Skripten ist nicht so einfach. Jeder Fehler kann den Prozess vollstÀndig zum Stillstand bringen, die Diagnose von Speicherlecks kann frustrierend sein, und eine F5-Debugging kann nicht mehr verwendet werden.

Die Situation hat sich mit der EinfĂŒhrung von PHP 7 verbessert: Es gibt jetzt einen zuverlĂ€ssigen Garbage Collector, das Fehlerhandling ist einfacher und die Kernel-Erweiterungen sind nun vor Speicherlecks geschĂŒtzt. Dennoch mĂŒssen Ingenieure weiterhin sorgfĂ€ltig mit dem Speicher umgehen und potenzielle Zustandsprobleme im Code beachten (gibt es eine Programmiersprache, bei der man darauf nicht achten muss?). Trotzdem erwarten uns in PHP 7 weniger Überraschungen.

Kann man das Modell der langlebigen PHP-Skripte nehmen, es fĂŒr trivialere Aufgaben wie die Verarbeitung von HTTP-Anfragen anpassen und so die Notwendigkeit beseitigen, bei jeder Anfrage alles neu zu laden?

Um dieses Ziel zu erreichen, musste zunÀchst eine Serveranwendung implementiert werden, die in der Lage ist, HTTP-Anfragen anzunehmen und diese nacheinander an einen PHP-Worker weiterzuleiten, ohne ihn dabei jedes Mal neu zu starten.

Wir wussten, dass wir einen Webserver mit reinem PHP (PHP-PM) oder mit einer C-Erweiterung (Swoole) schreiben könnten. Obwohl jede Methode ihre Vorteile hat, waren beide Optionen nicht das, was wir suchten – wir wollten mehr. Wir benötigten nicht einfach einen Webserver – wir strebten eine Lösung an, die uns von den Problemen des »schweren Starts« in PHP befreit und sich leicht an spezifische Anwendungen anpassen und erweitern lĂ€sst. Das heißt, wir brauchten einen Anwendungsserver.

Kann Go dabei helfen? Wir wussten, dass es das kann, denn die Sprache kompiliert Anwendungen in einzelne binĂ€re Dateien; sie ist plattformĂŒbergreifend; verwendet ein eigenes, sehr elegantes Modell fĂŒr parallele Verarbeitung (Concurrency) und eine Bibliothek zur Arbeit mit HTTP; und schließlich stehen uns tausende von Open-Source-Bibliotheken und Integrationen zur VerfĂŒgung.

Herausforderungen der Kombination von zwei Programmiersprachen

ZunÀchst musste festgelegt werden, wie zwei oder mehr Anwendungen miteinander kommunizieren.

Zum Beispiel mit einer großartigen Bibliothek Die Nutzung von Alexa PalĂŠstras ermöglichte die gemeinsame Nutzung des Speichers zwischen den PHP- und Go-Prozessen (Ă€hnlich wie mod_php in Apache). Allerdings hat diese Bibliothek EinschrĂ€nkungen, die ihre Anwendung fĂŒr unsere Lösung begrenzen.

Wir entschieden uns fĂŒr einen anderen, weit verbreiteten Ansatz: die Interaktion zwischen den Prozessen ĂŒber Sockets/Pipelines zu gestalten. Dieser Ansatz hat sich ĂŒber die letzten Jahrzehnte als zuverlĂ€ssig erwiesen und wurde auf Betriebssystemebene gut optimiert.

ZunĂ€chst erstellten wir ein einfaches binĂ€res Protokoll zum Datenaustausch zwischen den Prozessen und zur Fehlerbehandlung. In seiner einfachsten Form Ă€hnelt dieses Protokoll einem Netstring mit mit einem festen Paketheader (in unserem Fall 17 Byte), der Informationen ĂŒber den Pakettyp, seine GrĂ¶ĂŸe und eine binĂ€re Maske zur ÜberprĂŒfung der DatenintegritĂ€t enthĂ€lt.

Auf der PHP-Seite verwendeten wir die pack-Funktion, wÀhrend wir auf der Go-Seite die Bibliothek encoding/binary.

verwendeten. Ein einzelnes Protokoll erschien uns als unzureichend – daher fĂŒgten wir die Möglichkeit hinzu, Go-Dienste net/rpc direkt aus PHP aufzurufen.. SpĂ€ter hat uns das bei der Entwicklung sehr geholfen, da wir Go-Bibliotheken leicht in PHP-Anwendungen integrieren konnten. Das Ergebnis dieser Arbeit ist beispielsweise in einem anderen unserer Open-Source-Produkte sichtbar. Goridge.

Aufteilung von Aufgaben auf mehrere PHP-Worker

Nach der Implementierung des Interaktionsmechanismus dachten wir darĂŒber nach, wie wir Aufgaben am effizientesten an die PHP-Prozesse ĂŒbergeben können. Wenn eine Aufgabe eintrifft, muss der Anwendungsserver einen freien Worker zur Bearbeitung auswĂ€hlen. Falls der Worker/Prozess mit einem Fehler beendet wurde oder ‚gestorben‘ ist, entfernen wir ihn und erstellen einen neuen. Hat der Worker/Prozess erfolgreich gearbeitet, wird er wieder in den Pool der zur VerfĂŒgung stehenden Worker zurĂŒckgegeben.

RoadRunner: PHP ist nicht dazu gemacht, zu sterben, oder Golang kommt zur Hilfe

FĂŒr die Verwaltung des Pools aktiver Worker haben wir einen gepufferten Kanal, hinzugefĂŒgt, um unerwartet ‚gestorbene‘ Worker durch einen Mechanismus zur Fehler- und StatusĂŒberwachung aus dem Pool zu entfernen.

Somit haben wir einen funktionierenden PHP-Server erhalten, der in der Lage ist, beliebige, binÀr dargestellte Anfragen zu verarbeiten.

Damit unsere Anwendung als Webserver funktioniert, mussten wir einen zuverlĂ€ssigen PHP-Standard zur Darstellung von eingehenden HTTP-Anfragen wĂ€hlen. In unserem Fall wandeln wir net/http-Anfragen aus Go in das Format PSR-7, damit es mit den meisten heute verfĂŒgbaren PHP-Frameworks kompatibel ist.

Da PSR-7 als unverĂ€nderlich angesehen wird (technisch nicht ganz korrekt), mĂŒssen Entwickler Anwendungen schreiben, die Anfragen nicht als globale EntitĂ€t behandeln. Dies passt hervorragend zum Konzept von langlebigen PHP-Prozessen. Unsere endgĂŒltige Implementierung, die noch keinen Namen hat, sah so aus:

RoadRunner: PHP ist nicht dazu gemacht, zu sterben, oder Golang kommt zur Hilfe

Wir prĂ€sentieren RoadRunner — ein hochperformanter Server fĂŒr PHP-Anwendungen

Unsere erste Testaufgabe war ein API-Backend, bei dem gelegentlich unvorhersehbare Anstiege bei den Anfragen auftraten (viel hĂ€ufiger als gewöhnlich). Obwohl die Möglichkeiten von nginx in den meisten FĂ€llen ausreichten, standen wir regelmĂ€ĂŸig vor dem Fehler 502, weil wir das System nicht schnell genug auf die erwartete erhöhte Last ausbalancieren konnten.

Um diese Lösung zu ersetzen, haben wir Anfang 2018 unseren ersten PHP/Go-Anwendungsserver bereitgestellt. Und sofort erzielten wir unglaubliche Ergebnisse! Wir haben nicht nur den Fehler 502 vollstĂ€ndig beseitigt, sondern konnten auch die Anzahl der Server um zwei Drittel reduzieren, wodurch wir eine Menge Geld und viele Kopfschmerztabletten fĂŒr unsere Ingenieure und Produktmanager gespart haben.

Mitte des Jahres verbesserten wir unsere Lösung, veröffentlichten sie auf GitHub unter der MIT-Lizenz und nannten sie RoadRunner, was seine unglaubliche Geschwindigkeit und Effizienz unterstreicht.

Wie RoadRunner Ihren Entwicklungsstack verbessern kann

Anwendung RoadRunner erlaubte es uns, das Middleware net/http auf der Go-Seite zu nutzen, um die JWT-ÜberprĂŒfung bereits durchzufĂŒhren, bevor die Anfrage PHP erreicht, sowie um WebSockets zu verarbeiten und globalen Zustand in Prometheus zu aggregieren.

Dank des eingebauten RPC können wir APIs beliebiger Go-Bibliotheken fĂŒr PHP öffnen, ohne Wrapper-Erweiterungen schreiben zu mĂŒssen. Was noch wichtiger ist, können mit RoadRunner neue Server bereitgestellt werden, die sich von HTTP unterscheiden. Beispielsweise können PHP-Handler gestartet werden, AWS Lambdarobuste Queue-Parser erstellt und sogar gRPC in unsere Anwendungen aufgenommen werden.

Durch die Community-Entwicklungen in PHP und Go haben wir die StabilitĂ€t unserer Lösung verbessert. In einigen Tests konnten wir die Anwendungsleistung um das 40-Fache steigern, Debugging-Tools optimieren, die Integration mit dem Symfony-Framework umsetzen und UnterstĂŒtzung fĂŒr HTTPS, HTTP/2, Plugins und PSR-17 hinzufĂŒgen.

Fazit

Einige bleiben immer noch in der veralteten Vorstellung gefangen, dass PHP eine langsame und schwerfĂ€llige Sprache ist, die nur fĂŒr die Entwicklung von WordPress-Plugins geeignet ist. Diese Personen könnten sogar behaupten, dass PHP eine solche EinschrĂ€nkung hat: Wenn eine Anwendung groß genug wird, muss man auf eine „reifere“ Sprache wechseln und den ĂŒber viele Jahre gewachsenen Code neu schreiben.

Darauf möchten wir antworten: Denken Sie noch einmal nach. Wir sind der Meinung, dass nur Sie selbst irgendwelche EinschrĂ€nkungen fĂŒr PHP setzen. Sie können Ihr ganzes Leben damit verbringen, von einer Sprache zur anderen zu wechseln, wĂ€hrend Sie versuchen, die perfekte Kombination fĂŒr Ihre BedĂŒrfnisse zu finden, oder Sie können beginnen, Sprachen als Werkzeuge zu betrachten. Die vermeintlichen Nachteile von PHP können tatsĂ€chlich GrĂŒnde fĂŒr seinen Erfolg sein. Wenn Sie es mit einer anderen Sprache wie Go kombinieren, schaffen Sie viel leistungsfĂ€higere Produkte, als wenn Sie sich auf eine einzige Sprache beschrĂ€nken.

Nach der Arbeit mit der Kombination aus Go und PHP können wir sagen, dass wir sie lieben gelernt haben. Wir haben nicht vor, auf eine der beiden zugunsten der anderen zu verzichten – ganz im Gegenteil, wir werden nach Wegen suchen, um noch mehr aus diesem Dual-Stack herauszuholen.

UPD: Wir begrĂŒĂŸen den Schöpfer von RoadRunner und Mitautor des ursprĂŒnglichen Artikels – Lachezis

Quelle: habr.com

Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server đŸ”„ Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster