RoadRunner: PHP nuk është ndërtuar për të vdekur, ose Golang për të shpëtuar

RoadRunner: PHP nuk është ndërtuar për të vdekur, ose Golang për të shpëtuar

Përshëndetje, Habr! Jemi aktivë në Badoo duke punuar në performancën e PHP, meqenëse ne kemi një sistem mjaft të madh në këtë gjuhë dhe çështja e performancës është çështje e kursimit të parave. Më shumë se dhjetë vjet më parë, ne krijuam PHP-FPM për këtë, i cili në fillim ishte një grup arnimesh për PHP, dhe më vonë u bë pjesë e shpërndarjes zyrtare.

Vitet e fundit, PHP ka bërë përparim të madh: mbledhësi i mbeturinave është përmirësuar, niveli i stabilitetit është rritur - sot mund të shkruani demonë dhe skripta jetëgjatë në PHP pa asnjë problem. Kjo i lejoi Spiral Scout të shkonte më tej: RoadRunner, ndryshe nga PHP-FPM, nuk pastron kujtesën midis kërkesave, gjë që jep përfitime shtesë të performancës (megjithëse kjo qasje e ndërlikon procesin e zhvillimit). Aktualisht jemi duke eksperimentuar me këtë mjet, por nuk kemi ende ndonjë rezultat për të ndarë. Për ta bërë më argëtuese pritjen e tyre, Ne po publikojmë një përkthim të njoftimit të RoadRunner nga Spiral Scout.

Qasja nga artikulli është afër nesh: kur zgjidhim problemet tona, ne gjithashtu më së shpeshti përdorim një kombinim të PHP dhe Go, duke marrë përfitimet e të dy gjuhëve dhe duke mos hequr dorë nga njëra në favor të tjetrës.

Enjoy!

Gjatë dhjetë viteve të fundit, ne kemi krijuar aplikacione për kompanitë nga lista Pasuria 500, dhe për bizneset me një audiencë prej jo më shumë se 500 përdoruesish. Gjatë gjithë kësaj kohe, inxhinierët tanë zhvilluan backend-in kryesisht në PHP. Por dy vjet më parë, diçka pati një ndikim të madh jo vetëm në performancën e produkteve tona, por edhe në shkallëzueshmërinë e tyre - ne prezantuam Golang (Go) në grupin tonë të teknologjisë.

Pothuajse menjëherë, zbuluam se Go na lejoi të ndërtonim aplikacione më të mëdha me performancë deri në 40 herë më të shpejtë. Me të, ne mundëm të zgjeronim produktet ekzistuese të shkruara në PHP, duke i përmirësuar ato duke kombinuar avantazhet e të dyja gjuhëve.

Ne do t'ju tregojmë se si një kombinim i Go dhe PHP ndihmon në zgjidhjen e problemeve reale të zhvillimit dhe se si është kthyer në një mjet për ne që mund të eliminojë disa nga problemet që lidhen me Modeli i vdekjes PHP.

Mjedisi juaj i përditshëm i zhvillimit të PHP

Përpara se të flasim për mënyrën se si mund të përdorni Go për të ringjallur modelin e vdekur të PHP-së, le të hedhim një vështrim në mjedisin tuaj standard të zhvillimit të PHP.

Në shumicën e rasteve, ju e ekzekutoni aplikacionin duke përdorur një kombinim të serverit në internet nginx dhe serverit PHP-FPM. E para shërben skedarë statikë dhe ridrejton kërkesa specifike në PHP-FPM, dhe PHP-FPM vetë ekzekuton kodin PHP. Ndoshta po përdorni një kombinim më pak të popullarizuar nga Apache dhe mod_php. Por megjithëse funksionon pak më ndryshe, parimet janë të njëjta.

Le të shohim se si PHP-FPM ekzekuton kodin e aplikacionit. Kur vjen një kërkesë, PHP-FPM inicializon procesin e fëmijës PHP dhe i kalon detajet e kërkesës si pjesë e gjendjes së saj (_GET, _POST, _SERVER, etj.).

Gjendja nuk mund të ndryshojë gjatë ekzekutimit të një skripti PHP, kështu që ekziston vetëm një mënyrë për të marrë një grup të ri të dhënash hyrëse: duke pastruar kujtesën e procesit dhe duke e rifilluar atë.

