RoadRunner: PHP ni ustvarjen za smrt ali Golang na pomoč

RoadRunner: PHP ni ustvarjen za smrt ali Golang na pomoč

Hej Habr! Aktivni smo na Badoo delo na zmogljivosti PHP, saj imamo precej velik sistem v tem jeziku in je težava z zmogljivostjo težava prihranka denarja. Pred več kot desetimi leti smo za to ustvarili PHP-FPM, ki je bil sprva komplet popravkov za PHP, kasneje pa je prišel v uradno distribucijo.

V zadnjih letih je PHP zelo napredoval: izboljšal se je zbiralnik smeti, povečala se je stopnja stabilnosti - danes lahko v PHP brez težav pišete demone in dolgožive skripte. To je omogočilo, da je Spiral Scout šel dlje: RoadRunner, za razliko od PHP-FPM, ne čisti pomnilnika med zahtevami, kar daje dodatno povečanje zmogljivosti (čeprav ta pristop otežuje razvojni proces). Trenutno eksperimentiramo s tem orodjem, vendar še nimamo rezultatov, ki bi jih lahko delili. Da bo čakanje nanje bolj zabavno, objavljamo prevod oznanila RoadRunner podjetja Spiral Scout.

Pristop iz članka nam je blizu: pri reševanju svojih težav najpogosteje uporabljamo tudi kup PHP in Go, pri čemer izkoristimo prednosti obeh jezikov in ne opustimo enega v korist drugega.

Uživajte!

V zadnjih desetih letih smo izdelali aplikacije za podjetja s seznama Fortune 500in za podjetja z največ 500 uporabniki. Ves ta čas so naši inženirji razvijali zaledje predvsem v PHP. Toda pred dvema letoma je nekaj močno vplivalo ne le na zmogljivost naših izdelkov, ampak tudi na njihovo razširljivost – v naš tehnološki sklad smo uvedli Golang (Go).

Skoraj takoj smo odkrili, da nam Go omogoča izdelavo večjih aplikacij z do 40-krat izboljšano zmogljivostjo. Z njim smo lahko razširili naše obstoječe izdelke PHP in jih izboljšali z združevanjem prednosti obeh jezikov.

Povedali vam bomo, kako kombinacija Go in PHP pomaga pri reševanju resničnih razvojnih težav in kako se je za nas spremenila v orodje, ki se lahko znebi nekaterih težav, povezanih z PHP umirajoči model.

Vaše vsakodnevno razvojno okolje PHP

Preden se pogovorimo o tem, kako lahko uporabite Go za oživitev umirajočega modela PHP, si poglejmo vaše privzeto razvojno okolje PHP.

V večini primerov zaženete aplikacijo s kombinacijo spletnega strežnika nginx in strežnika PHP-FPM. Prvi služi statičnim datotekam in preusmerja specifične zahteve na PHP-FPM, medtem ko PHP-FPM sam izvaja kodo PHP. Morda uporabljate manj priljubljeno kombinacijo Apache in mod_php. Čeprav deluje nekoliko drugače, so načela enaka.

Oglejmo si, kako PHP-FPM izvaja kodo aplikacije. Ko pride zahteva, PHP-FPM inicializira podrejeni proces PHP in posreduje podrobnosti zahteve kot del svojega stanja (_GET, _POST, _SERVER itd.).

Stanje se med izvajanjem skripta PHP ne more spremeniti, zato je edini način, da dobite nov niz vhodnih podatkov, tako da počistite pomnilnik procesa in ga znova inicializirate.

Ta model izvedbe ima številne prednosti. Ni vam treba preveč skrbeti za porabo pomnilnika, vsi procesi so popolnoma izolirani in če eden od njih "umre", se bo samodejno znova ustvaril in ne bo vplival na preostale procese. Toda ta pristop ima tudi slabosti, ki se pojavijo pri poskusu povečanja aplikacije.

Slabosti in neučinkovitosti običajnega okolja PHP

Če ste profesionalni razvijalec PHP, potem veste, kje začeti nov projekt - z izbiro ogrodja. Sestavljen je iz knjižnic za vstavljanje odvisnosti, ORM-jev, prevodov in predlog. In seveda je mogoče vse uporabniške vnose priročno spraviti v en objekt (Symfony/HttpFoundation ali PSR-7). Okviri so kul!

A vse ima svojo ceno. V katerem koli ogrodju na ravni podjetja boste morali za obdelavo preproste uporabniške zahteve ali dostopa do baze podatkov naložiti vsaj desetine datotek, ustvariti številne razrede in razčleniti več konfiguracij. Najslabše pa je, da boste morali po opravljeni vsaki nalogi vse ponastaviti in začeti znova: vsa koda, ki ste jo pravkar sprožili, postane neuporabna, z njeno pomočjo ne boste več obdelali druge zahteve. Povejte to kateremu koli programerju, ki piše v kakšnem drugem jeziku, in videli boste začudenje na njegovem obrazu.

