
Përshëndetje, Habr! Ne në Badoo po punojmë aktivisht , pasi kemi një sistem të madh në këtë gjuhë dhe çështja e performancës është një çështje e kursimit të parave. Më shumë se dhjetë vjet më parë krijuam PHP-FPM, i cili në fillim ishte një grup patch-e për PHP, e më pas u bë pjesë e shpërndarjes zyrtare.
Gjatë viteve të fundit, PHP ka avancuar shumë: grumbulluesi i plehrave është përmirësuar, niveli i stabilitetit është rritur - sot në PHP mund të shkruhen lehtësisht demonë dhe skenarë afatgjatë. Kjo i ka dhënë mundësinë Spiral Scout të shkojë më tej: RoadRunner, ndryshe nga PHP-FPM, nuk pastron memorjen mes kërkesave, çka ofron një përfitim të shtuar në performancë (edhe pse ky qasje e nd complicon procesin e zhvillimit). Tani po eksperimentohemi me këtë mjet, por ende nuk kemi rezultate që mund të ndajnë. Që pritja të jetë më e këndshme, po publikojmë përkthimin e njoftimit të RoadRunner nga Spiral Scout.
Qasja nga artikulli na përshtatet: në zgjidhjen e detyrave tona gjithashtu shpesh përdorim çiftin PHP dhe Go, duke përfituar nga të dy gjuhët dhe pa hequr dorë nga njera në favor të tjetrës.
Gëzoni!
Gjatë dhjetë viteve të fundit kemi krijuar aplikacione edhe për kompanitë nga lista , dhe për bizneset me një audiencë prej më pak se 500 përdoruesish. Gjatë gjithë kësaj kohe inxhinierët tanë kanë zhvilluar backend kryesisht në PHP. Por dy vjet më parë diçka ndodhi që nd profoundly ndikoi jo vetëm në performancën e produkteve tona, por edhe në shkallëzueshmërinë e tyre - ne kemi futur Golang (Go) në kodin tonë teknologjik.
Pothuajse menjëherë zbuluam se Go na lejon të krijojmë aplikacione më të mëdha me një rritje të performancës deri në 40 herë. Me të, ne arritëm të zgjerojmë produktet ekzistuese të shkruara në PHP, duke i përmirësuar ato përmes kombinimit të avantazheve të të dy gjuhëve.
Ne do të tregojmë si kombinimi i Go dhe PHP ndihmon në zgjidhjen e detyrave reale të zhvillimit dhe si është shndërruar për ne në një mjet që mund të na ndihmojë për t'u liruar nga disa probleme të lidhura me .
Mjedisi juaj i përditshëm i zhvillimit PHP
Para se të flasim se si Go mund ta zgjojë modelin e ‘vdekjes’ së PHP, le të shqyrtojmë mjedisin tuaj standard të zhvillimit PHP.
Në shumicën e rasteve, ju e запускате aplikacionin me një kombinim të serverit web nginx dhe serverit PHP-FPM. I pari shërben skedarët statikë dhe redirection kërkesat specifike te PHP-FPM, ndërsa vetë PHP-FPM ekzekuton kodin PHP. Ndoshta ju po përdorni një kombinim më pak të njohur të Apache dhe mod_php. Por ndonëse ai funksionon pak ndryshe, parimet janë të njëjta.
Le të shohim se si PHP-FPM ekzekuton kodin e aplikacionit. Kur arrin një kërkesë, PHP-FPM inicializon një proces PHP dytësor dhe detajet e kërkesës i kalon si pjesë e gjendjes së tij (_GET, _POST, _SERVER etj.).
Gjatë ekzekutimit të skriptit PHP, gjendja nuk mund të ndryshohet, prandaj mund të merrni një set të ri të të dhënave hyrëse vetëm në një mënyrë: duke e pastruar memorien e procesit dhe e inicializoni atë përsëri.
Ky model ekzekutimi ka shumë përfitime. Nuk keni nevojë të shqetësoheni shumë për konsumimin e memories, të gjithë proceset janë plotësisht të izoluar, dhe nëse ndonjë prej tyre "vdes", do të rinovohet automatikisht dhe kjo nuk do të ndikojë në proceset e tjera. Por ka dhe disavantazhe lidhur me këtë qasje që shprehen gjatë përpjekjeve për të shkallëzuar aplikacionin.
Disavantazhet dhe efikasiteti i ulët i ambientit të zakonshëm PHP
Nëse jeni duke u marrë me zhvillimin profesional në PHP, atëherë e dini se nga duhet të filloni një projekt të ri - nga zgjedhja e një kuadri. Ky është një koleksion bibliotekash për injektimin e varësive, ORM, përkthime dhe shabllone. Dhe, sigurisht, të gjitha të dhënat e përdoruesve mund të vendosen lehtësisht në një objekt (Symfony/HttpFoundation ose PSR-7). Kuadrat janë të mrekullueshme!
Por gjithçka ka çmimin e vet. Në çdo kuadër të nivelit të ndërmarrjeve për të trajtuar një kërkesë të thjeshtë nga përdoruesi ose një thirrje në bazën e të dhënave, do t'ju duhet të ngarkoni të paktën dhjetëra skedarë, të krijoni shumë klasa dhe të analizoni disa konfiguracione. Por ajo që është më e keqe është se pas çdo detyre duhet të ritheksoni gjithçka dhe të filloni nga fillimi: çdo kod që sapo e inicializuat bëhet i pavleftshëm, me të cilin nuk mund të trajtoni një tjetër kërkesë. Thoni këtë çdo programuesi që shkruan në ndonjë gjuhë tjetër — dhe do ta shihni ndërprerjen në fytyrën e tij.
Inxhinierët PHP kanë kërkuar për vite me radhë mënyra për të zgjidhur këtë problem, duke përdorur metoda të treguara për "ngarkim të lenë", mikrofrafikë, biblioteka të optimizuara, memorie cache etj. Por në fund të fundit, gjithmonë është e nevojshme të rëndomësohen të gjithë aplikacionin dhe të fillohet nga e para, përsëri dhe përsëri. (Vërejtje e përkthyesit: pjesërisht ky problem do të zgjidhet me daljen në PHP 7.4)
A mund PHP, duke përdorur Go, të përballojë më shumë se një kërkesë?
Mund të shkruhen skripte PHP që jetojnë më shumë se disa minuta (deri në orë ose ditë): për shembull, detyra cron, analizues CSV, parserë radhësh. Të gjitha ato funksionojnë sipas një skenari: nxjerrin një detyrë, e përmbushin atë, presin për tjetrën. Kodi qëndron vazhdimisht në memorie, duke kursyer milisekonda të çmuara, pasi ngarkimi i kuadrit dhe aplikacionit kërkon shumë veprime shtesë.
Por zhvillimi i skripteve afatgjata nuk është aq e lehtë. Çdo gabim e vret plotësisht procesin, diagnostikimi i rrjedhjeve të memories e çmend, dhe nuk mund të përdoret debugging me F5 më.
Situata është përmirësuar me daljen e PHP 7: u shpik një mbledhës të besueshëm të mbeturinave, është bërë më e lehtë për të trajtuar gabimet, dhe zgjatjet e bërthamës tani janë të mbrojtura nga rrjedhjet. Megjithatë, inxhinierët ende duhet të jenë të kujdesshëm me memorien dhe të kujtojnë problemet e gjendjes në kod (a ekziston ndonjë gjuhë ku mund të mos i kushtohet vëmendje këtyre gjërave?). Dhe megjithatë, në PHP 7 na presin më pak befasi.
A është e mundur të merret modeli i punës me skriptet PHP që jetojnë gjatë, ta adaptojë atë për detyra më triviale si përpunimi i kërkesave HTTP dhe kështu të shpëtojmë nga nevoja për të ngarkuar gjithçka nga e para me çdo kërkesë?
Për të zgjidhur këtë detyrë, fillimisht duhej të realizohej një aplikacion serveri, i cili mund të pranonte kërkesa HTTP dhe t'i përcillte ato një nga një PHP punonjësit, pa e vrarë atë çdo herë.
E dinim se do ta shkruanim një server web në PHP të pastër (PHP-PM) ose duke përdorur një zgjerim C (Swoole). Dhe ndonëse çdo qasje kishte përfitimet e veta, asnjëra prej tyre nuk na kënaqte — dëshironim diçka më shumë. Na nevojitej jo thjesht një server web — ne prisnim të merrnim një zgjidhje që do të na çlironte nga problemet e "fillimit të rëndë" në PHP, e cila gjithashtu mund të adaptohej dhe zgjerohej lehtësisht për aplikacione specifike. Pra, na duhej një server aplikacionesh.
A mund të ndihmojë Go në këtë? E dinim që mund, sepse ky gjuhë kompilon aplikacionet në skedarë binarë të vetme; është shumëplatformike; përdor një model të elegancshëm të përpunimit paralel (concurrency) dhe një bibliotekë për punën me HTTP; dhe, përfundimisht, do të kishim akses në mijëra biblioteka dhe integrime open-source.
Vështirësitë e bashkimit të dy gjuhëve të programimit
Së pari, duhej të përcaktonim se si dy ose më shumë aplikacione do të komunikonin me njëri-tjetrin.
Për shembull, nëpërmjet të Aleks Palastras, ishte e mundur të implementonim ndarjen e memories mes proceseve PHP dhe Go (analogjisht me mod_php në Apache). Por kjo bibliotekë ka disa karakteristika që e kufizojnë përdorimin e saj për zgjidhjen e problemit tonë.
Vendosëm të përdorim një qasje tjetër, më të njohur: të ndërtojmë ndërveprimin mes proceseve përmes soketesh/piperash. Kjo qasje ka provuar besueshmërinë e saj dhe është optimizuar mirë në nivelin e sistemit operativ gjatë dekadave të kaluara.
Për fillim, krijuam një protokoll binar të thjeshtë për shkëmbimin e të dhënave mes proceseve dhe trajtimin e gabimeve gjatë dërgimit. Në formën e saj më të thjeshtë, protokolli i këtij tipi është i ngjashëm me me (në rastin tonë 17 byte), i cili përmban informacion mbi llojin e paketës, madhësinë e saj dhe një maskë binare për kontrollin e integritetit të të dhënave.
Nga ana e PHP, ne përdorëm , ndërsa nga ana e Go — bibliotekën.
Një protokoll na dukej i pamjaftueshëm — dhe ne shtuam mundësinë për të thirrur. Më vonë, kjo na ndihmoi shumë në zhvillim, pasi mund të integrojmë lehtësisht bibliotekat Go në aplikacionet PHP. Rezultatin e kësaj pune mund ta shihni, për shembull, në një produkt tonë tjetër open-source.
Shpërndarja e detyrave në disa PHP-punëtorë
Pas implementimit të mekanizmit të ndërveprimit, filluam të mendonim se si të transferonim detyrat në proceset PHP në mënyrën më efikase. Kur arrin një detyrë, serveri i aplikacioneve duhet të zgjedhë një punëtor të lirë për ta kryer atë. Nëse punëtori/procesi e përfundon punën me gabim ose "vdes", ne e heqim atë dhe krijojmë një të ri në vendin e tij. Nëse punëtori/procesi e përfundon me sukses, ne e kthejmë atë në rezervën e punëtorëve të disponueshëm për të kryer detyra.

