RoadRunner: PHP nie je stavané na to, aby zomrelo, alebo aby zachránilo Golang

RoadRunner: PHP nie je stavané na to, aby zomrelo, alebo aby zachránilo Golang

Čau Habr! Sme aktívni na Badoo pracuje na výkone PHP, keďže máme v tomto jazyku pomerne rozsiahly systém a problém s výkonom je otázkou šetrenia peňazí. Pred viac ako desiatimi rokmi sme na to vytvorili PHP-FPM, čo bola najskôr sada záplat pre PHP a neskôr vstúpila do oficiálnej distribúcie.

V posledných rokoch urobilo PHP veľký pokrok: zlepšil sa garbage collector, zvýšila sa úroveň stability - dnes môžete v PHP bez problémov písať démonov a skripty s dlhou životnosťou. To umožnilo Spiral Scout ísť ďalej: RoadRunner, na rozdiel od PHP-FPM, nečistí pamäť medzi požiadavkami, čo poskytuje ďalší nárast výkonu (hoci tento prístup komplikuje proces vývoja). V súčasnosti s týmto nástrojom experimentujeme, ale zatiaľ nemáme žiadne výsledky, o ktoré by sme sa mohli podeliť. Aby bolo čakanie na nich zábavnejšie, zverejňujeme preklad oznámenia RoadRunner od Spiral Scout.

Prístup z článku je nám blízky: pri riešení našich problémov tiež najčastejšie používame veľa PHP a Go, čím získavame výhody oboch jazykov a neopúšťame jeden v prospech druhého.

Užite si to!

Za posledných desať rokov sme zo zoznamu vytvorili aplikácie pre firmy Fortune 500a pre firmy s publikom maximálne 500 používateľov. Celý ten čas naši inžinieri vyvíjali backend hlavne v PHP. Pred dvoma rokmi však malo niečo veľký vplyv nielen na výkon našich produktov, ale aj na ich škálovateľnosť – zaviedli sme Golang (Go) do nášho technologického zásobníka.

Takmer okamžite sme zistili, že Go nám umožňuje vytvárať väčšie aplikácie s až 40-násobným zvýšením výkonu. Vďaka tomu sme mohli rozšíriť naše existujúce produkty PHP a vylepšiť ich kombináciou výhod oboch jazykov.

Prezradíme vám, ako kombinácia Go a PHP pomáha riešiť skutočné vývojové problémy a ako sa pre nás zmenila na nástroj, ktorý vás dokáže zbaviť niektorých problémov spojených s PHP umierajúci model.

Vaše každodenné vývojové prostredie PHP

Predtým, ako si povieme, ako môžete použiť Go na oživenie modelu umierania PHP, pozrime sa na vaše predvolené vývojové prostredie PHP.

Vo väčšine prípadov spúšťate svoju aplikáciu pomocou kombinácie webového servera nginx a servera PHP-FPM. Prvý z nich obsluhuje statické súbory a presmeruje špecifické požiadavky na PHP-FPM, zatiaľ čo PHP-FPM sám spúšťa kód PHP. Možno používate menej populárnu kombináciu Apache a mod_php. No hoci to funguje trochu inak, princípy sú rovnaké.

Poďme sa pozrieť na to, ako PHP-FPM spúšťa aplikačný kód. Keď príde požiadavka, PHP-FPM inicializuje podriadený proces PHP a odovzdá podrobnosti žiadosti ako súčasť jej stavu (_GET, _POST, _SERVER atď.).

Stav sa nemôže zmeniť počas vykonávania PHP skriptu, takže jediný spôsob, ako získať novú sadu vstupných údajov, je vymazanie pamäte procesu a jej opätovná inicializácia.

Tento model vykonávania má mnoho výhod. So spotrebou pamäte sa nemusíte príliš obávať, všetky procesy sú úplne izolované a ak jeden z nich „zomrie“, automaticky sa vytvorí znova a neovplyvní to ostatné procesy. Tento prístup má však aj nevýhody, ktoré sa objavia pri pokuse o škálovanie aplikácie.

