RoadRunner: PHP ne estas konstruita por morti, aŭ Golang por la savo

RoadRunner: PHP ne estas konstruita por morti, aŭ Golang por la savo

Hej Habr! Ni aktivas ĉe Badoo laborante pri PHP-agado, ĉar ni havas sufiĉe grandan sistemon en ĉi tiu lingvo kaj la rendimenta afero estas monŝpara afero. Antaŭ pli ol dek jaroj, ni kreis PHP-FPM por tio, kiu komence estis aro de flikoj por PHP, kaj poste eniris la oficialan distribuon.

En la lastaj jaroj, PHP faris grandan progreson: la rubkolektanto pliboniĝis, la nivelo de stabileco pliiĝis - hodiaŭ vi povas skribi demonojn kaj longdaŭrajn skriptojn en PHP senprobleme. Ĉi tio permesis al Spiral Scout iri plu: RoadRunner, male al PHP-FPM, ne purigas memoron inter petoj, kio donas plian rendimentan gajnon (kvankam ĉi tiu aliro malfaciligas la evoluprocezon). Ni nuntempe eksperimentas kun ĉi tiu ilo, sed ni ankoraŭ ne havas rezultojn por konigi. Por ke atendi ilin pli amuza, ni publikigas la tradukon de la anonco RoadRunner de Spiral Scout.

La aliro de la artikolo estas proksima al ni: solvante niajn problemojn, ni ankaŭ plej ofte uzas amason da PHP kaj Go, akirante la avantaĝojn de ambaŭ lingvoj kaj ne forlasante unu en favoro de la alia.

Ĝuu!

En la lastaj dek jaroj, ni kreis aplikojn por kompanioj el la listo fortuno 500, kaj por entreprenoj kun spektantaro de ne pli ol 500 uzantoj. Dum ĉi tiu tempo, niaj inĝenieroj disvolvis la backend ĉefe en PHP. Sed antaŭ du jaroj, io havis grandan efikon ne nur al la agado de niaj produktoj, sed ankaŭ al ilia skaleblo - ni enkondukis Golang (Go) en nian teknologian stakon.

Preskaŭ tuj, ni malkovris, ke Go permesis al ni konstrui pli grandajn aplikojn kun ĝis 40x-efikecplibonigoj. Per ĝi, ni povis etendi ekzistantajn produktojn skribitajn en PHP, plibonigante ilin kombinante la avantaĝojn de ambaŭ lingvoj.

Ni rakontos al vi kiel la kombinaĵo de Go kaj PHP helpas solvi realajn disvolvajn problemojn kaj kiel ĝi fariĝis por ni ilo, kiu povas forigi iujn problemojn asociitajn kun PHP-mortanta modelo.

Via ĉiutaga PHP-disvolva medio

Antaŭ ol ni parolu pri kiel vi povas uzi Iru por revivigi la PHP-mortantan modelon, ni rigardu vian defaŭltan PHP-disvolvan medion.

Plejofte, vi rulas vian aplikaĵon uzante kombinaĵon de la retservilo nginx kaj la PHP-FPM-servilo. La unua servas senmovajn dosierojn kaj alidirektas specifajn petojn al PHP-FPM, dum PHP-FPM mem efektivigas PHP-kodon. Vi eble uzas la malpli popularan kombinaĵon de Apache kaj mod_php. Sed kvankam ĝi funkcias iom malsame, la principoj estas la samaj.

Ni rigardu kiel PHP-FPM efektivigas aplikan kodon. Kiam peto envenas, PHP-FPM pravalorigas PHP-infan procezon kaj pasas la detalojn de la peto kiel parto de ĝia stato (_GET, _POST, _SERVER, ktp.).

La stato ne povas ŝanĝiĝi dum PHP-skripto-ekzekuto, do ekzistas nur unu maniero akiri novan aron da enigdatumoj: per malplenigo de la procezmemoro kaj rekomencigante ĝin.