Ky model ekzekutimi ka shumë përparësi. Nuk duhet të shqetësoheni shumë për konsumin e memories, të gjitha proceset janë plotësisht të izoluara dhe nëse njëri prej tyre vdes, ai do të rikrijohet automatikisht pa ndikuar në pjesën tjetër të proceseve. Por kjo qasje ka edhe disavantazhe që shfaqen kur përpiqeni të shkallëzoni aplikacionin.

Disavantazhet dhe joefikasiteti i një mjedisi të rregullt PHP

Nëse jeni të angazhuar në zhvillim profesional në PHP, atëherë e dini se ku të filloni një projekt të ri - duke zgjedhur një kornizë. Ai përbëhet nga biblioteka për injektimin e varësisë, ORM, përkthime dhe shabllone. Dhe sigurisht, të gjitha të dhënat e përdoruesit mund të vendosen lehtësisht në një objekt (Symfony/HttpFoundation ose PSR-7). Kornizat janë të lezetshme!

Por çdo gjë ka çmimin e vet. Në çdo kornizë të nivelit të ndërmarrjes, për të përpunuar një kërkesë të thjeshtë përdoruesi ose për të hyrë në një bazë të dhënash, do t'ju duhet të ngarkoni të paktën dhjetëra skedarë, të krijoni klasa të shumta dhe të analizoni disa konfigurime. Por gjëja më e keqe është se pas përfundimit të secilës detyrë do t'ju duhet të rivendosni gjithçka dhe të filloni përsëri: i gjithë kodi që sapo keni nisur bëhet i padobishëm, me ndihmën e tij nuk do të përpunoni më një kërkesë tjetër. Tregojani këtë çdo programuesi që shkruan në ndonjë gjuhë tjetër dhe do të shihni hutim në fytyrën e tij.

Inxhinierët e PHP-së kanë shpenzuar vite në kërkim të mënyrave për të zgjidhur këtë problem, duke përdorur teknika të zgjuara të ngarkimit dembel, mikrokorniza, biblioteka të optimizuara, cache, etj. Por në fund, ju duhet të rivendosni të gjithë aplikacionin dhe të filloni përsëri, përsëri dhe përsëri. (Shënim i përkthyesit: ky problem do të zgjidhet pjesërisht me ardhjen e preload në PHP 7.4)

A mund të mbijetojë PHP me Go më shumë se një kërkesë?

Është e mundur të shkruhen skriptet PHP që do të zgjasin më shumë se disa minuta (deri në orë ose ditë): për shembull, detyrat cron, analizuesit CSV, ndërprerësit e radhëve. Ata të gjithë punojnë sipas të njëjtit skenar: ata marrin një detyrë, e ekzekutojnë atë dhe presin për detyrën tjetër. Kodi qëndron në memorie, duke kursyer milisekonda të çmuara pasi kërkohen shumë hapa shtesë për të ngarkuar kornizën dhe aplikacionin.

Por zhvillimi i skenarëve jetëgjatë nuk është aq i lehtë. Çdo gabim e vret plotësisht procesin, diagnostikimi i rrjedhjeve të kujtesës ju çmend dhe nuk mund të përdorni më korrigjimin e F5.

Situata është përmirësuar me lëshimin e PHP 7: është shfaqur një koleksionist i besueshëm i mbeturinave, është bërë më e lehtë për të trajtuar gabimet dhe zgjerimet e kernelit tani mbrohen nga rrjedhjet. Vërtetë, inxhinierët ende duhet të jenë të kujdesshëm me kujtesën dhe të jenë të vetëdijshëm për çështjet e gjendjes në kod (a ka ndonjë gjuhë ku nuk duhet të shqetësohemi për këto gjëra?). E megjithatë, në PHP 7, na presin më pak surpriza.

A është e mundur të merret modeli i punës me skriptet PHP jetëgjata, ta përshtatet me detyra më të parëndësishme si përpunimi i kërkesave HTTP dhe në këtë mënyrë të eliminohet nevoja për të ngarkuar gjithçka nga e para për çdo kërkesë?

Për të zgjidhur këtë problem, fillimisht na duhej të implementonim një aplikacion serveri që mund të pranonte kërkesat HTTP dhe t'i dërgonte ato një nga një te punonjësi i PHP-së pa e vrarë atë çdo herë.