Nevýhody a neefektívnosť bežného prostredia PHP

Ak ste profesionálny vývojár PHP, potom viete, kde začať nový projekt - s výberom rámca. Pozostáva z knižníc na vkladanie závislostí, ORM, prekladov a šablón. A, samozrejme, všetky vstupy používateľa možno pohodlne vložiť do jedného objektu (Symfony/HttpFoundation alebo PSR-7). Rámce sú skvelé!

Ale všetko má svoju cenu. V akomkoľvek rámci podnikovej úrovne budete musieť na spracovanie jednoduchej požiadavky používateľa alebo prístupu k databáze načítať aspoň desiatky súborov, vytvoriť množstvo tried a analyzovať niekoľko konfigurácií. Najhoršie však je, že po dokončení každej úlohy budete musieť všetko resetovať a začať odznova: všetok kód, ktorý ste práve iniciovali, sa stane zbytočným, s jeho pomocou už nespracujete ďalšiu požiadavku. Povedzte to každému programátorovi, ktorý píše v inom jazyku, a na jeho tvári uvidíte zmätok.

PHP inžinieri už roky hľadajú spôsoby, ako vyriešiť tento problém, využívajúc šikovné techniky lenivého načítania, mikrorámce, optimalizované knižnice, vyrovnávaciu pamäť atď. Nakoniec však musíte celú aplikáciu resetovať a začať znova a znova. (Poznámka prekladateľa: tento problém bude čiastočne vyriešený príchodom predpätie v PHP 7.4)

Dokáže PHP s Go prežiť viac ako jednu požiadavku?

Je možné písať PHP skripty, ktoré žijú dlhšie ako niekoľko minút (až hodiny alebo dni): napríklad úlohy cron, analyzátory CSV, prerušovače frontov. Všetci pracujú podľa rovnakého scenára: získajú úlohu, vykonajú ju a čakajú na ďalšiu. Kód je neustále v pamäti, čo šetrí drahocenné milisekúndy, pretože na načítanie rámca a aplikácie je potrebných veľa ďalších krokov.

Ale vývoj dlhotrvajúcich skriptov nie je jednoduchý. Akákoľvek chyba úplne zabije proces, diagnostika úniku pamäte je zúrivá a ladenie F5 už nie je možné.

S vydaním PHP 7 sa situácia zlepšila: objavil sa spoľahlivý garbage collector, ľahšie sa riešia chyby a rozšírenia jadra sú teraz odolné voči úniku. Je pravda, že inžinieri si stále musia dávať pozor na pamäť a byť si vedomí problémov so stavom v kóde (existuje jazyk, ktorý dokáže tieto veci ignorovať?). Napriek tomu má PHP 7 pre nás pripravených menej prekvapení.

Je možné vziať model práce s dlhodobými PHP skriptami, prispôsobiť ho triviálnejším úlohám, ako je spracovanie HTTP požiadaviek, a zbaviť sa tak potreby načítať všetko od začiatku s každou požiadavkou?

Na vyriešenie tohto problému sme najprv potrebovali implementovať serverovú aplikáciu, ktorá by mohla prijímať HTTP požiadavky a presmerovať ich jednu po druhej na PHP pracovníka bez toho, aby ho zakaždým zabil.

Vedeli sme, že môžeme napísať webový server v čistom PHP (PHP-PM) alebo pomocou rozšírenia C (Swoole). A hoci každá metóda má svoje výhody, obe možnosti nám nevyhovovali - chceli sme niečo viac. Potrebovali sme viac ako len webový server – očakávali sme, že dostaneme riešenie, ktoré nás ušetrí od problémov spojených s „tvrdým štartom“ v PHP, ktoré sa zároveň dá ľahko prispôsobiť a rozšíriť pre konkrétne aplikácie. To znamená, že sme potrebovali aplikačný server.

