„RoadRunner“: PHP nėra sukurtas tam, kad mirtų, arba „Golang“ į pagalbą

„RoadRunner“: PHP nėra sukurtas tam, kad mirtų, arba „Golang“ į pagalbą

Sveiki, Habr! Esame aktyvūs Badoo dirba su PHP našumu, nes turime gana didelę sistemą šia kalba ir našumo klausimas yra pinigų taupymo klausimas. Daugiau nei prieš dešimt metų tam sukūrėme PHP-FPM, kuris iš pradžių buvo PHP pataisų rinkinys, o vėliau tapo oficialaus platinimo dalimi.

Pastaraisiais metais PHP padarė didelę pažangą: patobulėjo šiukšlių rinktuvas, išaugo stabilumo lygis – šiandien PHP be problemų galima rašyti demonus ir ilgaamžius scenarijus. Tai leido Spiral Scout žengti toliau: RoadRunner, skirtingai nei PHP-FPM, neišvalo atminties tarp užklausų, o tai suteikia papildomos našumo naudos (nors šis metodas apsunkina kūrimo procesą). Šiuo metu eksperimentuojame su šiuo įrankiu, bet dar neturime rezultatų, kuriais galėtume pasidalinti. Kad būtų smagiau jų laukti, Skelbiame Spiral Scout RoadRunner pranešimo vertimą.

Straipsnyje pateiktas požiūris mums artimas: spręsdami problemas taip pat dažniausiai naudojame PHP ir Go derinį, išnaudodami abiejų kalbų pranašumus ir neatsisakydami vienos kitos naudai.

Kurkime kartu!

Per pastaruosius dešimt metų įmonėms sukūrėme programas iš sąrašo Fortūna 500, ir įmonėms, kurių auditorija ne didesnė kaip 500 vartotojų. Visą šį laiką mūsų inžinieriai kūrė pagrindinę programą daugiausia PHP. Tačiau prieš dvejus metus kažkas padarė didelę įtaką ne tik mūsų gaminių veikimui, bet ir jų mastelio keitimui – pristatėme „Golang“ („Go“) į savo technologijų krūvą.

Beveik iš karto sužinojome, kad „Go“ leido sukurti didesnes programas, kurių veikimas yra iki 40 kartų didesnis. Su juo galėjome išplėsti esamus produktus, parašytus PHP kalba, patobulindami juos derindami abiejų kalbų pranašumus.

Mes jums pasakysime, kaip „Go“ ir „PHP“ derinys padeda išspręsti realias kūrimo problemas ir kaip jis tapo mūsų įrankiu, galinčiu pašalinti kai kurias su PHP mirštantis modelis.

Jūsų kasdienė PHP kūrimo aplinka

Prieš kalbėdami apie tai, kaip galite naudoti Go, kad atgaivintumėte mirštantį PHP modelį, pažvelkime į standartinę PHP kūrimo aplinką.

Daugeliu atvejų programą paleidžiate naudodami nginx žiniatinklio serverio ir PHP-FPM serverio derinį. Pirmasis aptarnauja statinius failus ir nukreipia konkrečias užklausas į PHP-FPM, o pats PHP-FPM vykdo PHP kodą. Galbūt jūs naudojate mažiau populiarų Apache ir mod_php derinį. Tačiau, nors tai veikia šiek tiek kitaip, principai yra tie patys.

Pažiūrėkime, kaip PHP-FPM vykdo programos kodą. Kai gaunama užklausa, PHP-FPM inicijuoja antrinį PHP procesą ir perduoda išsamią užklausos informaciją kaip jo būsenos dalį (_GET, _POST, _SERVER ir kt.).

Vykdant PHP scenarijų būsena negali pasikeisti, todėl yra tik vienas būdas gauti naują įvesties duomenų rinkinį: išvalant proceso atmintį ir ją iš naujo inicijuojant.

Šis vykdymo modelis turi daug privalumų. Jums nereikia daug rūpintis dėl atminties suvartojimo, visi procesai yra visiškai izoliuoti, o jei vienas iš jų miršta, jis bus automatiškai atkurtas, nepaveikdamas likusių procesų. Tačiau šis metodas taip pat turi trūkumų, kurie atsiranda bandant išplėsti programą.

