RoadRunner: PHP ist nicht dafĂŒr gemacht, zu sterben, und Golang hilft nicht

RoadRunner: PHP ist nicht dafĂŒr gemacht, zu sterben, und Golang hilft nicht

Hey Habr! Wir sind bei Badoo aktiv Ich arbeite an der PHP-Leistung, da wir ein ziemlich großes System in dieser Sprache haben und das Leistungsproblem eine Kosteneinsparungsfrage ist. Vor mehr als zehn Jahren haben wir dafĂŒr PHP-FPM erstellt, das zunĂ€chst eine Reihe von Patches fĂŒr PHP war und spĂ€ter in die offizielle Distribution gelangte.

In den letzten Jahren hat PHP große Fortschritte gemacht: Der Garbage Collector hat sich verbessert, die StabilitĂ€t ist gestiegen – heute kann man problemlos Daemons und langlebige Skripte in PHP schreiben. Dies ermöglichte es Spiral Scout, noch weiter zu gehen: RoadRunner rĂ€umt im Gegensatz zu PHP-FPM den Speicher zwischen Anfragen nicht auf, was zu einem zusĂ€tzlichen Leistungsgewinn fĂŒhrt (obwohl dieser Ansatz den Entwicklungsprozess verkompliziert). Wir experimentieren derzeit mit diesem Tool, haben aber noch keine Ergebnisse, die wir mitteilen können. Damit das Warten auf sie noch mehr Spaß macht, Wir veröffentlichen die Übersetzung der RoadRunner-AnkĂŒndigung von Spiral Scout.

Der Ansatz aus dem Artikel liegt uns am Herzen: Bei der Lösung unserer Probleme verwenden wir meistens auch eine Reihe von PHP und Go, um die Vorteile beider Sprachen zu nutzen und nicht die eine zugunsten der anderen aufzugeben.

Viel Spaß damit!

In den letzten zehn Jahren haben wir Anwendungen fĂŒr Unternehmen aus der Liste erstellt Fortune-500und fĂŒr Unternehmen mit einem Publikum von nicht mehr als 500 Benutzern. WĂ€hrend dieser Zeit haben unsere Ingenieure das Backend hauptsĂ€chlich in PHP entwickelt. Doch vor zwei Jahren hatte etwas einen großen Einfluss nicht nur auf die Leistung unserer Produkte, sondern auch auf deren Skalierbarkeit – wir fĂŒhrten Golang (Go) in unseren Technologie-Stack ein.

Fast sofort stellten wir fest, dass wir mit Go grĂ¶ĂŸere Anwendungen mit bis zu 40-fachen Leistungssteigerungen erstellen konnten. Damit konnten wir unsere bestehenden PHP-Produkte erweitern und verbessern, indem wir die Vorteile beider Sprachen kombinierten.

Wir verraten Ihnen, wie die Kombination von Go und PHP dabei hilft, echte Entwicklungsprobleme zu lösen und wie sie fĂŒr uns zu einem Tool geworden ist, das einige der damit verbundenen Probleme beseitigen kann Aussterbendes PHP-Modell.

Ihre tÀgliche PHP-Entwicklungsumgebung

Bevor wir darĂŒber sprechen, wie Sie mit Go das sterbende PHP-Modell wiederbeleben können, werfen wir einen Blick auf Ihre Standard-PHP-Entwicklungsumgebung.

In den meisten FĂ€llen fĂŒhren Sie Ihre Anwendung mit einer Kombination aus dem Nginx-Webserver und dem PHP-FPM-Server aus. Ersteres bedient statische Dateien und leitet spezifische Anfragen an PHP-FPM weiter, wĂ€hrend PHP-FPM selbst PHP-Code ausfĂŒhrt. Möglicherweise verwenden Sie die weniger beliebte Kombination aus Apache und mod_php. Obwohl es etwas anders funktioniert, sind die Prinzipien dieselben.

Werfen wir einen Blick darauf, wie PHP-FPM Anwendungscode ausfĂŒhrt. Wenn eine Anfrage eingeht, initialisiert PHP-FPM einen untergeordneten PHP-Prozess und ĂŒbergibt die Details der Anfrage als Teil ihres Status (_GET, _POST, _SERVER usw.).

Der Status kann sich wĂ€hrend der AusfĂŒhrung des PHP-Skripts nicht Ă€ndern, daher gibt es nur eine Möglichkeit, einen neuen Satz Eingabedaten zu erhalten: durch Löschen des Prozessspeichers und erneutes Initialisieren.

Dieses AusfĂŒhrungsmodell hat viele Vorteile. Sie mĂŒssen sich nicht zu viele Gedanken ĂŒber den Speicherverbrauch machen, alle Prozesse sind vollstĂ€ndig isoliert, und wenn einer von ihnen „stirbt“, wird er automatisch neu erstellt und hat keine Auswirkungen auf die ĂŒbrigen Prozesse. Allerdings weist dieser Ansatz auch Nachteile auf, die bei der Skalierung der Anwendung auftreten.