Môže s tým Go pomôcť? Vedeli sme, že môže, pretože tento jazyk kompiluje aplikácie do jednotlivých binárnych súborov; je multiplatformový; používa vlastný, veľmi elegantný, model paralelného spracovania (súbežnosť) a knižnicu pre prácu s HTTP; a nakoniec nám budú k dispozícii tisíce knižníc a integrácií s otvoreným zdrojom.

Ťažkosti pri kombinovaní dvoch programovacích jazykov

V prvom rade bolo potrebné určiť, ako budú dve a viac aplikácií medzi sebou komunikovať.

Napríklad pomocou výborná knižnica Alex Palaestras, bolo možné zdieľať pamäť medzi PHP a Go procesmi (podobne ako mod_php v Apache). Ale táto knižnica má funkcie, ktoré obmedzujú jej použitie na riešenie nášho problému.

Rozhodli sme sa použiť iný, bežnejší prístup: vybudovať interakciu medzi procesmi prostredníctvom zásuviek / potrubí. Tento prístup sa v posledných desaťročiach ukázal ako spoľahlivý a bol dobre optimalizovaný na úrovni operačného systému.

Na začiatok sme vytvorili jednoduchý binárny protokol na výmenu dát medzi procesmi a riešenie chýb prenosu. Vo svojej najjednoduchšej forme je tento typ protokolu podobný sieťový reťazec с hlavička paketu s pevnou veľkosťou (v našom prípade 17 bajtov), ​​ktorý obsahuje informácie o type paketu, jeho veľkosti a binárnu masku na kontrolu integrity dát.

Na strane PHP, ktorú sme použili funkcia baleniaa na strane Prejsť knižnicu kódovanie/binárne.

Zdalo sa nám, že jeden protokol nestačí – a pridali sme možnosť volať net/rpc go služby priamo z PHP. Neskôr nám to veľmi pomohlo pri vývoji, keďže sme mohli ľahko integrovať knižnice Go do PHP aplikácií. Výsledok tejto práce je možné vidieť napríklad na našom ďalšom open-source produkte Goridge.

Rozdelenie úloh medzi viacerých pracovníkov PHP

Po implementácii interakčného mechanizmu sme začali uvažovať o najefektívnejšom spôsobe prenosu úloh do procesov PHP. Keď príde úloha, aplikačný server si musí vybrať voľného pracovníka, ktorý ju vykoná. Ak pracovník/proces odíde s chybou alebo „zomrie“, zbavíme sa ho a vytvoríme nový, ktorý ho nahradí. A ak bol pracovník/proces úspešne dokončený, vrátime ho do skupiny pracovníkov, ktorí sú k dispozícii na vykonávanie úloh.

RoadRunner: PHP nie je stavané na to, aby zomrelo, alebo aby zachránilo Golang

Na uloženie bazéna aktívnych pracovníkov sme použili kanál s vyrovnávacou pamäťou, na odstránenie nečakane „mŕtvych“ pracovníkov z fondu sme pridali mechanizmus na sledovanie chýb a stavov pracovníkov.

Výsledkom je, že máme funkčný PHP server schopný spracovať akékoľvek požiadavky prezentované v binárnej forme.

Aby naša aplikácia začala fungovať ako webový server, museli sme zvoliť spoľahlivý PHP štandard, ktorý bude reprezentovať všetky prichádzajúce HTTP požiadavky. V našom prípade len my transformovať net/http žiadosť z Prejsť na formát PSR-7takže je kompatibilný s väčšinou rámcov PHP, ktoré sú dnes k dispozícii.

Pretože PSR-7 je považovaný za nemenný (niekto by povedal, že technicky nie je), vývojári musia písať aplikácie, ktoré v zásade nepovažujú požiadavku za globálnu entitu. To pekne zapadá do konceptu dlhodobých procesov PHP. Naša konečná implementácia, ktorá ešte nebola pomenovaná, vyzerala takto:

