RoadRunner: PHP is nie gebou om te sterf nie, of Golang tot die redding

RoadRunner: PHP is nie gebou om te sterf nie, of Golang tot die redding

Haai Habr! Ons is aktief by Badoo werk aan PHP prestasie, aangesien ons 'n redelike groot stelsel in hierdie taal het en die prestasiekwessie 'n geldbesparingskwessie is. Meer as tien jaar gelede het ons PHP-FPM hiervoor geskep, wat eers 'n stel pleisters vir PHP was, en later die amptelike verspreiding betree het.

In onlangse jare het PHP groot vordering gemaak: die vullisverwyderaar het verbeter, die vlak van stabiliteit het toegeneem - vandag kan jy sonder enige probleme demone en langlewende skrifte in PHP skryf. Dit het Spiral Scout toegelaat om verder te gaan: RoadRunner, anders as PHP-FPM, maak nie geheue skoon tussen versoeke nie, wat 'n bykomende prestasiewins gee (alhoewel hierdie benadering die ontwikkelingsproses bemoeilik). Ons eksperimenteer tans met hierdie instrument, maar ons het nog geen resultate om te deel nie. Om dit lekkerder te maak om vir hulle te wag, ons publiseer die vertaling van die RoadRunner-aankondiging van Spiral Scout.

Die benadering van die artikel is na aan ons: wanneer ons ons probleme oplos, gebruik ons ​​ook meestal 'n klomp PHP en Go, om die voordele van albei tale te kry en nie die een ten gunste van die ander te laat vaar nie.

Geniet dit!

In die afgelope tien jaar het ons aansoeke vir maatskappye vanaf die lys geskep Fortune 500, en vir besighede met 'n gehoor van nie meer as 500 gebruikers nie. Al hierdie tyd het ons ingenieurs die backend hoofsaaklik in PHP ontwikkel. Maar twee jaar gelede het iets 'n groot impak gehad, nie net op die werkverrigting van ons produkte nie, maar ook op hul skaalbaarheid - ons het Golang (Go) in ons tegnologiestapel ingebring.

Byna onmiddellik het ons ontdek dat Go ons toegelaat het om groter toepassings te bou met tot 40x prestasieverbeterings. Daarmee kon ons bestaande produkte wat in PHP geskryf is, uitbrei en dit verbeter deur die voordele van albei tale te kombineer.

Ons sal jou vertel hoe die kombinasie van Go en PHP help om werklike ontwikkelingsprobleme op te los en hoe dit vir ons 'n hulpmiddel geword het wat van sommige van die probleme wat verband hou met PHP sterwende model.

Jou daaglikse PHP-ontwikkelingsomgewing

Voordat ons praat oor hoe jy Go kan gebruik om die PHP-sterwende model te laat herleef, kom ons kyk na jou verstek PHP-ontwikkelingsomgewing.

In die meeste gevalle loop jy jou toepassing deur 'n kombinasie van die nginx-webbediener en die PHP-FPM-bediener te gebruik. Eersgenoemde bedien statiese lêers en herlei spesifieke versoeke na PHP-FPM, terwyl PHP-FPM self PHP-kode uitvoer. Jy gebruik dalk die minder gewilde kombinasie van Apache en mod_php. Maar hoewel dit 'n bietjie anders werk, is die beginsels dieselfde.

Kom ons kyk na hoe PHP-FPM toepassingskode uitvoer. Wanneer 'n versoek inkom, inisialiseer PHP-FPM 'n PHP-kindproses en gee die besonderhede van die versoek deur as deel van sy toestand (_GET, _POST, _SERVER, ens.).

Die toestand kan nie verander tydens PHP-skripuitvoering nie, so daar is net een manier om 'n nuwe stel invoerdata te kry: deur die prosesgeheue skoon te maak en dit te herinitialiseer.