Inženirji PHP že leta iščejo načine za rešitev tega problema, pri čemer uporabljajo pametne tehnike lenega nalaganja, mikrookvirja, optimizirane knjižnice, predpomnilnik itd. Toda na koncu morate še vedno ponastaviti celotno aplikacijo in začeti znova in znova. (Opomba prevajalca: ta problem bo delno rešen s prihodom preload v PHP 7.4)

Ali lahko PHP z Go preživi več kot eno zahtevo?

Možno je napisati skripte PHP, ki živijo dlje kot nekaj minut (do ur ali dni): na primer opravila cron, razčlenjevalniki CSV, razbijalci čakalnih vrst. Vsi delajo po istem scenariju: pridobijo nalogo, jo izvedejo in čakajo na naslednjo. Koda je ves čas v pomnilniku in prihrani dragocene milisekunde, saj je za nalaganje ogrodja in aplikacije potrebnih veliko dodatnih korakov.

Toda razvijanje dolgoživih skriptov ni enostavno. Vsaka napaka popolnoma uniči proces, diagnosticiranje puščanja pomnilnika je jezno in odpravljanje napak F5 ni več mogoče.

Stanje se je izboljšalo z izdajo PHP 7: pojavil se je zanesljiv zbiralnik smeti, postalo je lažje obravnavati napake in razširitve jedra so zdaj neprepustne. Res je, da morajo biti inženirji še vedno previdni pri pomnilniku in se zavedati težav s stanjem v kodi (ali obstaja jezik, ki lahko te stvari prezre?). Kljub temu nam PHP 7 pripravlja manj presenečenj.

Ali je mogoče vzeti model dela z dolgoživimi skripti PHP, ga prilagoditi bolj trivialnim opravilom, kot je obdelava zahtev HTTP, in se s tem znebiti potrebe, da bi z vsako zahtevo naložili vse od začetka?

Da bi rešili to težavo, smo morali najprej implementirati strežniško aplikacijo, ki bi lahko sprejela zahteve HTTP in jih eno za drugo preusmerila na delavca PHP, ne da bi ga vsakič uničila.

Vedeli smo, da lahko napišemo spletni strežnik v čistem PHP (PHP-PM) ali z uporabo razširitve C (Swoole). In čeprav ima vsaka metoda svoje prednosti, nam obe možnosti nista ustrezali - želeli smo nekaj več. Potrebovali smo več kot le spletni strežnik – pričakovali smo, da bomo dobili rešitev, ki bi nas lahko rešila težav, povezanih s “težkim zagonom” v PHP-ju, ki bi jo bilo hkrati enostavno prilagoditi in razširiti za specifične aplikacije. Se pravi, potrebovali smo aplikacijski strežnik.

Ali lahko Go pomaga pri tem? Vedeli smo, da lahko, ker jezik prevaja aplikacije v posamezne dvojiške datoteke; je medplatformski; uporablja lasten, zelo eleganten model vzporednega procesiranja (sočasnost) in knjižnico za delo s HTTP; in končno nam bo na voljo na tisoče odprtokodnih knjižnic in integracij.

Težave pri kombiniranju dveh programskih jezikov

Najprej je bilo treba ugotoviti, kako bosta dve ali več aplikacij med seboj komunicirali.

Na primer z uporabo odlična knjižnica Alex Palaestras, je bilo možno deliti pomnilnik med procesoma PHP in Go (podobno kot mod_php v Apacheju). Toda ta knjižnica ima funkcije, ki omejujejo njeno uporabo za reševanje našega problema.

Odločili smo se za drugačen, pogostejši pristop: zgraditi interakcijo med procesi prek vtičnic / cevovodov. Ta pristop se je v zadnjih desetletjih izkazal za zanesljivega in je bil dobro optimiziran na ravni operacijskega sistema.

Za začetek smo ustvarili preprost binarni protokol za izmenjavo podatkov med procesi in obravnavanje napak pri prenosu. V najpreprostejši obliki je ta vrsta protokola podobna netstring с glava paketa fiksne velikosti (v našem primeru 17 bajtov), ​​ki vsebuje podatke o vrsti paketa, njegovi velikosti in binarno masko za preverjanje celovitosti podatkov.

Na strani PHP smo uporabili funkcija pakiranja, na strani Go pa knjižnica kodiranje/binarno.

Zdelo se nam je, da en protokol ni dovolj - in dodali smo možnost klicanja storitve net/rpc go neposredno iz PHP-ja. Kasneje nam je to zelo pomagalo pri razvoju, saj smo Go knjižnice zlahka integrirali v PHP aplikacije. Rezultat tega dela je na primer viden v našem drugem odprtokodnem izdelku Gorige.

Porazdelitev nalog na več delavcev PHP

Po implementaciji interakcijskega mehanizma smo začeli razmišljati o najučinkovitejšem načinu prenosa nalog v PHP procese. Ko prispe naloga, mora aplikacijski strežnik izbrati prostega delavca za njeno izvedbo. Če delavec/proces zapusti z napako ali "umre", se ga znebimo in ustvarimo novega, ki ga nadomesti. In če se je delavec/proces uspešno zaključil, ga vrnemo v skupino delavcev, ki so na voljo za opravljanje nalog.