Ĉi tiu ekzekutmodelo havas multajn avantaĝojn. Vi ne devas tro zorgi pri memorkonsumo, ĉiuj procezoj estas tute izolitaj, kaj se unu el ili "mortas", ĝi estos aŭtomate rekreita kaj ĝi ne influos la ceterajn procezojn. Sed ĉi tiu aliro ankaŭ havas malavantaĝojn, kiuj aperas kiam oni provas grimpi la aplikaĵon.

Malavantaĝoj kaj Neefikecoj de Regula PHP-Medio

Se vi estas profesia PHP-programisto, tiam vi scias kie komenci novan projekton - kun la elekto de kadro. Ĝi konsistas el dependecaj injektaj bibliotekoj, ORMoj, tradukoj kaj ŝablonoj. Kaj, kompreneble, ĉiuj uzantenigo povas oportune esti metita en unu objekton (Symfony/HttpFoundation aŭ PSR-7). Kadroj estas bonegaj!

Sed ĉio havas sian prezon. En iu ajn entrepren-nivela kadro, por procesi simplan uzantan peton aŭ aliron al datumbazo, vi devos ŝargi almenaŭ dekojn da dosieroj, krei multajn klasojn kaj analizi plurajn agordojn. Sed la plej malbona afero estas, ke post plenumi ĉiun taskon, vi devos restarigi ĉion kaj rekomenci: la tuta kodo, kiun vi ĵus iniciatis, fariĝas senutila, kun ĝia helpo vi ne plu prilaboros alian peton. Diru ĉi tion al iu ajn programisto, kiu skribas en iu alia lingvo, kaj vi vidos konfuzon sur lia vizaĝo.

PHP-inĝenieroj serĉis manierojn solvi ĉi tiun problemon dum jaroj, uzante lertajn maldiligentajn ŝarĝajn teknikojn, mikrokadrojn, optimumigitajn bibliotekojn, kaŝmemoron, ktp. Sed finfine, vi ankoraŭ devas restarigi la tutan aplikaĵon kaj rekomenci, denove kaj denove. . (Noto de la tradukinto: ĉi tiu problemo estos parte solvita kun la alveno de preŝargi en PHP 7.4)

Ĉu PHP kun Go povas travivi pli ol unu peton?

Eblas verki PHP-skriptojn, kiuj vivas pli longe ol kelkaj minutoj (ĝis horoj aŭ tagoj): ekzemple cron-taskoj, CSV-analiziloj, vostorompiloj. Ili ĉiuj funkcias laŭ la sama scenaro: ili prenas taskon, plenumas ĝin kaj atendas la sekvan. La kodo loĝas en memoro la tutan tempon, ŝparante altvalorajn milisekundojn ĉar estas multaj pliaj paŝoj necesaj por ŝarĝi la kadron kaj aplikaĵon.

Sed disvolvi longvivajn skriptojn ne estas facila. Ajna eraro tute mortigas la procezon, diagnozi memorajn likojn estas indigniga, kaj F5-sencimigo ne plu eblas.

La situacio pliboniĝis kun la ĵeto de PHP 7: fidinda rubkolektanto aperis, fariĝis pli facile pritrakti erarojn, kaj kernaj etendaĵoj nun estas likaj. Vere, inĝenieroj ankoraŭ devas zorgi pri memoro kaj konscii pri ŝtataj aferoj en kodo (ĉu ekzistas lingvo, kiu povas ignori ĉi tiujn aferojn?). Tamen, PHP 7 havas malpli da surprizoj al ni.

Ĉu eblas preni la modelon labori kun longdaŭraj PHP-skriptoj, adapti ĝin al pli banalaj taskoj kiel prilabori HTTP-petojn, kaj tiel forigi la bezonon ŝargi ĉion de nulo kun ĉiu peto?

Por solvi ĉi tiun problemon, ni unue bezonis efektivigi servilan aplikaĵon, kiu povus akcepti HTTP-petojn kaj redirekti ilin unu post alia al la PHP-laboristo sen mortigi ĝin ĉiufoje.

