
Përshëndetje, Habr! Ne në Badoo jemi aktivisht , pasi kemi një sistem të mjaftueshëm të madh në këtë gjuhë dhe çështja e performancës është një çështje kursimi parash. Më shumë se një dekadë më parë, ne krijuam PHP-FPM, i cili fillimisht ishte një grup patch-e për PHP, dhe më vonë u bë pjesë e shpërndarjes zyrtare.
Gjatë viteve të fundit, PHP ka përparuar shumë: mekanizmi i grumbullimit të mbetjes ka përmirësuar, stabiliteti është rritur — sot në PHP është e mundur të shkruani demonë dhe skriptë me jetëgjatësi pa probleme të mëdha. Kjo i ka lejuar Spiral Scout të shkojë më tej: RoadRunner, ndryshe nga PHP-FPM, nuk pastron memorie midis kërkesave, gjë që ofron një fitim të shtuar në performancë (ndonëse kjo qasje e ndërlikon procesin e zhvillimit). Ne tani po eksperimentojmë me këtë mjet, por ende nuk kemi rezultati që mund të ndajmë. Që të presim me më shumë gëzim, po publikojmë përkthimin e njoftimit të RoadRunner nga Spiral Scout.
Naiveja nga artikulli është e njohur për ne: kur zgjidhim problemet tona, shpesh përdorim kombinimin PHP dhe Go, duke përfituar nga të dyja gjuhët pa hequr dorë nga njëra për shkak të tjetrës.
Shijoni!
Gjatë dhjetë viteve të fundit, ne krijuam aplikacione për kompani nga lista , dhe për biznese me një audiencë prej më pak se 500 përdoruesve. Gjatë kësaj periudhe, inxhinierët tanë zhvilluan predominant backend në PHP. Por dy vjet më parë, diçka ndikoi ndjeshëm jo vetëm në performancën e produkteve tona, por edhe në shkallëzueshmërinë e tyre — ne prezantuam Golang (Go) në stackun tonë të teknologjisë.
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ë, arritëm të zgjeronim produktet ekzistuese të shkruara në PHP, duke i përmirësuar ato falë kombinimit të të dyja gjuhëve.
Ne do të tregojmë se si kombinimi Go dhe PHP ndihmon në zgjidhjen e problemeve reale të zhvillimit dhe si u bë një mjet që na ndihmon të heqim dorë nga disa nga problemet e lidhura me .
Mjedisi juaj për zhvillim PHP çdo ditë
Para se treguar se si, me ndihmën e Go, mund ta ringjallni modelin e "vdekjes" së PHP-së, le të shqyrtojmë ambientin tuaj standard të zhvillimit PHP.
Në shumicën e rasteve, ju e hapni aplikacionin me një kombinim të serverit web nginx dhe serverit PHP-FPM. I pari shërben skedarët statikë dhe redirekton kërkesat specifike në PHP-FPM, ndërsa PHP-FPM ekzekuton kodin PHP. Ndoshta po përdorni një kombinim më pak të njohur të Apache dhe mod_php. Por, ndonëse funksionon pak ndryshe, parimet janë të njëjta.
Le të shqyrtojmë se si PHP-FPM ekzekuton kodin e aplikacionit. Kur vjen një kërkesë, PHP-FPM inicializon një proces të dytë PHP, dhe detajet e kërkesës i kalon si pjesë e gjendjes së tij (_GET, _POST, _SERVER etj.).
Gjendja nuk mund të ndryshojë gjatë ekzekutimit të skenarit PHP, prandaj një grup të ri të të dhënave hyrëse mund të merret vetëm në një mënyrë: duke pastruar memorien e procesit dhe duke e inicializuar atë përsëri.
Ky model ka shumë përfitime. Nuk duhet të shqetësoheni shumë për konsumin e memories, të gjitha proceset janë të izoluara plotësisht, dhe nëse njëri prej tyre "vdes", ai do të rikrijohet automatikisht pa prekur proceset e tjera. Por ky qasje ka edhe disavantazhe, që shfaqen kur përpiqeni të shkallëzoni aplikacionin.
Disavantazhet dhe efikasiteti i ulët i një ambienti të zakonshëm PHP
Nëse jeni të angazhuar në zhvillimin profesional me PHP, e dini nga duhet të filloni një projekt të ri — nga zgjedhja e një framework-u. Ai përbën biblioteka për shtimin e varësive, ORM, përkthime dhe shabllone. Dhe, sigurisht, të gjitha të dhënat e hyrjes nga përdoruesit mund të vendosen lehtësisht në një objekt (Symfony/HttpFoundation ose PSR-7). Framework-et janë të shkëlqyera!
Por çdo gjë ka një çmim. Në çdo framework të nivelit enterprise, për të procesuar një kërkesë të thjeshtë të përdoruesit ose një thirrje në bazën e të dhënave, do të duhet të ngarkoni të paktën dhjetëra skedarë, të krijoni klasa të shumta dhe të analizoni disa konfigurime. Por më e keqja është se pas kryerjes së çdo detyre, do të duhet të rinisni nga fillimi: të gjithë kodin e sapo iniciuar bëhet i padobishëm, me të cilin nuk mund të përpunoni një tjetër kërkesë. Thoni këtë kushdo programuesi që shkruan në ndonjë gjuhë tjetër, dhe do të shihni një shprehje konfuzioni në fytyrën e tij.
Inxhinierët PHP kanë kërkuar për vite mënyra për të zgjidhur këtë problem, duke përdorur metodologji të menduara për 'ngarkimin e lenient', mikrofremeworkë, biblioteka të optimizuara, cache, etj. Por në fund, përsëri duhet të riachoni të gjithë aplikacionin dhe të filloni nga fillimi, përsëri dhe përsëri. (Shënim i përkthyesit: pjesërisht ky problem do të zgjidhet me daljen e në PHP 7.4)
A mund të përballojë PHP më shumë se një kërkesë me ndihmën e Go?
Mund të shkruhen skripte PHP që jetojnë më gjatë se disa minuta (deri në orë ose ditë): për shembull, detyra cron, parserat CSV, dekompozuesit e radhës. Të gjitha ato funksionojnë sipas një skenari të njëjtë: nxjerrin një detyrë, e kryejnë atë, presin për të ardhmen. Kodi qëndron vazhdimisht në memorie, duke kursyer milisekonda të çmuara, pasi ngarkimi i kornizës dhe aplikacionit kërkon të kryhen shumë veprime të tjera shtesë.
Por nuk është aq e lehtë të zhvillosh skripte me jetëgjatësi. Çdo gabim e vret plotësisht procesin, diagnostikimi i rrjedhjeve të memories e çon në çmenduri, dhe nuk mund të përdoret më debugging me F5.
Situata u përmirësua me daljen e PHP 7: një mbledhës i besueshëm i mbetjeve u shfaq, u bë më e lehtë për të përpunuar gabimet, dhe zgjerimet e bërthamës tani janë të mbrojtura nga rrjedhjet. Megjithatë, inxhinierët duhet ende të jenë të kujdesshëm me memorinë dhe të kujtojnë problemet e gjendjes në kod (a ka ndonjë gjuhë ku nuk duhen kushtuar vëmendje këtyre gjërave?). Sidoqoftë, në PHP 7 na presin më pak papritur.
A mund të përdorim një model pune me skripte PHP të qëndrueshme, ta adaptojmë atë për detyra më të thjeshta si përpunimi i kërkesave HTTP dhe kështu të shmangim ngarkimin e gjithçkaje nga fillimi për secilën kërkesë?
Për të zgjidhur këtë problem, së pari duhej të realizonim një aplikacion serveri që do të ishte në gjendje të pranojë kërkesa HTTP dhe t'i drejtojë ato një nga një te PHP-uperi, pa e vrarë atë çdo herë.
E dinim që mund të shkruajmë një server web në PHP të pastër (PHP-PM) ose duke përdorur një ekstenzion C (Swoole). Dhe edhe pse çdo metodë ka përfitimet e veta, asnjëra prej tyre nuk na kënaqte — dëshironim diçka më shumë. Na duhej jo thjesht një server web — ne planifikonim të merrnim një zgjidhje që mund të na çlironte nga problemet e "fillimit të rëndë" në PHP, e cila gjithashtu mund të adaptohej dhe shtrinte lehtësisht për aplikacione të veçanta. Kështu, na duhej një server aplikacionesh.
A mund të na ndihmojë Go në këtë? E dinim se mundet, sepse ky gjuhë kompilon aplikacione në skedarë binarë të vetme; është shumëplatformë; përdor një model shumë të elegancshëm për përpunimin paralele (concurrency) dhe një bibliotekë për punë me HTTP; dhe, përfundimisht, do të kemi në dispozicion mijëra biblioteka open-source dhe integrime.
Vështirësitë e përzierjes së dy gjuhëve të programimit
Së pari, duhej të përcaktohej se si dy ose më shumë aplikacione do të komunikonin me njëri-tjetrin.
Për shembull, përmes të Alekës Palaeastrasit, ishte e mundur të realizohej ndarja e memories midis proceseve PHP dhe Go (ngjashëm me mod_php në Apache). Por kjo bibliotekë ka veçori që i kufizojnë për përdorim në zgjidhjen e detyrës sonë.
Ne vendosëm të përdorim një qasje tjetër, më të zakonshme: të ndërtojmë ndërveprimin midis proceseve përmes soketeve/pipes. Kjo qasje ka treguar besueshmërinë e saj dhe është optimizuar mirë në nivelin e sistemit operativ gjatë dekadave të fundit.
Në fillim, ne krijuam një protokoll binar të thjeshtë për shkëmbimin e të dhënave midis proceseve dhe trajtimin e gabimeve të transmetimit. Në formën e tij 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 verifikimin e integritetit të të dhënave.
Në anën e PHP, ne përdorëm , ndërsa në anën e Go — bibliotekën.
Na duket se një protokoll nuk ishte i mjaftueshë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. Rezultati i kësaj pune mund të shihet, për shembull, në produktin tonë tjetër open-source.
Shpërndarja e detyrave ndërmjet disa PHP-punëtorëve
Pas realizimit të mekanizmit të ndërveprimit, filluam të mendojmë se si të përcjellim detyrat në proceset PHP në mënyrën më të efektshme. Kur vjen një detyrë, serveri i aplikacioneve duhet të zgjedhë një punëtor të lirë për ta ekzekutuar atë. Nëse punëtori/procesi përfundon punën me gabim ose 'vdes', ne e eliminojmë atë dhe krijojmë një të ri në vendin e tij. Ndërsa, nëse punëtori/procesi e përfundon me sukses, e kthejmë atë në grupin e punëtorëve të disponueshëm për të kryer detyra.

