RoadRunner: PHP no està creat per morir, ni Golang per al rescat

RoadRunner: PHP no està creat per morir, ni Golang per al rescat

Hola Habr! Estem actius a Badoo treballant en el rendiment de PHP, ja que tenim un sistema bastant gran en aquest idioma i el problema del rendiment és un problema d'estalvi de diners. Fa més de deu anys, vam crear PHP-FPM per a això, que al principi era un conjunt de pedaços per a PHP, i després va entrar a la distribució oficial.

En els darrers anys, PHP ha avançat molt: el col·lector d'escombraries ha millorat, el nivell d'estabilitat ha augmentat; avui podeu escriure dimonis i scripts de llarga vida en PHP sense cap problema. Això va permetre a Spiral Scout anar més enllà: RoadRunner, a diferència de PHP-FPM, no neteja la memòria entre sol·licituds, la qual cosa proporciona un guany de rendiment addicional (tot i que aquest enfocament complica el procés de desenvolupament). Actualment estem experimentant amb aquesta eina, però encara no tenim cap resultat per compartir. Per fer-los més divertit esperar, publiquem la traducció de l'anunci RoadRunner de Spiral Scout.

L'enfocament de l'article ens és proper: a l'hora de resoldre els nostres problemes, també fem servir més sovint un munt de PHP i Go, aconseguint els beneficis dels dos idiomes i no abandonant un a favor de l'altre.

Enjoy!

En els darrers deu anys hem creat aplicacions per a empreses de la llista Fortune 500, i per a empreses amb un públic de no més de 500 usuaris. Durant tot aquest temps, els nostres enginyers han estat desenvolupant el backend principalment en PHP. Però fa dos anys, alguna cosa va tenir un gran impacte no només en el rendiment dels nostres productes, sinó també en la seva escalabilitat: vam introduir Golang (Go) a la nostra pila de tecnologia.

Gairebé immediatament, vam descobrir que Go ens permetia crear aplicacions més grans amb millores de rendiment fins a 40 vegades. Amb ell, vam poder ampliar els nostres productes PHP existents, millorant-los combinant els avantatges dels dos idiomes.

Us explicarem com la combinació de Go i PHP ajuda a resoldre problemes reals de desenvolupament i com s'ha convertit en una eina per a nosaltres que ens pot desfer alguns dels problemes associats amb Model de mort PHP.

El vostre entorn de desenvolupament PHP diari

Abans de parlar de com podeu utilitzar Go per reviure el model de mort de PHP, donem una ullada al vostre entorn de desenvolupament PHP predeterminat.

En la majoria dels casos, executeu la vostra aplicació mitjançant una combinació del servidor web nginx i el servidor PHP-FPM. El primer serveix fitxers estàtics i redirigeix ​​sol·licituds específiques a PHP-FPM, mentre que PHP-FPM executa codi PHP. És possible que utilitzeu la combinació menys popular d'Apache i mod_php. Però tot i que funciona una mica diferent, els principis són els mateixos.

Fem una ullada a com PHP-FPM executa el codi de l'aplicació. Quan arriba una sol·licitud, PHP-FPM inicialitza un procés fill PHP i passa els detalls de la sol·licitud com a part del seu estat (_GET, _POST, _SERVER, etc.).

L'estat no pot canviar durant l'execució d'un script PHP, de manera que l'única manera d'obtenir un nou conjunt de dades d'entrada és esborrant la memòria del procés i inicialitzant-la de nou.

Aquest model d'execució té molts avantatges. No t'has de preocupar massa pel consum de memòria, tots els processos estan completament aïllats, i si un d'ells "mor", es recrearà automàticament i no afectarà la resta de processos. Però aquest enfocament també té desavantatges que apareixen quan s'intenta escalar l'aplicació.

Desavantatges i ineficiències d'un entorn PHP normal

Si sou un desenvolupador professional de PHP, ja sabeu per on començar un nou projecte, amb l'elecció d'un marc. Consisteix en biblioteques d'injecció de dependències, ORM, traduccions i plantilles. I, per descomptat, totes les entrades de l'usuari es poden posar còmodament en un objecte (Symfony/HttpFoundation o PSR-7). Els marcs són genials!

Però tot té el seu preu. En qualsevol marc d'empresa, per processar una sol·licitud d'usuari simple o accedir a una base de dades, haureu de carregar almenys desenes de fitxers, crear nombroses classes i analitzar diverses configuracions. Però el pitjor és que després de completar cada tasca, hauràs de reiniciar-ho tot i començar de nou: tot el codi que acabes d'iniciar es torna inútil, amb la seva ajuda ja no processaràs una altra sol·licitud. Digues-ho a qualsevol programador que escrigui en un altre idioma i veuràs el desconcert a la seva cara.