Įprastos PHP aplinkos trūkumai ir neefektyvumas

Jei užsiimate profesiniu tobulėjimu PHP kalba, tuomet žinote, kur pradėti naują projektą – pasirinkdami karkasą. Jį sudaro priklausomybės įvedimo bibliotekos, ORM, vertimai ir šablonai. Ir, žinoma, visą vartotojo įvestį galima patogiai sudėti į vieną objektą (Symfony/HttpFoundation arba PSR-7). Karkasai yra puikūs!

Bet viskas turi savo kainą. Bet kurioje įmonės lygio sistemoje, norėdami apdoroti paprastą vartotojo užklausą arba pasiekti duomenų bazę, turėsite įkelti mažiausiai dešimtis failų, sukurti daugybę klasių ir išanalizuoti kelias konfigūracijas. Tačiau blogiausia, kad atlikus kiekvieną užduotį reikės viską iš naujo nustatyti ir pradėti iš naujo: visas ką tik inicijuotas kodas tampa nenaudingas, jo pagalba nebeapdorosite kitos užklausos. Pasakykite tai bet kuriam programuotojui, kuris rašo bet kuria kita kalba, ir pamatysite sumišimą jo veide.

PHP inžinieriai daugelį metų ieškojo būdų, kaip išspręsti šią problemą, naudodami protingus tingaus įkėlimo būdus, mikrorėmus, optimizuotas bibliotekas, talpyklas ir kt. Tačiau galiausiai vis tiek turite iš naujo nustatyti visą programą ir pradėti iš naujo, vėl ir vėl. (Vertėjo pastaba: ši problema bus iš dalies išspręsta atsiradus preload PHP 7.4)

Ar PHP su Go gali atlaikyti daugiau nei vieną užklausą?

Galima rašyti PHP scenarijus, kurie truks ilgiau nei kelias minutes (iki valandų ar dienų): pavyzdžiui, cron užduotis, CSV analizatorius, eilių tvarkykles. Jie visi dirba pagal tą patį scenarijų: paima užduotį, ją vykdo ir laukia kitos. Kodas yra atmintyje, todėl sutaupoma brangių milisekundžių, nes norint įkelti sistemą ir programą reikia atlikti daug papildomų veiksmų.

Tačiau sukurti ilgaamžius scenarijus nėra taip paprasta. Bet kokia klaida visiškai sustabdo procesą, atminties nutekėjimo diagnozavimas veda iš proto ir nebegalite naudoti F5 derinimo.

Situacija pagerėjo išleidus PHP 7: atsirado patikimas šiukšlių surinkėjas, tapo lengviau valdyti klaidas, o branduolio plėtiniai dabar apsaugoti nuo nutekėjimo. Tiesa, inžinieriai vis tiek turi būti atsargūs su atmintimi ir žinoti būsenos problemas kode (ar yra kalba, kurioje dėl šių dalykų nereikėtų rūpintis?). Tačiau PHP 7 mūsų laukia mažiau netikėtumų.

Ar galima paimti darbo su ilgalaikiais PHP scenarijais modelį, pritaikyti jį smulkesnėms užduotims, pvz., HTTP užklausų apdorojimui, ir tokiu būdu panaikinti poreikį įkelti viską nuo nulio kiekvienai užklausai?

Norėdami išspręsti šią problemą, pirmiausia turėjome įdiegti serverio programą, kuri galėtų priimti HTTP užklausas ir po vieną persiųsti jas PHP darbuotojui, jo kiekvieną kartą nenužudydama.

Žinojome, kad žiniatinklio serverį galime rašyti grynu PHP (PHP-PM) arba C plėtiniu (Swoole). Ir nors kiekvienas metodas turi savų privalumų, abu variantai mums netiko – norėjome kažko daugiau. Mums reikėjo daugiau nei tik žiniatinklio serverio – tikėjomės gauti sprendimą, kuris galėtų išgelbėti mus nuo problemų, susijusių su „sunkia pradžia“ PHP, kurį būtų galima lengvai pritaikyti ir išplėsti konkrečioms programoms. Tai yra, mums reikėjo programų serverio.