Hierdie uitvoeringsmodel het baie voordele. Jy hoef jou nie te veel oor geheueverbruik te bekommer nie, alle prosesse is heeltemal geïsoleer, en as een van hulle "sterf", sal dit outomaties herskep word en dit sal nie die res van die prosesse beïnvloed nie. Maar hierdie benadering het ook nadele wat voorkom wanneer u die toepassing probeer skaal.

Nadele en ondoeltreffendheid van 'n gereelde PHP-omgewing

As jy 'n professionele PHP-ontwikkelaar is, dan weet jy waar om 'n nuwe projek te begin - met die keuse van 'n raamwerk. Dit bestaan ​​uit afhanklikheidsinspuitingsbiblioteke, ORM's, vertalings en sjablone. En natuurlik kan alle gebruikersinvoer gerieflik in een voorwerp geplaas word (Symfony/HttpFoundation of PSR-7). Raamwerke is gaaf!

Maar alles het sy prys. In enige ondernemingsvlakraamwerk, om 'n eenvoudige gebruikerversoek of toegang tot 'n databasis te verwerk, sal jy ten minste dosyne lêers moet laai, talle klasse moet skep en verskeie konfigurasies moet ontleed. Maar die ergste is dat u na die voltooiing van elke taak alles moet terugstel en weer moet begin: al die kode wat u pas begin het, word nutteloos, met die hulp daarvan sal u nie meer 'n ander versoek verwerk nie. Vertel dit vir enige programmeerder wat in 'n ander taal skryf, en jy sal verbystering op sy gesig sien.

PHP-ingenieurs soek al jare na maniere om hierdie probleem op te los, met behulp van slim lui-laai-tegnieke, mikroraamwerke, geoptimaliseerde biblioteke, kas, ens. Maar op die ou end moet jy steeds die hele toepassing terugstel en oor, weer en weer begin. (Vertalersnota: hierdie probleem sal gedeeltelik opgelos word met die koms van voorbelading in PHP 7.4)

Kan PHP met Go meer as een versoek oorleef?

Dit is moontlik om PHP-skrifte te skryf wat langer as 'n paar minute leef (tot ure of dae): byvoorbeeld cron-take, CSV-ontleders, toubrekers. Hulle werk almal volgens dieselfde scenario: hulle haal 'n taak op, voer dit uit en wag vir die volgende een. Die kode is heeltyd in die geheue, en spaar kosbare millisekondes aangesien daar baie bykomende stappe is wat nodig is om die raamwerk en toepassing te laai.

Maar om langlewende skrifte te ontwikkel is nie maklik nie. Enige fout maak die proses heeltemal dood, die diagnose van geheuelekkasies is woedend, en F5-ontfouting is nie meer moontlik nie.

Die situasie het verbeter met die vrystelling van PHP 7: 'n betroubare vullisverwyderaar het verskyn, dit het makliker geword om foute te hanteer, en kernuitbreidings is nou lekvry. True, ingenieurs moet steeds versigtig wees met geheue en bewus wees van staatskwessies in kode (is daar 'n taal wat hierdie dinge kan ignoreer?). Tog het PHP 7 minder verrassings vir ons in die vooruitsig.

Is dit moontlik om die model te neem om met langlewende PHP-skrifte te werk, dit aan te pas by meer onbenullige take soos die verwerking van HTTP-versoeke, en daardeur ontslae te raak van die behoefte om alles van nuuts af met elke versoek te laai?

Om hierdie probleem op te los, moes ons eers 'n bedienertoepassing implementeer wat HTTP-versoeke kon aanvaar en hulle een vir een na die PHP-werker herlei sonder om dit elke keer dood te maak.

Ons het geweet dat ons 'n webbediener in suiwer PHP (PHP-PM) of 'n C-uitbreiding (Swoole) kon skryf. En hoewel elke metode sy eie meriete het, het albei opsies nie by ons gepas nie - ons wou iets meer hê. Ons het meer as net 'n webbediener nodig gehad - ons het verwag om 'n oplossing te kry wat ons kon red van die probleme wat verband hou met 'n "harde begin" in PHP, wat terselfdertyd maklik aangepas en uitgebrei kon word vir spesifieke toepassings. Dit wil sê, ons het 'n toepassingsbediener nodig gehad.