Ne e dinim se mund të shkruanim një server në internet në PHP të pastër (PHP-PM) ose duke përdorur shtesën C (Swoole). Dhe megjithëse secila metodë ka meritat e veta, të dyja opsionet nuk na përshtaten - ne donim diçka më shumë. Ne kishim nevojë për më shumë sesa thjesht një server në internet - shpresonim të gjenim një zgjidhje që mund të na shpëtonte nga problemet që lidhen me "fillimin e vështirë" në PHP, i cili në të njëjtën kohë mund të përshtatej dhe zgjerohej lehtësisht për aplikacione specifike. Kjo do të thotë, na duhej një server aplikacioni.

Mund të ndihmojë Go me këtë? Ne e dinim se mundet sepse gjuha përpilon aplikacionet në binare të vetme; është ndër-platformë; përdor modelin e vet, shumë elegant, të përpunimit paralel (konkurrencë) dhe bibliotekën për të punuar me HTTP; dhe së fundi, mijëra biblioteka dhe integrime me burim të hapur do të jenë të disponueshme për ne.

Vështirësitë e kombinimit të dy gjuhëve programuese

Hapi i parë ishte përcaktimi se si dy ose më shumë aplikacione do të komunikonin me njëri-tjetrin.

Për shembull, duke përdorur bibliotekë e mrekullueshme Alex Palaestras mund të zbatojë ndarjen e memories midis proceseve PHP dhe Go (të ngjashme me mod_php në Apache). Por kjo bibliotekë ka veçori që kufizojnë përdorimin e saj për zgjidhjen e problemit tonë.

Ne vendosëm të përdorim një qasje tjetër, më të zakonshme: të ndërtojmë ndërveprim ndërmjet proceseve përmes prizave/tubacioneve. Kjo qasje ka provuar besueshmërinë e saj gjatë dekadave të fundit dhe është optimizuar mirë në nivelin e sistemit operativ.

Për të filluar, ne krijuam një protokoll të thjeshtë binar për shkëmbimin e të dhënave ndërmjet proceseve dhe trajtimin e gabimeve të transmetimit. Në formën e tij më të thjeshtë, ky lloj protokolli është i ngjashëm me rrjetë с kokën e paketës me madhësi fikse (në rastin tonë 17 byte), e cila përmban informacione për llojin e paketës, madhësinë e saj dhe një maskë binare për të kontrolluar integritetin e të dhënave.

Në anën PHP kemi përdorur funksioni i paketës, dhe në anën Go - një bibliotekë kodim/binar.

Na u duk se një protokoll nuk ishte i mjaftueshëm - kështu që shtuam aftësinë për të thirrur Shkoni shërbimet net/rpc direkt nga PHP. Kjo më vonë na ndihmoi shumë në zhvillim, pasi ne mund të integronim lehtësisht bibliotekat Go në aplikacionet PHP. Rezultati i kësaj pune mund të shihet, për shembull, në produktin tonë tjetër me burim të hapur Gorixh.

Shpërndarja e detyrave nëpër punëtorë të shumtë të PHP

Pas zbatimit të mekanizmit të ndërveprimit, ne filluam të mendojmë se si të transferojmë detyrat në mënyrë më efikase në proceset PHP. Kur arrin një detyrë, serveri i aplikacionit duhet të zgjedhë një punonjës të lirë për ta përfunduar atë. Nëse një punëtor/proces përfundon me një gabim ose "vdes", ne e heqim qafe atë dhe krijojmë një të re për ta zëvendësuar. Dhe nëse punëtori/procesi ka përfunduar me sukses, ne e kthejmë atë në grupin e punëtorëve të disponueshëm për të kryer detyrat.

RoadRunner: PHP nuk është ndërtuar për të vdekur, ose Golang për të shpëtuar

Për të ruajtur një grup punëtorësh aktivë kemi përdorur kanal i buferuar, për të hequr punëtorët e papritur "të vdekur" nga pishina, ne shtuam një mekanizëm për gjurmimin e gabimeve dhe gjendjeve të punëtorëve.

Si rezultat, ne morëm një server PHP që funksiononte i aftë për të përpunuar çdo kërkesë të paraqitur në formë binare.

Në mënyrë që aplikacioni ynë të funksiononte si një server në internet, na duhej të zgjidhnim një standard të besueshëm PHP për të përfaqësuar çdo kërkesë hyrëse HTTP. Në rastin tonë ne thjesht transformoj Kërkesa net/http nga Shko në format PSR-7në mënyrë që të jetë në përputhje me shumicën e kornizave PHP të disponueshme sot.