Ar „Go“ gali padėti šiuo klausimu? Žinojome, kad tai gali, nes kalba sujungia programas į vieną dvejetainį failą; tai yra kelių platformų; darbui su HTTP naudoja savo, labai elegantišką, lygiagrečio apdorojimo modelį (lygiagretumą) ir biblioteką; ir galiausiai tūkstančiai atvirojo kodo bibliotekų ir integracijų bus prieinamos mums.

Dviejų programavimo kalbų derinimo sunkumai

Pirmas žingsnis buvo nustatyti, kaip dvi ar daugiau programų susisieks tarpusavyje.

Pavyzdžiui, naudojant nuostabi biblioteka Alexas Palaestras galėtų įgyvendinti atminties dalijimąsi tarp PHP ir Go procesų (panašiai kaip mod_php programoje Apache). Tačiau ši biblioteka turi funkcijų, kurios riboja jos naudojimą sprendžiant mūsų problemą.

Mes nusprendėme naudoti kitą, labiau įprastą metodą: sukurti sąveiką tarp procesų per lizdus / vamzdynus. Šis metodas įrodė savo patikimumą per pastaruosius dešimtmečius ir buvo gerai optimizuotas operacinės sistemos lygiu.

Pirmiausia sukūrėme paprastą dvejetainį protokolą, skirtą keistis duomenimis tarp procesų ir tvarkyti perdavimo klaidas. Paprasčiausia forma šis protokolo tipas yra panašus į tinklo eilutė с fiksuoto dydžio paketo antraštė (mūsų atveju 17 baitų), kuriame yra informacija apie paketo tipą, jo dydį ir dvejetainę kaukę duomenų vientisumui patikrinti.

PHP pusėje mes naudojome pakuotės funkcija, o Go pusėje – biblioteka kodavimas / dvejetainis.

Mums atrodė, kad vieno protokolo neužtenka – todėl pridėjome galimybę skambinti Eikite į net/rpc paslaugas tiesiai iš PHP. Vėliau tai mums labai padėjo kuriant, nes galėjome lengvai integruoti Go bibliotekas į PHP programas. Šio darbo rezultatas gali būti matomas, pavyzdžiui, kitame mūsų atvirojo kodo produkte Goridžas.

Užduočių paskirstymas keliems PHP darbuotojams

Įdiegę sąveikos mechanizmą pradėjome galvoti, kaip efektyviausiai užduotis perkelti į PHP procesus. Kai gaunama užduotis, programos serveris turi pasirinkti laisvą darbuotoją, kad ją atliktų. Jei darbuotojas / procesas baigiasi dėl klaidos arba „miršta“, mes jo atsikratome ir sukuriame naują, kad jį pakeistume. Ir jei darbuotojas/procesas sėkmingai baigtas, grąžiname jį į darbuotojų grupę, kuri gali atlikti užduotis.

„RoadRunner“: PHP nėra sukurtas tam, kad mirtų, arba „Golang“ į pagalbą

Naudojome aktyvių darbuotojų telkiniui saugoti buferinis kanalas, norėdami pašalinti netikėtai „mirusius“ darbuotojus iš telkinio, įtraukėme klaidų ir darbuotojų būsenų sekimo mechanizmą.

Dėl to gavome veikiantį PHP serverį, galintį apdoroti visas dvejetaine forma pateiktas užklausas.

Kad mūsų programa veiktų kaip žiniatinklio serveris, turėjome pasirinkti patikimą PHP standartą, kuris atspindėtų visas gaunamas HTTP užklausas. Mūsų atveju mes tiesiog transformuoti net/http užklausa iš Eiti į formatą PSR-7kad jis būtų suderinamas su daugeliu šiandien prieinamų PHP sistemų.