Për të ruajtur grupin e punëtorëve aktivë, ne përdorëm, për të hequr nga grupi punëtorët që 'vdesin' papritur, shtuam një mekanizëm për monitorimin e gabimeve dhe gjendjeve të punëtorëve.
Si rezultat, morëm një server PHP funksional, i aftë për të trajtuar çdo kërkesë të paraqitur në format binar.
Për të bërë që aplikacioni ynë të fillonte të punonte si një server web, duhej të zgjidhnim një standard të besueshëm PHP për përfaqësimin e çdo kërkese HTTP të ardhur. Në rastin tonë, ne thjesht kërkesën net/http nga Go në formatin, për ta bërë atë të pajtueshëm me shumicën e kuadrove PHP të disponueshme sot.
Duke PSR-7 konsiderohet i pandryshueshëm (disa do të thoshin që teknikisht nuk është kështu), zhvilluesit duhet të shkruajnë aplikacione që në parim nuk e trajtojnë kërkesën si një entitet global. Kjo përshtatet perfekt me konceptin e proceseve PHP me jetëgjatësi të gjatë. Implementimi ynë përfundimtar, që ende nuk ka marrë emër, dukej kështu:

Paraqesim RoadRunner —
Detyra jonë e parë testuese ishte një API-backend, mbi të cilin përkohësisht shfaqeshin shpërthime kërkesh që ishin të papritur (shumë më shpesh se zakonisht). Megjithëse në shumicën e rasteve mundësitë e nginx ishin të mjaftueshme, ne vazhdimisht përballeshim me gabimin 502, sepse nuk mund të balancojmë sistemin mjaft shpejt sipas rritjes së pritur të ngarkesës.
Për të zëvendësuar këtë zgjidhje, në fillim të vitit 2018 nxorrëm serverin tonë të parë të PHP/Go për aplikacione. Dhe menjëherë morëm një efekt të jashtëzakonshëm! Ne jo vetëm që eliminuam plotësisht gabimin 502, por gjithashtu arritëm të reduktojmë numrin e serverëve me dy të tretat, duke kursyer një sasi të madhe parash dhe tableta për dhimbjen e kokës për inxhinierët dhe menaxherët e produkteve.
Në mes të vitit përmirësuam zgjidhjen tonë, e publikuan atë në GitHub nën licencën MIT dhe e quajtëm , duke theksuar kështu shpejtësinë e tij të jashtëzakonshme dhe efikasitetin.
Si mundet RoadRunner të përmirësojë stakun tuaj të zhvillimit
Aplikimi na lejoi të përdorim Middleware net/http në anën e Go për të kryer verifikimin JWT përpara se kërkesa të arrijë në PHP, si dhe për të trajtuar WebSockets dhe për të agreguar globalisht gjendjet në Prometheus.
Me RPC të integruar, është e mundur të hapim API-të e çdo biblioteke Go për PHP pa shkruar zgjerime-nëpërmjet. Më e rëndësishmja, me RoadRunner mund të vendosim serverë të rinj, të cilët dallohet nga HTTP. Disa shembuj të mundshëm janë nisja e trajtuesve në PHP, krijimi i parserëve të besueshëm të radhëve dhe madje edhe shtimi në aplikacionet tona.
Me bashkësitë PHP dhe Go ne përmirësuam stabilitetin e zgjidhjes, në disa teste rritëm performancën e aplikacioneve deri në 40 herë, përmirësuam mjetet e debugut, realizuam integrimin me framework-un Symfony dhe shtuam mbështetje për HTTPS, HTTP/2, plugin dhe PSR-17.
Përfundimi
Disa akoma janë të bllokuar në një perceptim të vjetëruar të PHP si një gjuhë të ngadaltë dhe të rëndë, të përshtatshme vetëm për të shkruar plug-ins 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ë zgjedhësh një gjuhë më "të pjekur" dhe të riprogramosh bazën e kodit të grumbulluar për vite me radhë.
Për të gjitha këto, dëshirojmë të përgjigjemi: mendoni përsëri. Ne besojmë se vetëm ju vetë vendosni disa kufizime për PHP. Mund të kaloni gjithë jetën tuaj duke kaluar nga një gjuhë në tjetrën, duke u munduar të gjeni kombinimin ideal për nevojat tuaja, ose mund të filloni ta shihni gjuhët si mjete. Pseudoshkakët e një gjuhe si PHP në të vërtetë mund të jenë arsyet për suksesin e saj. Dhe nëse e kombinoni atë me një gjuhë tjetër si Go, do të krijoni produkte shumë më të fuqishme se sa nëse do të kufizoheshit në përdorimin e një gjuhe të vetme.
Pas punës me kombinimin Go dhe PHP, mund të afirmojmë se i kemi dashur. Nuk planifikojmë të sakrifikojmë një për shkak të tjetrit — përkundrazi, do të kërkojmë mënyra për të nxjerrë edhe më shumë përfitime nga ky dyfishim i stack-ut.
UPD: mirëseardhje krijuesit të RoadRunner dhe bashkautorit të artikullit origjinal —
Burimi: habr.com
