Heim  >  Artikel  >  Backend-Entwicklung  >  Geboren für Geschwindigkeit: die Kombination aus PHP und Golang – RoadRunner

Geboren für Geschwindigkeit: die Kombination aus PHP und Golang – RoadRunner

青灯夜游
青灯夜游nach vorne
2022-09-23 19:40:455744Durchsuche

Geboren für Geschwindigkeit: die Kombination aus PHP und Golang – RoadRunner

Seit einem Jahrzehnt entwickeln wir Apps für Fortune-500-Unternehmen und Unternehmen mit 500 Benutzern oder weniger. In der Vergangenheit haben unsere Ingenieure hauptsächlich PHP zur Entwicklung des Backends verwendet. Doch vor zwei Jahren traten einige Probleme auf, die nicht nur die Leistung unserer Produkte, sondern auch deren Skalierbarkeit stark beeinträchtigten – deshalb haben wir Golang (Go) in unseren Technologie-Stack eingeführt.

Fast gleichzeitig stellten wir fest, dass wir mit Go nicht nur größere Anwendungen erstellen, sondern auch die Leistung um das bis zu 40-fache steigern konnten. Damit sind wir in der Lage, bestehende in PHP geschriebene Produkte zu erweitern und zu verbessern, indem wir das Beste aus beiden Sprachen kombinieren.

Wir erklären Ihnen anhand umfangreicher Go- und PHP-Erfahrung, wie Sie damit echte Entwicklungsprobleme lösen und wie wir es in ein Tool verwandeln können, um einige der mit dem „PHP Death Model“ verbundenen Probleme zu beseitigen.

Normale PHP-Entwicklungsumgebung

# Bevor wir erklären, wie Go das PHP-Death-Modell verbessert, wollen wir zunächst die herkömmliche PHP-Entwicklungsumgebung verstehen.

Normalerweise laufen Anwendungen auf Nginx und PHP-FPM. Nginx verarbeitet statische Anfragen, während dynamische Anfragen an PHP-FPM umgeleitet werden, das den PHP-Code ausführt. Vielleicht verwenden Sie Apache und mod_php, aber sie haben das gleiche Prinzip und unterscheiden sich nur geringfügig in ihrer Funktionsweise.

Sehen Sie, wie PHP-FPM Code ausführt. Wenn eine Anfrage empfangen wird, initialisiert PHP-FPM den PHP-Unterprozess und leitet die Anfragedetails als Teil seines Status (_GET, _POST, _SERVER usw.) an ihn weiter.

Während der Ausführung eines PHP-Skripts kann der Status nicht geändert werden, daher gibt es nur eine Möglichkeit, einen neuen Satz Eingabedaten zu erhalten: den Prozessspeicher löschen und ihn erneut initialisieren.

Dieses Leistungsmodell hat viele Vorteile. Sie müssen sich nicht zu viele Gedanken über den Speicherverbrauch machen, alle Prozesse sind vollständig isoliert. Wenn einer der Prozesse „stirbt“, wird er automatisch neu erstellt und hat keine Auswirkungen auf andere Prozesse. Dieser Ansatz weist jedoch Nachteile auf, wenn Sie versuchen, Ihre Anwendung zu skalieren.

Nachteile und Ineffizienzen einer typischen PHP-Umgebung

Wenn Sie professionell in PHP entwickeln, wissen Sie, wo Sie bei der Erstellung eines neuen Projekts anfangen müssen – bei der Auswahl eines Frameworks. Es handelt sich um eine Bibliothek für Abhängigkeitsinjektion, ORM, Transformationen und Vorlagenmethoden. Natürlich können alle vom Benutzer eingegebenen Daten bequem in einem einzigen Objekt (Symfony / HttpFoundation oder PSR-7) platziert werden. Diese Rahmen sind großartig!