Ni sciis, ke ni povus skribi retservilon en pura PHP (PHP-PM) aŭ uzante C-etendon (Swoole). Kaj kvankam ĉiu metodo havas siajn proprajn meritojn, ambaŭ opcioj ne konvenis al ni - ni volis ion pli. Ni bezonis pli ol nur retservilon - ni atendis akiri solvon kiu povus savi nin de la problemoj asociitaj kun "malfacila komenco" en PHP, kiu samtempe povus esti facile adaptita kaj etendita por specifaj aplikoj. Tio estas, ni bezonis aplikaĵoservilon.

Ĉu Go povas helpi pri tio? Ni sciis, ke ĝi povus, ĉar la lingvo kompilas aplikojn en unuopajn binarojn; ĝi estas transplatforma; uzas propran, tre elegantan, paralelan pretigan modelon (samtempa) kaj bibliotekon por labori kun HTTP; kaj fine, miloj da malfermfontaj bibliotekoj kaj integriĝoj estos disponeblaj por ni.

La Malfacilaĵoj de Kombini Du Programlingvojn

Antaŭ ĉio, estis necese determini kiel du aŭ pli da aplikoj komunikados unu kun la alia.

Ekzemple, uzante bonega biblioteko Alex Palaestras, eblis dividi memoron inter PHP kaj Go-procezoj (simila al mod_php en Apache). Sed ĉi tiu biblioteko havas funkciojn, kiuj limigas ĝian uzon por solvi nian problemon.

Ni decidis uzi alian, pli oftan aliron: konstrui interagadon inter procezoj per ingoj / duktoj. Ĉi tiu aliro pruvis esti fidinda dum la pasintaj jardekoj kaj estis bone optimumigita je la mastruma sistemo.

Komence, ni kreis simplan binaran protokolon por interŝanĝi datumojn inter procezoj kaj pritrakti transmisirajn erarojn. En ĝia plej simpla formo, ĉi tiu tipo de protokolo similas al retŝnuro с fiksa grandeco paka kaplinio (en nia kazo 17 bajtoj), kiu enhavas informojn pri la tipo de pako, ĝia grandeco kaj binara masko por kontroli la integrecon de la datumoj.

Sur la PHP-flanko ni uzis paka funkcio, kaj ĉe la Go-flanko, la biblioteko kodigo/binara.

Ŝajnis al ni, ke unu protokolo ne sufiĉas – kaj ni aldonis la eblon voki net/rpc iri servojn rekte de PHP. Poste, ĉi tio multe helpis nin en evoluo, ĉar ni povis facile integri Go-bibliotekojn en PHP-aplikojn. La rezulto de ĉi tiu laboro videblas, ekzemple, en nia alia malfermfonta produkto Goridge.

Distribuado de taskoj tra pluraj PHP-laboristoj

Post efektivigo de la interaga mekanismo, ni komencis pensi pri la plej efika maniero transdoni taskojn al PHP-procezoj. Kiam tasko alvenas, la aplikaĵoservilo devas elekti liberan laboriston por plenumi ĝin. Se laboristo/procezo eliras kun eraro aŭ "mortas", ni forigas ĝin kaj kreas novan por anstataŭigi ĝin. Kaj se la laboristo/procezo sukcese finiĝis, ni resendas ĝin al la aro de laboristoj disponeblaj por plenumi taskojn.

RoadRunner: PHP ne estas konstruita por morti, aŭ Golang por la savo

Por konservi la aron de aktivaj laboristoj, ni uzis bufrita kanalo, por forigi neatendite "mortintajn" laboristojn el la naĝejo, ni aldonis mekanismon por spuri erarojn kaj statojn de laboristoj.

Kiel rezulto, ni ricevis funkciantan PHP-servilon kapablan prilabori iujn ajn petojn prezentitajn en binara formo.

Por ke nia aplikaĵo ekfunkciu kiel retservilo, ni devis elekti fidindan PHP-normon por reprezenti ajnajn envenantajn HTTP-petojn. En nia kazo, ni nur transformi net/http peto de Iru al formato PSR-7tiel ke ĝi estas kongrua kun la plej multaj el la PHP-kadroj disponeblaj hodiaŭ.