Për ruajtjen e rezervës së punëtorëve aktivë, ne përdorëm, për të hequr punëtarët që papritur "vdesin" nga rezerva, shtuam një mekanizëm për ndjekjen e gabimeve dhe gjendjeve të punëtorëve.
Si rezultat, morëm një server PHP funksional që mund të procesojë çdo kërkesë të paraqitur në formë binar.
Që aplikacioni ynë të fillonte të funksiononte si një server web, duhej të zgjidheshim një standard të besueshëm PHP për përfaqësimin e çdo kërkese HTTP të ardhur. Në rastin tonë, ne thjesht net/http-kërcim nga Go në formatin, për t'u bërë në përputhje me shumicën e kornizave PHP të disponueshme sot.
Duke qenë se PSR-7 konsiderohet i pandryshueshëm (disa do të thonë se teknikisht nuk është kështu), zhvilluesit përballen me sfidën që të shkruajnë aplikacione që në thelb nuk e trajtojnë kërkesën si një entitet global. Kjo kombinon shkëlqyeshëm me konceptin e proceseve PHP afatgjatë. Zbatimi ynë përfundimtar, që ende nuk ka marrë një emër, dukej kështu:

Prezantimi i RoadRunner —
Detyra jonë e parë në testim ishte një API-backend, ku periudhërisht ndodhnin shpërthime të papritura të kërkesave (shumë më shpesh se zakonisht). Megjithëse në shumicën e rasteve kapacitetet e nginx ishin të mjaftueshme, tani për tani ndeshëm rregullisht me gabimin 502, sepse nuk mundëm ta balancojmë sistemin mjaft shpejt për të pritur rritjen e pritur të ngarkesës.
Për të zëvendësuar këtë zgjidhje, në fillim të vitit 2018, ne vendosëm serverin tonë të parë PHP/Go për aplikacione. Dhe menjëherë morëm një efekt të pabesueshëm! Ne jo vetëm që u shkëputëm plotësisht nga gabimi 502, por gjithashtu arritëm të pakësonim numrin e serverëve me dy të tretat, duke kursyer një sasi të madhe parash dhe tabletash për dhimbjen e kokës për inxhinierët dhe menaxherët e produkteve.
Në mes të vitit, ne përmirësuam zgjidhjen tonë, e publikova atë në GitHub nën licencën MIT dhe e quajtëm , duke theksuar kështu shpejtësinë dhe efikasitetin e tij të jashtëzakonshëm.
Si mund të përmirësojë RoadRunner stivin tuaj të zhvillimit
Përdorimi na lejohet të përdorim Middleware net/http në anën e Go, për të zhvilluar verifikimin e JWT-në edhe para se kërkesa të arrijë në PHP, si dhe për të trajtuar WebSockets dhe për të agreguar globalisht gjendjet në Prometheus.
Falë RPC të integruar, mund të hapim API të çdo biblioteke Go për PHP pa shkruar ekstensionet-mbështetëse. Çka është edhe më e rëndësishme, me RoadRunner mund të vendosim servera të rinj, të ndryshëm nga HTTP. Siç shembuj mund të përmenden ekzekutimi i trajtuesve në PHP, krijimi i parserëve të besueshëm të radhëve dhe madje edhe shtimi në aplikacionet tona.
Me ndihmën e komuniteteve PHP dhe Go, ne rritëm stabilitetin e zgjidhjes, në disa teste rritëm performancën e aplikacioneve deri në 40 herë, përmirësuam mjetet për debugging, realizuam integrimin me framework-un Symfony dhe shtuam mbështetje për HTTPS, HTTP/2, plugina dhe PSR-17.
Përfundim
Disa ende janë të kapluar nga koncepti i vjetër për PHP si një gjuhë të ngadaltë dhe të rëndë, e përshtatshme vetëm për të shkruar plugina për WordPress. Këta njerëz madje mund të thonë se PHP ka një kufizim të tillë: kur aplikacioni bëhet mjaft i madh, duhet të zgjidhni një gjuhë më "të pjekur" dhe ta ripërpunoni bazën e kodit që është grumbulluar për shumë vjet.
Për të gjitha këto dëshirojmë të themi: mendoni edhe një herë. Ne besojmë se vetëm ju vetë vendosni ndonjë kufizim për PHP. Mund të shpenzoni tërë jetën duke kaluar nga një gjuhë në tjetrën, duke u përpjekur të gjeni kombinimin ideal me nevojat tuaja, ose mund të filloni ta shihni gjuhët si mjete. Këto sakrifica të imagjinuara të gjuhës si PHP në të vërtetë mund të jenë arsye për suksesin e saj. Dhe nëse e bashkoni me një gjuhë tjetër si Go, do të krijoni produkte shumë më të fuqishme sesa nëse do të kufizoheshit vetëm me një gjuhë.
Pas punës me kombinimin Go dhe PHP, mund të deklarojmë se i kemi dashuruar. Nuk planifikuar të sakrifikojmë një në favor të tjetrit — përkundrazi, do të kërkojmë mënyra për të nxjerrë edhe më shumë përfitime nga ky stiv i dyfishtë.
UPD: mirëpresim krijuesin e RoadRunner dhe bashkautor të artikullit origjinal —
Burimi: habr.com