Kan Go hiermee help? Ons het geweet dit kan omdat die taal toepassings saamstel in enkele binaries; dit is kruisplatform; gebruik sy eie, baie elegante, parallelle verwerkingsmodel (sameloop) en 'n biblioteek om met HTTP te werk; en uiteindelik sal duisende oopbronbiblioteke en integrasies vir ons beskikbaar wees.

Die probleme om twee programmeertale te kombineer

Eerstens was dit nodig om te bepaal hoe twee of meer toepassings met mekaar sal kommunikeer.

Byvoorbeeld, die gebruik van uitstekende biblioteek Alex Palaestras, dit was moontlik om geheue te deel tussen PHP- en Go-prosesse (soortgelyk aan mod_php in Apache). Maar hierdie biblioteek het kenmerke wat die gebruik daarvan beperk om ons probleem op te los.

Ons het besluit om 'n ander, meer algemene benadering te gebruik: om interaksie tussen prosesse deur voetstukke / pyplyne te bou. Hierdie benadering het die afgelope dekades bewys dat dit betroubaar is en is goed geoptimaliseer op die bedryfstelselvlak.

Om mee te begin, het ons 'n eenvoudige binêre protokol geskep vir die uitruil van data tussen prosesse en die hantering van transmissiefoute. In sy eenvoudigste vorm is hierdie tipe protokol soortgelyk aan netstring с vaste grootte pakkie kop (in ons geval 17 grepe), wat inligting bevat oor die tipe pakkie, sy grootte en 'n binêre masker om die integriteit van die data na te gaan.

Aan die PHP-kant het ons gebruik pak funksie, en aan die Go-kant, die biblioteek enkodering/binêr.

Dit het vir ons gelyk of een protokol nie genoeg was nie – en ons het die vermoë bygevoeg om te bel net/rpc go dienste direk vanaf PHP. Later het dit ons baie gehelp met ontwikkeling, aangesien ons Go-biblioteke maklik in PHP-toepassings kon integreer. Die resultaat van hierdie werk kan byvoorbeeld in ons ander oopbronproduk gesien word Goridge.

Verspreiding van take oor verskeie PHP-werkers

Nadat ons die interaksiemeganisme geïmplementeer het, het ons begin dink aan die doeltreffendste manier om take na PHP-prosesse oor te dra. Wanneer 'n taak aankom, moet die toepassingbediener 'n gratis werker kies om dit uit te voer. As 'n werker/proses uitgaan met 'n fout of "sterf", raak ons ​​daarvan ontslae en skep 'n nuwe een om dit te vervang. En as die werker/proses suksesvol voltooi is, stuur ons dit terug na die poel werkers wat beskikbaar is om take uit te voer.

RoadRunner: PHP is nie gebou om te sterf nie, of Golang tot die redding

Om die poel aktiewe werkers te stoor, het ons gebruik gebufferde kanaal, om onverwags "dooie" werkers uit die swembad te verwyder, het ons 'n meganisme bygevoeg om foute en toestande van werkers op te spoor.

As gevolg hiervan het ons 'n werkende PHP-bediener gekry wat in staat is om enige versoeke wat in binêre vorm aangebied word, te verwerk.

Om ons toepassing as 'n webbediener te laat werk, moes ons 'n betroubare PHP-standaard kies om enige inkomende HTTP-versoeke te verteenwoordig. In ons geval, ons net transformeer net/http versoek van Gaan na formaat PSR-7sodat dit versoenbaar is met die meeste van die PHP-raamwerke wat vandag beskikbaar is.

Omdat PSR-7 as onveranderlik beskou word (sommige sou sê tegnies is dit nie), moet ontwikkelaars toepassings skryf wat die versoek in beginsel nie as 'n globale entiteit hanteer nie. Dit pas mooi by die konsep van langlewende PHP-prosesse. Ons finale implementering, wat nog genoem moet word, het soos volg gelyk:

RoadRunner: PHP is nie gebou om te sterf nie, of Golang tot die redding

Bekendstelling van RoadRunner - hoë werkverrigting PHP toepassingsbediener

Ons eerste toetstaak was 'n API-agtergrond, wat periodiek onvoorspelbare versoeke inbars (baie meer dikwels as gewoonlik). Alhoewel nginx in die meeste gevalle voldoende was, het ons gereeld 502 foute teëgekom omdat ons nie die stelsel vinnig genoeg kon balanseer vir die verwagte toename in las nie.

Om hierdie oplossing te vervang, het ons vroeg in 2018 ons eerste PHP/Go-toepassingsbediener ontplooi. En het dadelik 'n ongelooflike effek gekry! Ons het nie net heeltemal van die 502-fout ontslae geraak nie, maar ons kon die aantal bedieners met twee derdes verminder, wat baie geld en hoofpynpille vir ingenieurs en produkbestuurders bespaar het.

Teen die middel van die jaar het ons ons oplossing verbeter, dit op GitHub gepubliseer onder die MIT-lisensie en dit genoem RoadRunner, wat die ongelooflike spoed en doeltreffendheid daarvan beklemtoon.

Hoe RoadRunner jou ontwikkelingstapel kan verbeter

Aansoek RoadRunner het ons toegelaat om Middleware net/http aan die Go-kant te gebruik om JWT-verifikasie uit te voer voordat die versoek PHP bereik, sowel as om WebSockets en totale toestand wêreldwyd in Prometheus te hanteer.

Danksy die ingeboude RPC, kan jy die API van enige Go-biblioteke vir PHP oopmaak sonder om uitbreidingsomhulsels te skryf. Nog belangriker, met RoadRunner kan jy nuwe nie-HTTP-bedieners ontplooi. Voorbeelde sluit in lopende hanteerders in PHP AWS Lambda, skep betroubare toubrekers, en voeg selfs by gRPC aan ons toepassings.

Met die hulp van die PHP- en Go-gemeenskappe het ons die stabiliteit van die oplossing verbeter, toepassingswerkverrigting tot 40 keer in sommige toetse verbeter, ontfoutingsnutsmiddels verbeter, integrasie met die Symfony-raamwerk geïmplementeer, en ondersteuning vir HTTPS, HTTP/2 bygevoeg, plugins en PSR-17.

Gevolgtrekking

Sommige mense is steeds vasgevang in die verouderde idee van PHP as 'n stadige, onhandelbare taal wat net goed is om plugins vir WordPress te skryf. Hierdie mense kan selfs sê dat PHP so 'n beperking het: wanneer die toepassing groot genoeg word, moet jy 'n meer "volwasse" taal kies en die kodebasis wat oor baie jare opgehoop is, herskryf.

Op dit alles wil ek antwoord: dink weer. Ons glo dat slegs jy enige beperkings vir PHP stel. Jy kan jou hele lewe spandeer om van een taal na 'n ander oor te skakel, probeer om die perfekte pasmaat vir jou behoeftes te vind, of jy kan begin om aan tale as hulpmiddels te dink. Die vermeende gebreke van 'n taal soos PHP kan eintlik die rede vir sy sukses wees. En as jy dit kombineer met 'n ander taal soos Go, dan sal jy baie kragtiger produkte skep as wanneer jy beperk was tot die gebruik van enige een taal.

Nadat ons met 'n klomp Go en PHP gewerk het, kan ons sê dat ons van hulle hou. Ons beplan nie om die een vir die ander op te offer nie – inteendeel, ons sal maniere soek om nog meer waarde uit hierdie dubbele stapel te kry.

UPD: ons verwelkom die skepper van RoadRunner en die mede-outeur van die oorspronklike artikel - Lachesis

Bron: will.com

Voeg 'n opmerking