Aber alles hat seinen Preis. Um eine einfache Benutzeranfrage zu bearbeiten oder auf eine Datenbank zuzugreifen, müssen Sie in jedem Unternehmensframework mindestens ein paar Dutzend Dateien laden, viele Klassen erstellen und mehrere Konfigurationen analysieren. Aber das Schlimmste ist, dass Sie nach Abschluss jeder Aufgabe alles zurücksetzen und neu starten müssen: Der gesamte Code, den Sie gerade gestartet haben, wird unbrauchbar und Sie können mit seiner Hilfe keine weitere Anfrage mehr bearbeiten. Erzählen Sie dies jedem Programmierer, der in anderen Sprachen schreibt – und Sie werden die Verwirrung auf seinem Gesicht sehen.

Seit Jahren suchen PHP-Ingenieure nach Möglichkeiten, dieses Problem zu lösen, indem sie Lazy-Loading-Technologie, Mikroframes, Optimierungsbibliotheken, Caching usw. verwenden. Aber irgendwann müssen Sie trotzdem die gesamte Anwendung aufgeben und von vorne beginnen *(Anmerkung des Übersetzers: Mit dem Aufkommen des Vorladens in PHP7.4 wird dieses Problem teilweise gelöst)

Kann ein PHP-Prozess mehrere Anfragen verarbeiten?

Sie können PHP-Skripte schreiben, die länger als ein paar Minuten (bis zu Stunden oder Tage) dauern: z. B. Cron-Jobs, CSV-Parser, Warteschlangenhandler. Alle diese Jobs folgen einem Muster: Sie bekommen eine Aufgabe, bearbeiten sie und bekommen dann die nächste Aufgabe. Der Code befindet sich im Speicher, sodass zusätzliche Vorgänge zum Laden von Frameworks und Anwendungen vermieden werden und wertvolle Zeit gespart wird.

Aber die Entwicklung von Skripten mit langer Laufzeit ist nicht so einfach. Bei Fehlern wird der Prozess abgebrochen, ein Speicherüberlauf führt zum Absturz und F5 kann nicht zum Debuggen des Programms verwendet werden.

Seit PHP 7 hat sich alles verbessert: Es gibt einen zuverlässigen Garbage Collector, die Fehlerbehandlung ist einfacher geworden und Erweiterungen des Kernels können Speicherlecks vermeiden. Ja, Ingenieure müssen sich immer noch sorgfältig mit der Frage des Speichers und der Erinnerung an den Status im Code befassen (welche Sprache erlaubt es Ihnen, diesen Dingen keine Beachtung zu schenken?). Natürlich gibt es in PHP 7 nicht viele Überraschungen.

Ist es möglich, ein Modell residenter PHP-Skripte für trivialere Aufgaben wie die Verarbeitung von HTTP-Anfragen zu übernehmen, wodurch die Notwendigkeit entfällt, für jede Anfrage alles von Grund auf herunterzuladen?

Um dieses Problem zu lösen, müssen Sie zunächst eine Serveranwendung implementieren, die HTTP-Anfragen empfangen und diese einzeln an den PHP-Worker umleiten kann, anstatt ihn jedes Mal zu beenden.

Wir wissen, dass wir Webserver in reinem PHP (PHP-PM) oder mit C-Erweiterungen (Swoole) schreiben können. Obwohl jeder Ansatz seine Vorzüge hat, hat keine der beiden Optionen für uns funktioniert – ich wollte etwas mehr. Wir brauchten mehr als nur einen Webserver – wir wollten eine Lösung, die es uns ermöglicht, die Probleme zu vermeiden, die mit „Neustarts“ in PHP einhergehen, und die gleichzeitig leicht an spezifische Anwendungen anpassbar und erweiterbar ist. Das heißt, wir benötigen einen Anwendungsserver.

Kann Go helfen, dieses Problem zu lösen? Wir wissen, dass dies möglich ist, weil die Sprache die Anwendung in eine einzige Binärdatei kompiliert; sie verwendet ein eigenes Parallelverarbeitungsmodell (Parallelität) und schließlich können wir weitere Open-Source-Bibliotheken in unsere integrieren Programme.

Schwierigkeiten beim Zusammenführen zweier Programmiersprachen