Ĉar PSR-7 estas konsiderata neŝanĝebla (kelkaj dirus, ke teknike ĝi ne estas), programistoj devas skribi aplikaĵojn, kiuj principe ne traktas la peton kiel tutmondan enton. Ĉi tio bone kongruas kun la koncepto de longdaŭraj PHP-procezoj. Nia fina efektivigo, kiu ankoraŭ ne estas nomita, aspektis jene:

RoadRunner: PHP ne estas konstruita por morti, aŭ Golang por la savo

Prezentante RoadRunner - alta rendimento PHP-aplikservilo

Nia unua testa tasko estis API-backend, kiu periode krevas neantaŭvideble (multe pli ofte ol kutime). Kvankam nginx estis sufiĉa en la plej multaj kazoj, ni regule renkontis 502-erarojn ĉar ni ne povis ekvilibrigi la sistemon sufiĉe rapide por la atendata pliiĝo de ŝarĝo.

Por anstataŭigi ĉi tiun solvon, ni deplojis nian unuan PHP/Go-aplikservilon komence de 2018. Kaj tuj ricevis nekredeblan efikon! Ne nur ni tute forigis la 502-eraron, sed ni povis redukti la nombron da serviloj je du trionoj, ŝparante multe da mono kaj kapdolorajn pilolojn por inĝenieroj kaj produktestroj.

Je la mezo de la jaro, ni plibonigis nian solvon, publikigis ĝin sur GitHub sub la MIT-licenco kaj nomis ĝin. Stratkurulo, tiel emfazante ĝian nekredeblan rapidecon kaj efikecon.

Kiel RoadRunner povas plibonigi vian disvolvan stakon

Apliko Stratkurulo permesis al ni uzi Middleware net/http ĉe la Go-flanko por fari JWT-konfirmon antaŭ ol la peto atingas PHP, kaj ankaŭ pritrakti WebSockets kaj entuta stato tutmonde en Prometheus.

Danke al la enkonstruita RPC, vi povas malfermi la API de iuj Go-bibliotekoj por PHP sen skribi etendaĵojn. Pli grave, kun RoadRunner vi povas disfaldi novajn ne-HTTP-servilojn. Ekzemploj inkluzivas kurantajn traktilojn en PHP AWS Lambda, kreante fidindajn atendovicrompilojn, kaj eĉ aldonante gRPC al niaj aplikoj.

Kun la helpo de la PHP kaj Go-komunumoj, ni plibonigis la stabilecon de la solvo, pliigis aplikaĵon ĝis 40 fojojn en iuj testoj, plibonigis sencimigajn ilojn, efektivigis integriĝon kun la kadro Symfony kaj aldonis subtenon por HTTPS, HTTP/2, kromaĵojn, kaj PSR-17.

konkludo

Iuj homoj ankoraŭ estas kaptitaj en la malmoderna nocio de PHP kiel malrapida, maloportuna lingvo nur bona por skribi kromaĵojn por WordPress. Ĉi tiuj homoj eĉ povus diri, ke PHP havas tian limigon: kiam la aplikaĵo fariĝas sufiĉe granda, vi devas elekti pli "maturan" lingvon kaj reverki la kodon akumulitan dum multaj jaroj.

Al ĉio ĉi mi volas respondi: pripensu denove. Ni kredas, ke nur vi starigas iujn ajn limigojn por PHP. Vi povas pasigi vian tutan vivon transirante de unu lingvo al alia, provante trovi la perfektan taŭgan por viaj bezonoj, aŭ vi povas komenci pensi pri lingvoj kiel iloj. La supozitaj difektoj de lingvo kiel PHP povas fakte esti la kialo de ĝia sukceso. Kaj se vi kombinas ĝin kun alia lingvo kiel Go, tiam vi kreos multe pli potencajn produktojn ol se vi estus limigita al uzado de iu ajn lingvo.

Laborinte kun amaso da Go kaj PHP, ni povas diri, ke ni amas ilin. Ni ne planas oferi unu por la alia - male, ni serĉos manierojn akiri eĉ pli da valoro de ĉi tiu duobla stako.

UPD: ni bonvenigas la kreinton de RoadRunner kaj la kunaŭtoron de la originala artikolo - Lachesis

fonto: www.habr.com

Aldoni komenton