RoadRunner: PHP nie je stavané na to, aby zomrelo, alebo aby zachránilo Golang

Predstavujeme RoadRunner - vysokovýkonný aplikačný server PHP

Našou prvou testovacou úlohou bol backend API, ktorý periodicky nepredvídateľne praská (oveľa častejšie ako zvyčajne). Aj keď bol nginx vo väčšine prípadov dostatočný, pravidelne sme sa stretávali s chybami 502, pretože sme nedokázali dostatočne rýchlo vyvážiť systém pre očakávané zvýšenie zaťaženia.

Aby sme toto riešenie nahradili, začiatkom roka 2018 sme nasadili náš prvý aplikačný server PHP/Go. A okamžite to malo neuveriteľný efekt! Nielenže sme sa úplne zbavili chyby 502, ale dokázali sme znížiť počet serverov o dve tretiny, čím sme inžinierom a produktovým manažérom ušetrili veľa peňazí a piluliek na bolesť hlavy.

Do polovice roka sme naše riešenie vylepšili, zverejnili na GitHub pod licenciou MIT a pomenovali Roadrunner, čím sa zdôraznila jeho neuveriteľná rýchlosť a efektívnosť.

Ako môže RoadRunner zlepšiť váš vývojový balík

Aplikácia Roadrunner nám umožnilo použiť Middleware net/http na strane Go na vykonanie overenia JWT predtým, ako požiadavka dosiahne PHP, ako aj na globálne spracovanie WebSockets a agregovaného stavu v Prometheus.

Vďaka vstavanému RPC môžete otvoriť API ľubovoľných knižníc Go pre PHP bez písania obalov rozšírení. Ešte dôležitejšie je, že s RoadRunner môžete nasadiť nové servery bez HTTP. Príklady zahŕňajú spustenie obslužných programov v PHP AWS Lambda, vytváranie spoľahlivých prerušovačov frontov a dokonca aj pridávanie gRPC do našich aplikácií.

Pomocou komunít PHP a Go sme zlepšili stabilitu riešenia, zvýšili výkon aplikácií až 40-krát v niektorých testoch, zlepšili nástroje na ladenie, implementovali integráciu s frameworkom Symfony a pridali podporu pre HTTPS, HTTP/2, pluginy a PSR-17.

Záver

Niektorí ľudia sú stále chytení v zastaranej predstave PHP ako pomalého a nepraktického jazyka vhodného len na písanie pluginov pre WordPress. Títo ľudia by dokonca mohli povedať, že PHP má také obmedzenie: keď je aplikácia dostatočne veľká, musíte si vybrať „vyspelejší“ jazyk a prepísať kódovú základňu nahromadenú počas mnohých rokov.

Na toto všetko chcem odpovedať: zamyslite sa znova. Veríme, že len vy nastavujete akékoľvek obmedzenia pre PHP. Môžete stráviť celý svoj život prechodom z jedného jazyka do druhého, snažiť sa nájsť dokonalú zhodu pre vaše potreby, alebo môžete začať myslieť na jazyky ako na nástroje. Predpokladané nedostatky jazyka ako PHP môžu byť v skutočnosti dôvodom jeho úspechu. A ak to skombinujete s iným jazykom, ako je Go, vytvoríte oveľa výkonnejšie produkty, ako keby ste sa obmedzili na používanie akéhokoľvek jedného jazyka.

Po práci s množstvom Go a PHP môžeme povedať, že ich milujeme. Neplánujeme obetovať jeden pre druhého – práve naopak, budeme hľadať spôsoby, ako z tohto dual stacku vyťažiť ešte väčšiu hodnotu.

UPD: vítame tvorcu RoadRunner a spoluautora pôvodného článku - Lachesis

Zdroj: hab.com

Pridať komentár