Për shkak se PSR-7 konsiderohet i pandryshueshëm (disa do të thoshin teknikisht se nuk është), zhvilluesit duhet të shkruajnë aplikacione që nuk e trajtojnë në thelb kërkesën si një entitet global. Kjo përshtatet mirë me konceptin e proceseve PHP jetëgjata. Zbatimi ynë përfundimtar, i cili ende nuk ishte emëruar, dukej kështu:

RoadRunner: PHP nuk është ndërtuar për të vdekur, ose Golang për të shpëtuar

Prezantimi i RoadRunner - server aplikacioni PHP me performancë të lartë

Detyra jonë e parë e testimit ishte mbështetja e API-së, e cila në mënyrë periodike përjetoi shpërthime të papritura kërkesash (shumë më shpesh se zakonisht). Megjithëse nginx ishte i mjaftueshëm në shumicën e rasteve, ne hasëm rregullisht 502 gabime sepse nuk mundëm të balanconim sistemin aq shpejt sa për rritjen e pritshme të ngarkesës.

Për të zëvendësuar këtë zgjidhje, ne vendosëm serverin tonë të parë të aplikacionit PHP/Go në fillim të 2018. Dhe menjëherë morëm një efekt të jashtëzakonshëm! Jo vetëm që e hoqëm plotësisht gabimin 502, por gjithashtu mundëm të reduktonim numrin e serverëve me dy të tretat, duke kursyer shumë para dhe dhimbje koke për inxhinierët dhe menaxherët e produkteve.

Nga mesi i vitit, ne e kishim përsosur zgjidhjen tonë, e publikuam në GitHub nën licencën MIT dhe e quajtëm atë Roadrunner, duke theksuar kështu shpejtësinë dhe efikasitetin e tij të jashtëzakonshëm.

Si mund të përmirësojë RoadRunner Stack-in tuaj të Zhvillimit

Aplikim Roadrunner na lejoi të përdorim Middleware net/http në anën Go për të kryer verifikimin JWT përpara se kërkesa të godasë PHP, si dhe për të trajtuar WebSockets dhe grumbullimin global të shtetit në Prometheus.

Falë RPC-së së integruar, mund të hapni API-në e çdo biblioteke Go për PHP pa shkruar mbështjellës shtesë. Më e rëndësishmja, RoadRunner mund të përdoret për të vendosur serverë të rinj jo-HTTP. Shembujt përfshijnë lëshimin e mbajtësve në PHP AWS Lambda, duke krijuar bllokues të besueshëm të radhëve dhe madje duke shtuar gRPC tek aplikacionet tona.

Me ndihmën e komuniteteve PHP dhe Go, ne kemi rritur stabilitetin e zgjidhjes, kemi rritur performancën e aplikacionit deri në 40 herë në disa teste, kemi përmirësuar mjetet e korrigjimit, kemi implementuar integrimin me kornizën Symfony dhe kemi shtuar mbështetje për HTTPS, HTTP/ 2, shtojcat dhe PSR-17.

Përfundim

Disa njerëz janë ende të kapur në pamjen e vjetëruar të PHP-së si një gjuhë e ngadaltë, e rëndë, e mirë vetëm për të shkruar shtojca të WordPress. Këta njerëz madje mund të thonë se PHP ka një kufizim: kur aplikacioni bëhet mjaft i madh, ju duhet të zgjidhni një gjuhë më "të pjekur" dhe të rishkruani bazën e kodit që është grumbulluar gjatë shumë viteve.

Për të gjitha këto dua t'i përgjigjem: mendoni përsëri. Ne besojmë se vetëm ju mund të vendosni ndonjë kufizim për PHP. Ju mund ta kaloni gjithë jetën tuaj duke kërcyer nga një gjuhë në tjetrën, duke u përpjekur të gjeni përputhjen e përsosur për nevojat tuaja, ose mund të filloni të mendoni për gjuhët si mjete. Mangësitë e perceptuara të një gjuhe si PHP mund të jenë në fakt arsyet e suksesit të saj. Dhe nëse e kombinoni me një gjuhë tjetër si Go, mund të krijoni produkte shumë më të fuqishme sesa nëse do të kufizoheni vetëm në një gjuhë.

Pasi kemi punuar me një kombinim të Go dhe PHP, mund të themi se i duam ato. Ne nuk planifikojmë të sakrifikojmë njërën për tjetrën, por përkundrazi të kërkojmë mënyra për të marrë edhe më shumë vlerë nga ky pirg i dyfishtë.

UPD: Ne mirëpresim krijuesin e RoadRunner dhe bashkëautorin e artikullit origjinal - Lachesis

Burimi: www.habr.com

Shto një koment