Kadangi PSR-7 laikomas nekintamu (kai kurie sakytų, kad techniškai taip nėra), kūrėjai turi rašyti programas, kurios iš esmės nelaiko užklausos kaip pasaulinio subjekto. Tai puikiai dera su ilgalaikių PHP procesų koncepcija. Mūsų galutinis įgyvendinimas, kuris dar nebuvo pavadintas, atrodė taip:

„RoadRunner“: PHP nėra sukurtas tam, kad mirtų, arba „Golang“ į pagalbą

Pristatome RoadRunner – didelio našumo PHP programų serveris

Pirmoji mūsų bandymo užduotis buvo API užpakalinė programa, kuri periodiškai patirdavo netikėtų užklausų (daug dažniau nei įprastai). Nors nginx daugeliu atvejų pakako, mes reguliariai susidurdavome su 502 klaidomis, nes negalėjome pakankamai greitai subalansuoti sistemos, kad būtų galima padidinti apkrovą.

Norėdami pakeisti šį sprendimą, 2018 m. pradžioje įdiegėme savo pirmąjį PHP/Go taikomųjų programų serverį. Ir iš karto gavome neįtikėtiną efektą! Mes ne tik visiškai atsikratėme 502 klaidos, bet ir sugebėjome dviem trečdaliais sumažinti serverių skaičių, sutaupydami daug pinigų ir galvos skausmo inžinieriams ir produktų vadybininkams.

Iki metų vidurio ištobulinome savo sprendimą, paskelbėme jį „GitHub“ pagal MIT licenciją ir pavadinome RoadRunner, taip pabrėžiant neįtikėtiną greitį ir efektyvumą.

Kaip „RoadRunner“ gali pagerinti jūsų plėtros krūvą

taikymas RoadRunner leido mums naudoti „Middleware“ net/http „Go“ pusėje, kad galėtume atlikti JWT patikrinimą dar prieš užklausą pasiekiant PHP, taip pat tvarkyti „WebSockets“ ir pasaulinės būsenos agregaciją „Prometheus“.

Dėl integruoto RPC galite atidaryti bet kurios „Go“ bibliotekos API, skirtą PHP, nerašydami plėtinių paketų. Dar svarbiau, kad RoadRunner gali būti naudojamas diegti naujus ne HTTP serverius. Pavyzdžiai apima tvarkyklių paleidimą PHP „AWS Lambda“, sukuriant patikimus eilių tvarkykles ir netgi pridedant grRPC į mūsų programas.

Naudodami PHP ir Go bendruomenes padidinome sprendimo stabilumą, padidinome programos našumą iki 40 kartų kai kurių testų metu, patobulinome derinimo įrankius, įdiegėme integraciją su Symfony sistema ir pridėjome HTTPS, HTTP/ 2, papildiniai ir PSR-17.

išvada

Kai kurie žmonės vis dar yra pagauti pasenusio PHP kaip lėtos, sudėtingos kalbos, tinkančios tik „WordPress“ papildiniams rašyti. Šie žmonės netgi gali pasakyti, kad PHP turi apribojimą: kai programa tampa pakankamai didelė, reikia pasirinkti „brandesnę“ kalbą ir perrašyti per daugelį metų kauptą kodų bazę.

Į visa tai noriu atsakyti: pagalvok dar kartą. Manome, kad tik jūs galite nustatyti PHP apribojimus. Galite praleisti visą gyvenimą šokinėdami nuo vienos kalbos prie kitos, bandydami rasti tinkamiausią savo poreikiams, arba galite pradėti galvoti apie kalbas kaip apie įrankius. Tokios kalbos kaip PHP trūkumai iš tikrųjų gali būti jos sėkmės priežastis. O jei derinsite ją su kita kalba, pvz., „Go“, galėsite sukurti daug galingesnius produktus nei apsiribodami tik viena kalba.

Dirbdami su „Go“ ir „PHP“ deriniu, galime pasakyti, kad mums jie patinka. Mes neplanuojame paaukoti vieno dėl kito, o ieškome būdų, kaip gauti dar daugiau naudos iš šio dvigubo krūvos.

UPD: sveikiname „RoadRunner“ kūrėją ir originalaus straipsnio bendraautorius – Lachesis

Šaltinis: www.habr.com

Добавить комментарий