Els enginyers de PHP fa anys que busquen maneres de resoldre aquest problema, utilitzant tècniques de càrrega mandrosa intel·ligents, microframeworks, biblioteques optimitzades, memòria cau, etc. Però, al final, encara cal restablir tota l'aplicació i tornar a començar, una i altra vegada. (Nota del traductor: aquest problema es resoldrà parcialment amb l'arribada de precàrrega en PHP 7.4)

Pot PHP amb Go sobreviure a més d'una sol·licitud?

És possible escriure scripts PHP que viuen més d'uns quants minuts (fins a hores o dies): per exemple, tasques cron, analitzadors CSV, trencadors de cua. Tots funcionen segons el mateix escenari: recuperen una tasca, l'executen i esperen la següent. El codi resideix a la memòria tot el temps, estalviant mil·lisegons preciosos, ja que es requereixen molts passos addicionals per carregar el marc i l'aplicació.

Però desenvolupar guions de llarga vida no és fàcil. Qualsevol error mata completament el procés, el diagnòstic de fuites de memòria és indignant i la depuració F5 ja no és possible.

La situació ha millorat amb el llançament de PHP 7: ha aparegut un col·lector d'escombraries fiable, s'ha tornat més fàcil gestionar els errors i les extensions del nucli són ara a prova de fuites. És cert que els enginyers encara han de tenir cura amb la memòria i ser conscients dels problemes d'estat en el codi (hi ha un llenguatge que pugui ignorar aquestes coses?). Tot i així, PHP 7 ens espera menys sorpreses.

És possible prendre el model de treballar amb scripts PHP de llarga durada, adaptar-lo a tasques més trivials com processar sol·licituds HTTP i, per tant, desfer-se de la necessitat de carregar-ho tot des de zero amb cada sol·licitud?

Per resoldre aquest problema, primer calia implementar una aplicació de servidor que pogués acceptar peticions HTTP i redirigir-les una per una al treballador PHP sense matar-lo cada vegada.

Sabíem que podríem escriure un servidor web en PHP pur (PHP-PM) o amb una extensió C (Swoole). I tot i que cada mètode té els seus propis mèrits, ambdues opcions no ens van bé: volíem alguna cosa més. Necessitàvem més que un servidor web: esperàvem obtenir una solució que ens pogués salvar dels problemes associats amb un "inici dur" en PHP, que alhora es podria adaptar i ampliar fàcilment per a aplicacions específiques. És a dir, necessitàvem un servidor d'aplicacions.

Go pot ajudar amb això? Sabíem que podria ser perquè el llenguatge compila aplicacions en binaris únics; és multiplataforma; utilitza el seu propi model de processament paral·lel molt elegant (concurrència) i una biblioteca per treballar amb HTTP; i finalment, milers de biblioteques i integracions de codi obert estaran disponibles per a nosaltres.

Les dificultats de combinar dos llenguatges de programació

En primer lloc, calia determinar com dues o més aplicacions es comunicaran entre elles.

Per exemple, utilitzant excel·lent biblioteca Alex Palaestras, va ser possible compartir memòria entre processos PHP i Go (similar a mod_php a Apache). Però aquesta biblioteca té característiques que limiten el seu ús per resoldre el nostre problema.

Vam decidir utilitzar un enfocament diferent i més comú: crear interacció entre processos a través de sòcols / canalitzacions. Aquest enfocament ha demostrat ser fiable durant les últimes dècades i s'ha optimitzat a nivell de sistema operatiu.

Per començar, vam crear un protocol binari senzill per intercanviar dades entre processos i gestionar errors de transmissió. En la seva forma més simple, aquest tipus de protocol és similar a cadena de xarxa с capçalera de paquet de mida fixa (en el nostre cas 17 bytes), que conté informació sobre el tipus de paquet, la seva mida i una màscara binària per comprovar la integritat de les dades.

Al costat de PHP hem utilitzat funció de paquet, i al costat Go, la biblioteca codificació/binari.

Ens va semblar que un protocol no era suficient, i vam afegir la possibilitat de trucar net/rpc go serveis directament des de PHP. Més tard, això ens va ajudar molt en el desenvolupament, ja que podíem integrar fàcilment les biblioteques Go a les aplicacions PHP. El resultat d'aquest treball es pot veure, per exemple, en el nostre altre producte de codi obert Goridge.

Distribució de tasques entre diversos treballadors PHP