Nachteile und Ineffizienzen einer regulÀren PHP-Umgebung

Wenn Sie ein professioneller PHP-Entwickler sind, wissen Sie, wo Sie ein neues Projekt starten mĂŒssen – mit der Wahl eines Frameworks. Es besteht aus AbhĂ€ngigkeitsinjektionsbibliotheken, ORMs, Übersetzungen und Vorlagen. Und natĂŒrlich können alle Benutzereingaben bequem in einem Objekt (Symfony/HttpFoundation oder PSR-7) zusammengefasst werden. Frameworks sind cool!

Aber alles hat seinen Preis. In jedem Framework auf Unternehmensebene mĂŒssen Sie zur Verarbeitung einer einfachen Benutzeranfrage oder eines Zugriffs auf eine Datenbank mindestens Dutzende Dateien laden, zahlreiche Klassen erstellen und mehrere Konfigurationen analysieren. Aber das Schlimmste ist, dass Sie nach Abschluss jeder Aufgabe alles zurĂŒcksetzen und von vorne beginnen mĂŒssen: Der gesamte Code, den Sie gerade initiiert haben, wird unbrauchbar und Sie können mit seiner Hilfe keine weitere Anfrage mehr bearbeiten. ErzĂ€hlen Sie dies jedem Programmierer, der in einer anderen Sprache schreibt, und Sie werden die Verwirrung auf seinem Gesicht sehen.

PHP-Ingenieure suchen seit Jahren nach Möglichkeiten, dieses Problem zu lösen, indem sie clevere Lazy-Loading-Techniken, Mikroframeworks, optimierte Bibliotheken, Cache usw. verwenden. Aber am Ende muss man immer noch die gesamte Anwendung zurĂŒcksetzen und von vorne beginnen . (Anmerkung des Übersetzers: Dieses Problem wird mit der EinfĂŒhrung von teilweise gelöst Vorspannung in PHP 7.4)

Kann PHP mit Go mehr als eine Anfrage ĂŒberstehen?

Es ist möglich, PHP-Skripte zu schreiben, die lĂ€nger als ein paar Minuten (bis zu Stunden oder Tage) leben: zum Beispiel Cron-Tasks, CSV-Parser, Warteschlangenbrecher. Sie arbeiten alle nach dem gleichen Szenario: Sie rufen eine Aufgabe ab, fĂŒhren sie aus und warten auf die nĂ€chste. Der Code bleibt stĂ€ndig im Speicher und spart wertvolle Millisekunden, da zum Laden des Frameworks und der Anwendung viele zusĂ€tzliche Schritte erforderlich sind.

Aber langlebige Skripte zu entwickeln ist nicht einfach. Jeder Fehler beendet den Prozess vollstÀndig, die Diagnose von Speicherlecks ist Àrgerlich und ein F5-Debugging ist nicht mehr möglich.

Mit der Veröffentlichung von PHP 7 hat sich die Situation verbessert: Ein zuverlĂ€ssiger Garbage Collector ist erschienen, der Umgang mit Fehlern ist einfacher geworden und Kernel-Erweiterungen sind jetzt auslaufsicher. Zwar mĂŒssen Ingenieure immer noch vorsichtig mit dem Speicher umgehen und sich der Zustandsprobleme im Code bewusst sein (gibt es eine Sprache, die diese Dinge ignorieren kann?). Dennoch hĂ€lt PHP 7 weniger Überraschungen fĂŒr uns bereit.

Ist es möglich, das Modell der Arbeit mit langlebigen PHP-Skripten zu ĂŒbernehmen und es an trivialere Aufgaben wie die Verarbeitung von HTTP-Anfragen anzupassen und dadurch die Notwendigkeit zu beseitigen, bei jeder Anfrage alles von Grund auf neu zu laden?

Um dieses Problem zu lösen, mussten wir zunÀchst eine Serveranwendung implementieren, die HTTP-Anfragen akzeptieren und diese einzeln an den PHP-Worker umleiten konnte, ohne ihn jedes Mal zu beenden.

Wir wussten, dass wir einen Webserver in reinem PHP (PHP-PM) oder mit einer C-Erweiterung (Swoole) schreiben konnten. Und obwohl jede Methode ihre eigenen VorzĂŒge hat, passten beide Optionen nicht zu uns – wir wollten etwas mehr. Wir brauchten mehr als nur einen Webserver – wir erwarteten eine Lösung, die uns die mit einem „harten Start“ in PHP verbundenen Probleme erspart und die gleichzeitig leicht fĂŒr bestimmte Anwendungen angepasst und erweitert werden kann. Das heißt, wir brauchten einen Anwendungsserver.

Kann Go dabei helfen? Wir wussten, dass dies möglich ist, da die Sprache Anwendungen in einzelne BinĂ€rdateien kompiliert. es ist plattformĂŒbergreifend; verwendet ein eigenes, sehr elegantes Parallelverarbeitungsmodell (ParallelitĂ€t) und eine Bibliothek fĂŒr die Arbeit mit HTTP; Und schließlich stehen uns Tausende von Open-Source-Bibliotheken und -Integrationen zur VerfĂŒgung.

Die Schwierigkeiten bei der Kombination zweier Programmiersprachen

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

Zum Beispiel mit ausgezeichnete Bibliothek Alex Palaestras, es war möglich, Speicher zwischen PHP- und Go-Prozessen zu teilen (Ă€hnlich wie mod_php in Apache). Aber diese Bibliothek verfĂŒgt ĂŒber Funktionen, die ihre Verwendung zur Lösung unseres Problems einschrĂ€nken.

Wir haben uns fĂŒr einen anderen, allgemeineren Ansatz entschieden: die Interaktion zwischen Prozessen ĂŒber Sockets/Pipelines aufzubauen. Dieser Ansatz hat sich in den letzten Jahrzehnten als zuverlĂ€ssig erwiesen und wurde auf Betriebssystemebene gut optimiert.

ZunĂ€chst haben wir ein einfaches BinĂ€rprotokoll fĂŒr den Datenaustausch zwischen Prozessen und den Umgang mit Übertragungsfehlern erstellt. In seiner einfachsten Form Ă€hnelt dieser Protokolltyp Netzstring с Paket-Header mit fester GrĂ¶ĂŸe (in unserem Fall 17 Bytes), das Informationen ĂŒber den Pakettyp, seine GrĂ¶ĂŸe und eine BinĂ€rmaske zur ÜberprĂŒfung der IntegritĂ€t der Daten enthĂ€lt.

Auf der PHP-Seite haben wir verwendet Packfunktionund auf der Go-Seite die Bibliothek Kodierung/binÀr.

Es schien uns, dass ein Protokoll nicht ausreichte – und wir fĂŒgten die Möglichkeit zum Anrufen hinzu net/rpc go-Dienste direkt aus PHP. Dies hat uns spĂ€ter bei der Entwicklung sehr geholfen, da wir Go-Bibliotheken problemlos in PHP-Anwendungen integrieren konnten. Das Ergebnis dieser Arbeit ist beispielsweise in unserem anderen Open-Source-Produkt zu sehen Goridge.

Verteilen von Aufgaben auf mehrere PHP-Worker

Nach der Implementierung des Interaktionsmechanismus begannen wir darĂŒber nachzudenken, wie wir Aufgaben am effizientesten an PHP-Prozesse ĂŒbertragen können. Wenn eine Aufgabe eintrifft, muss der Anwendungsserver einen freien Worker fĂŒr die AusfĂŒhrung auswĂ€hlen. Wenn ein Worker/Prozess mit einem Fehler beendet wird oder „stirbt“, entfernen wir ihn und erstellen einen neuen, um ihn zu ersetzen. Und wenn der Worker/Prozess erfolgreich abgeschlossen wurde, geben wir ihn an den Pool der Worker zurĂŒck, die fĂŒr die AusfĂŒhrung von Aufgaben zur VerfĂŒgung stehen.

RoadRunner: PHP ist nicht dafĂŒr gemacht, zu sterben, und Golang hilft nicht

Um den Pool aktiver Arbeiter zu speichern, haben wir verwendet gepufferter KanalUm unerwartet „tote“ Arbeiter aus dem Pool zu entfernen, haben wir einen Mechanismus zur Verfolgung von Fehlern und ZustĂ€nden von Arbeitern hinzugefĂŒgt.

Als Ergebnis erhielten wir einen funktionierenden PHP-Server, der alle in binÀrer Form dargestellten Anfragen verarbeiten kann.

Damit unsere Anwendung als Webserver funktionieren konnte, mussten wir einen zuverlĂ€ssigen PHP-Standard wĂ€hlen, der alle eingehenden HTTP-Anfragen darstellt. In unserem Fall haben wir einfach verwandeln net/http-Anfrage von Gehe zum Format PSR-7sodass es mit den meisten heute verfĂŒgbaren PHP-Frameworks kompatibel ist.

Da PSR-7 als unverĂ€nderlich gilt (manche wĂŒrden sagen, dass dies technisch gesehen nicht der Fall ist), mĂŒssen Entwickler Anwendungen schreiben, die die Anfrage grundsĂ€tzlich nicht als globale Einheit behandeln. Dies passt gut zum Konzept langlebiger PHP-Prozesse. Unsere endgĂŒltige Implementierung, die noch benannt werden muss, sah folgendermaßen aus:

RoadRunner: PHP ist nicht dafĂŒr gemacht, zu sterben, und Golang hilft nicht

Wir stellen vor: RoadRunner – Hochleistungs-PHP-Anwendungsserver

Unsere erste Testaufgabe war ein API-Backend, das regelmĂ€ĂŸig (viel hĂ€ufiger als ĂŒblich) mit unvorhersehbaren Anfragen ĂŒberhĂ€uft. Obwohl Nginx in den meisten FĂ€llen ausreichend war, kam es regelmĂ€ĂŸig zu 502-Fehlern, da wir das System nicht schnell genug fĂŒr den erwarteten Lastanstieg ausgleichen konnten.

Als Ersatz fĂŒr diese Lösung haben wir Anfang 2018 unseren ersten PHP/Go-Anwendungsserver bereitgestellt. Und hatte sofort eine unglaubliche Wirkung! Wir haben nicht nur den 502-Fehler vollstĂ€ndig beseitigt, sondern konnten auch die Anzahl der Server um zwei Drittel reduzieren, was den Ingenieuren und Produktmanagern eine Menge Geld und Kopfschmerztabletten erspart hat.

Bis Mitte des Jahres hatten wir unsere Lösung verbessert, sie unter der MIT-Lizenz auf GitHub veröffentlicht und benannt RoadRunner, was seine unglaubliche Geschwindigkeit und Effizienz unterstreicht.

Wie RoadRunner Ihren Entwicklungs-Stack verbessern kann

Anwendung RoadRunner ermöglichte es uns, Middleware net/http auf der Go-Seite zu verwenden, um eine JWT-ÜberprĂŒfung durchzufĂŒhren, bevor die Anfrage PHP erreicht, sowie WebSockets und den Aggregatzustand global in Prometheus zu verarbeiten.

Dank des integrierten RPC können Sie die API aller Go-Bibliotheken fĂŒr PHP öffnen, ohne Erweiterungs-Wrapper schreiben zu mĂŒssen. Noch wichtiger ist, dass Sie mit RoadRunner neue Nicht-HTTP-Server bereitstellen können. Beispiele hierfĂŒr sind das AusfĂŒhren von Handlern in PHP AWS Lambda, Erstellen zuverlĂ€ssiger Warteschlangenbrecher und sogar HinzufĂŒgen gRPC zu unseren Anwendungen.

Mit Hilfe der PHP- und Go-Communitys haben wir die StabilitĂ€t der Lösung verbessert, die Anwendungsleistung in einigen Tests um das bis zu 40-fache gesteigert, Debugging-Tools verbessert, die Integration mit dem Symfony-Framework implementiert und UnterstĂŒtzung fĂŒr HTTPS, HTTP/2, Plugins und PSR-17.

Fazit

Manche Leute hĂ€ngen immer noch der altmodischen Vorstellung an, PHP sei eine langsame, umstĂ€ndliche Sprache, die sich nur zum Schreiben von Plugins eigne. WordPressManche wĂŒrden sogar behaupten, PHP habe eine EinschrĂ€nkung: Wenn eine Anwendung groß genug wird, ist es notwendig, eine ausgereiftere Sprache zu wĂ€hlen und die ĂŒber viele Jahre gewachsene Codebasis neu zu schreiben.

Auf all das möchte ich antworten: Denken Sie noch einmal darĂŒber nach. Wir glauben, dass nur Sie EinschrĂ€nkungen fĂŒr PHP festlegen. Sie können Ihr ganzes Leben damit verbringen, von einer Sprache zur anderen zu wechseln und zu versuchen, die perfekte Lösung fĂŒr Ihre BedĂŒrfnisse zu finden, oder Sie beginnen, Sprachen als Werkzeuge zu betrachten. Die vermeintlichen MĂ€ngel einer Sprache wie PHP könnten tatsĂ€chlich der Grund fĂŒr ihren Erfolg sein. Und wenn Sie es mit einer anderen Sprache wie Go kombinieren, werden Sie viel leistungsfĂ€higere Produkte erstellen, als wenn Sie auf die Verwendung einer einzigen Sprache beschrĂ€nkt wĂ€ren.

Nachdem wir mit einer Menge Go und PHP gearbeitet haben, können wir sagen, dass wir sie lieben. Wir haben nicht vor, das eine fĂŒr das andere zu opfern – im Gegenteil, wir werden nach Möglichkeiten suchen, noch mehr Nutzen aus diesem Dual-Stack zu ziehen.

UPD: Wir begrĂŒĂŸen den Schöpfer von RoadRunner und den Co-Autor des Originalartikels – Lachesis

Source: habr.com

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