Zunächst muss ermittelt werden, wie zwei oder mehr Anwendungen miteinander kommunizieren können.

Zum Beispiel kann mit der go-php-Bibliothek von Alex Palaestras eine gemeinsame Speichernutzung zwischen PHP- und Go-Prozessen (wie mod_php in Apache) erreicht werden. Die Funktionalität dieser Bibliothek schränkt jedoch unsere Verwendung zur Lösung von Problemen ein.

Wir haben uns für einen anderen, gängigeren Ansatz entschieden: die Strukturierung der Interaktion zwischen Prozessen mithilfe von Sockets/Pipelines. Dieser Ansatz hat sich im letzten Jahrzehnt als zuverlässig erwiesen und ist auf Betriebssystemebene gut optimiert.

Zuerst 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 einem Netstring mit einem Paketheader fester Größe (17 Bytes in unserem Beispiel), der Informationen über den Pakettyp enthält. Seine Größe und Binärmaskeninformationen werden zur Überprüfung der Daten verwendet Integrität.

Auf der PHP-Seite haben wir die pack-Funktion verwendet, und auf der Go-Seite haben wir die encoding/binary-Bibliothek verwendet.

Ein Protokoll war für uns etwas veraltet und wir haben die Möglichkeit hinzugefügt, net /rpc Go-Dienste direkt aus PHP aufzurufen. Diese Funktion hat uns bei der späteren Entwicklung sehr geholfen, da wir Go-Bibliotheken problemlos in PHP-Anwendungen integrieren konnten. Die Ergebnisse dieser Arbeit sind in einem weiteren unserer Open-Source-Produkte zu sehen: Goridge.

Aufgaben auf mehrere PHP-Worker verteilen

Nachdem der Interaktionsmechanismus implementiert wurde, begannen wir darüber nachzudenken, wie wir Aufgaben besser an den PHP-Prozess übertragen können. Wenn eine Aufgabe eintrifft, muss der Anwendungsserver einen inaktiven Worker auswählen, der sie ausführt. Wenn der Arbeitsprozess mit einem Fehler endet oder „stirbt“, löschen wir ihn und erstellen einen neuen. Wenn der Worker-Prozess erfolgreich ausgeführt wird, geben wir ihn an den Worker-Pool zurück, wo er zum Ausführen von Aufgaben verwendet werden kann.

Geboren für Geschwindigkeit: die Kombination aus PHP und Golang – RoadRunner

Um den Pool aktiver Worker-Prozesse zu speichern, verwenden wir einen Pufferkanal. Um unerwartet „tote“ Worker-Prozesse aus dem Pool zu löschen, haben wir einen Mechanismus zur Verfolgung von Fehlern und Worker-Prozessstatus hinzugefügt.

Endlich haben wir einen funktionierenden PHP-Server, der jede in Binärform gerenderte Anfrage verarbeiten kann.

Damit unsere Anwendung als Webserver arbeiten kann, müssen wir einen zuverlässigen PHP-Standard auswählen, um alle eingehenden HTTP-Anfragen zu verarbeiten. In unserem Fall konvertieren wir einfach eine einfache Netz-/http-Anfrage von Go in das PSR-7-Format, sodass sie mit den meisten heute verfügbaren PHP-Frameworks kompatibel ist.

Da PSR-7 als unveränderlich gilt (manche würden sagen, technisch gesehen nicht), müssen Entwickler Anwendungen schreiben, die Anfragen grundsätzlich nicht als globale Einheiten behandeln. Dies steht voll und ganz im Einklang mit dem Konzept der PHP-residenten Prozesse. Unsere endgültige Implementierung (die noch keinen Namen erhalten hat) sieht so aus:

Geboren für Geschwindigkeit: die Kombination aus PHP und Golang – RoadRunner

RoadRunner – HochPerformance PHP Application Server

Unsere erste Testaufgabe war ein API-Backend, auf dem in regelmäßigen Abständen unvorhersehbare Burst-Anfragen (häufiger als üblich) auftraten. Während die Nginx-Funktionen in den meisten Fällen ausreichend sind, treten häufig 502-Fehler auf, da das System bei erwarteten Laststeigerungen nicht schnell ausgeglichen werden kann.