RoadRunner: PHP ni ustvarjen za smrt ali Golang na pomoč

Za shranjevanje bazena aktivnih delavcev smo uporabili medpomnilniški kanal, smo za odstranitev nepričakovano "mrtvih" delavcev iz bazena dodali mehanizem za sledenje napakam in stanjem delavcev.

Kot rezultat smo dobili delujoč strežnik PHP, ki lahko obdela vse zahteve, predstavljene v binarni obliki.

Da bi naša aplikacija začela delovati kot spletni strežnik, smo morali izbrati zanesljiv PHP standard za predstavljanje vseh dohodnih zahtev HTTP. V našem primeru smo samo preoblikovati net/http zahteva iz Pojdi na format PSR-7tako da je združljiv z večino ogrodij PHP, ki so danes na voljo.

Ker PSR-7 velja za nespremenljivega (nekateri bi rekli, da tehnično ni), morajo razvijalci pisati aplikacije, ki zahteve načeloma ne obravnavajo kot globalno entiteto. To se lepo ujema s konceptom dolgoživih procesov PHP. Naša končna izvedba, ki še ni imenovana, je izgledala takole:

RoadRunner: PHP ni ustvarjen za smrt ali Golang na pomoč

Predstavljamo RoadRunner - visoko zmogljiv aplikacijski strežnik PHP

Naša prva testna naloga je bilo zaledje API-ja, ki občasno nepredvidljivo poči (veliko pogosteje kot običajno). Čeprav je nginx v večini primerov zadostoval, smo redno naleteli na napake 502, ker nismo mogli dovolj hitro uravnotežiti sistema za pričakovano povečanje obremenitve.

Da bi nadomestili to rešitev, smo v začetku leta 2018 uvedli naš prvi aplikacijski strežnik PHP/Go. In takoj dobil neverjeten učinek! Ne samo, da smo se popolnoma znebili napake 502, ampak smo lahko zmanjšali število strežnikov za dve tretjini, s čimer smo prihranili veliko denarja in tablete proti glavobolu za inženirje in produktne menedžerje.

Do sredine leta smo našo rešitev izboljšali, jo objavili na GitHubu pod licenco MIT in jo poimenovali Roadrunner, s čimer poudarjajo njegovo neverjetno hitrost in učinkovitost.

Kako lahko RoadRunner izboljša vaš razvojni sklad

Uporaba Roadrunner nam je omogočil, da uporabimo Middleware net/http na strani Go za izvedbo preverjanja JWT, preden zahteva doseže PHP, ter globalno obravnavamo WebSockets in agregatno stanje v Prometheusu.

Zahvaljujoč vgrajenemu RPC lahko odprete API katere koli knjižnice Go za PHP brez pisanja ovojov razširitev. Še pomembneje pa je, da lahko s RoadRunnerjem uvedete nove strežnike, ki niso HTTP. Primeri vključujejo izvajanje rokovalcev v PHP AWS Lambda, ustvarjanje zanesljivih razbijalcev čakalnih vrst in celo dodajanje gRPC na naše aplikacije.

S pomočjo skupnosti PHP in Go smo izboljšali stabilnost rešitve, povečali zmogljivost aplikacije do 40-krat pri nekaterih testih, izboljšali orodja za odpravljanje napak, implementirali integracijo z ogrodjem Symfony in dodali podporo za HTTPS, HTTP/2, vtičniki in PSR-17.

Zaključek

Nekateri ljudje so še vedno ujeti v zastarelo predstavo o PHP kot počasnem, okornem jeziku, ki je primeren le za pisanje vtičnikov za WordPress. Ti ljudje bi lahko celo rekli, da ima PHP takšno omejitev: ko aplikacija postane dovolj velika, morate izbrati bolj »zrel« jezik in prepisati osnovo kode, ki se je kopičila v mnogih letih.

Na vse to želim odgovoriti: premislite znova. Verjamemo, da samo vi postavljate kakršne koli omejitve za PHP. Lahko celo življenje prehajate iz enega jezika v drugega in poskušate najti popolnega ujemanja za svoje potrebe ali pa o jezikih začnete razmišljati kot o orodjih. Domnevne pomanjkljivosti jezika, kot je PHP, so lahko dejansko razlog za njegov uspeh. In če ga kombinirate z drugim jezikom, kot je Go, potem boste ustvarili veliko močnejše izdelke, kot če bi bili omejeni na uporabo enega samega jezika.

Ker smo delali s kupom Go in PHP, lahko rečemo, da jih obožujemo. Ne nameravamo žrtvovati enega zaradi drugega - ravno nasprotno, iskali bomo načine, kako iz tega dvojnega sklada pridobiti še večjo vrednost.

UPD: pozdravljamo ustvarjalca RoadRunnerja in soavtorja izvirnega članka - Lachesis

Vir: www.habr.com

Dodaj komentar