Després d'implementar el mecanisme d'interacció, vam començar a pensar com transferir de manera més eficient les tasques als processos PHP. Quan arriba una tasca, el servidor d'aplicacions ha de seleccionar un treballador lliure per completar-la. Si un treballador/procés acaba amb un error o "mor", ens desferrem i en creem un de nou per substituir-lo. I si el treballador/procés s'ha completat amb èxit, el retornem al grup de treballadors disponible per realitzar tasques.

RoadRunner: PHP no està creat per morir, ni Golang per al rescat

Per emmagatzemar el grup de treballadors actius, hem utilitzat canal buffer, per eliminar els treballadors "morts" inesperadament del grup, hem afegit un mecanisme per fer un seguiment dels errors i l'estat dels treballadors.

Com a resultat, vam obtenir un servidor PHP que funcionava capaç de processar qualsevol sol·licitud presentada en forma binària.

Perquè la nostra aplicació comencés a funcionar com a servidor web, vam haver de triar un estàndard PHP fiable per representar qualsevol sol·licitud HTTP entrant. En el nostre cas, només transformar sol·licitud net/http de Vés a format PSR-7de manera que sigui compatible amb la majoria dels frameworks PHP disponibles actualment.

Com que PSR-7 es considera immutable (alguns dirien que tècnicament no ho és), els desenvolupadors han d'escriure aplicacions que en principi no tracten la sol·licitud com una entitat global. Això encaixa molt bé amb el concepte de processos PHP de llarga durada. La nostra implementació final, que encara s'ha de nomenar, va quedar així:

RoadRunner: PHP no està creat per morir, ni Golang per al rescat

Presentació de RoadRunner - Servidor d'aplicacions PHP d'alt rendiment

La nostra primera tasca de prova va ser un backend d'API, que esclata periòdicament de manera imprevisible (molt més sovint de l'habitual). Tot i que nginx va ser suficient en la majoria dels casos, ens trobem regularment amb errors 502 perquè no vam poder equilibrar el sistema amb prou rapidesa per a l'augment esperat de càrrega.

Per substituir aquesta solució, vam desplegar el nostre primer servidor d'aplicacions PHP/Go a principis del 2018. I de seguida va tenir un efecte increïble! No només ens vam desfer de l'error 502 per complet, sinó que vam poder reduir el nombre de servidors en dos terços, estalviant molts diners i pastilles de mal de cap per als enginyers i gestors de producte.

A mitjans d'any, havíem millorat la nostra solució, l'havíem publicat a GitHub amb la llicència MIT i l'hem nomenat avanç, destacant així la seva increïble velocitat i eficiència.

Com RoadRunner pot millorar la vostra pila de desenvolupament

Aplicació avanç ens va permetre utilitzar Middleware net/http al costat Go per realitzar la verificació JWT abans que la sol·licitud arribés a PHP, així com gestionar WebSockets i l'estat agregat globalment a Prometheus.

Gràcies a l'RPC integrat, podeu obrir l'API de qualsevol biblioteca Go per a PHP sense escriure embolcalls d'extensió. Més important encara, amb RoadRunner podeu implementar nous servidors que no siguin HTTP. Els exemples inclouen l'execució de controladors en PHP AWS Lambda, creant interruptors de cua fiables i fins i tot afegint gRPC a les nostres aplicacions.

Amb l'ajuda de les comunitats PHP i Go, hem millorat l'estabilitat de la solució, hem augmentat el rendiment de l'aplicació fins a 40 vegades en algunes proves, hem millorat les eines de depuració, hem implementat la integració amb el marc de Symfony i hem afegit suport per HTTPS, HTTP/2, connectors i PSR-17.

Conclusió

Algunes persones encara estan atrapades en la noció obsoleta de PHP com un llenguatge lent i poc manejable que només és bo per escriure connectors per a WordPress. Aquestes persones fins i tot podrien dir que PHP té aquesta limitació: quan l'aplicació es fa prou gran, heu de triar un llenguatge més "madur" i reescriure la base de codi acumulada durant molts anys.

A tot això vull respondre: pensa-ho de nou. Creiem que només vostè estableix les restriccions per a PHP. Pots passar tota la teva vida fent la transició d'un idioma a un altre, intentant trobar la combinació perfecta per a les teves necessitats, o pots començar a pensar en els idiomes com a eines. Els suposats defectes d'un llenguatge com PHP poden ser realment la raó del seu èxit. I si el combineu amb un altre idioma com Go, creareu productes molt més potents que si us limiteu a utilitzar qualsevol idioma.

Després d'haver treballat amb un munt de Go i PHP, podem dir que els estimem. No tenim previst sacrificar l'un per l'altre; al contrari, buscarem maneres d'obtenir encara més valor d'aquesta pila dual.

UPD: donem la benvinguda al creador de RoadRunner i al coautor de l'article original - Lachesis

Font: www.habr.com

Afegeix comentari