Um dieses Problem zu lösen, haben wir Anfang 2018 unseren ersten PHP/Go-Anwendungsserver bereitgestellt. Und sofort erstaunliche Ergebnisse erzielt! Wir haben nicht nur 502-Fehler vollständig eliminiert, sondern auch die Anzahl der Server um zwei Drittel reduziert, was eine Menge Geld gespart und den Ingenieuren und Produktmanagern Kopfschmerzen bereitet hat.

Mitte des Jahres haben wir unsere Lösung verbessert und sie auf GitHub unter der MIT-Lizenz veröffentlicht und ihr den Namen RoadRunner gegeben, was ihre erstaunliche Geschwindigkeit und Effizienz hervorhebt. Wie

RoadRunner Ihren Entwicklungs-Stack verbessern kann. Die Verwendung von

RoadRunner ermöglicht es uns, die Middleware Net/http auf der Go-Seite zu verwenden, sogar eine JWT-Validierung durchzuführen, bevor die Anfrage in PHP geht, sowie WebSocket und zu verarbeiten Globals im Prometheus-Aggregationsstatus.

Dank des integrierten RPC können Sie die API jeder Go-Bibliothek in PHP öffnen, ohne ein Erweiterungspaket schreiben zu müssen. Darüber hinaus können Sie mit RoadRunner neue Server bereitstellen, die sich von HTTP unterscheiden. Beispiele hierfür sind das Ausführen von AWS Lambda-Prozessoren in PHP, das Erstellen leistungsstarker Warteschlangenselektoren und sogar das Hinzufügen von gRPC zu unseren Anwendungen.

Durch die Verwendung von PHP und Go wurde die Lösung stetig verbessert, wodurch die Anwendungsleistung in einigen Tests um das 40-fache verbessert, die Debugging-Tools verbessert, die Integration mit dem Symfony-Framework implementiert und Unterstützung für HTTPS, HTTP /2-Plug-in und hinzugefügt wurden PSR-17-Unterstützung.

Fazit

Manche Leute sind immer noch an das veraltete Konzept von PHP gebunden und denken, dass PHP eine langsame, umständliche Sprache ist, die sich nur zum Schreiben von Plugins unter WordPress eignet. Diese Leute gehen sogar so weit zu sagen, dass PHP eine Einschränkung hat: Wenn die Anwendung groß genug wird, muss man eine „ausgereiftere“ Sprache wählen und die über die Jahre angesammelte Codebasis neu schreiben.

Meine Antwort auf diese Fragen lautet: Denken Sie noch einmal darüber nach. Wir glauben, dass nur Sie PHP einige Grenzen gesetzt haben. Sie können Ihr Leben damit verbringen, von einer Sprache zur anderen zu wechseln und zu versuchen, diejenige zu finden, die Ihren Bedürfnissen perfekt entspricht, oder Sie können Sprachen als Werkzeuge betrachten. Bei einer Sprache wie PHP können ihre vermeintlichen Mängel der wahre Grund für ihren Erfolg sein. Wenn Sie es mit einer anderen Sprache wie Go kombinieren, erstellen Sie ein leistungsfähigeres Produkt als nur die Verwendung einer Sprache.

Nachdem wir Go und PHP abwechselnd verwendet haben, können wir sagen, dass wir sie lieben. Wir werden nicht das eine für das andere opfern, sondern Wege finden, mehr aus dieser dualen Architektur herauszuholen.

Englische Originaladresse: https://sudonull.com/post/6470-RoadRunner-PHP-is-not-created-to-die-or-Golang-to-the-rescue


Übersetzungsadresse: https: //learnku.com/php/t/61733

Empfohlenes Lernen: „

PHP-Video-Tutorial

Das obige ist der detaillierte Inhalt vonGeboren für Geschwindigkeit: die Kombination aus PHP und Golang – RoadRunner. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:learnku.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen