RoadRunner: PHP ei ole rakennettu kuolemaan, tai Golangia auttamaan

RoadRunner: PHP ei ole rakennettu kuolemaan, tai Golangia auttamaan

Hei Habr! Olemme aktiivisia Badoossa työskentelee PHP-suorituskyvyn parissa, koska meillä on melko suuri järjestelmä tällä kielellä ja suorituskykyongelma on rahaa säästävä ongelma. Yli kymmenen vuotta sitten loimme tätä varten PHP-FPM:n, joka oli aluksi sarja korjaustiedostoja PHP:lle ja sitten tuli viralliseen jakeluun.

Viime vuosina PHP on edistynyt valtavasti: roskakeräys on parantunut, vakaustaso on parantunut - nykyään voit kirjoittaa demoneita ja pitkäikäisiä skriptejä PHP: llä ilman ongelmia. Tämä antoi Spiral Scoutin mennä pidemmälle: RoadRunner, toisin kuin PHP-FPM, ei puhdista muistia pyyntöjen välillä, mikä lisää suorituskykyä (vaikka tämä lähestymistapa monimutkaistaa kehitysprosessia). Kokeilemme parhaillaan tätä työkalua, mutta meillä ei ole vielä jaettavia tuloksia. Jotta heidän odottamisensa olisi hauskempaa, julkaisemme käännöksen Spiral Scoutin RoadRunner-ilmoituksesta.

Artikkelin lähestymistapa on lähellä meitä: ongelmamme ratkaisemisessa käytämme myös useimmiten joukkoa PHP:tä ja Go:ta, saamme hyödyksi molemmista kielistä emmekä hylkää toista toisen hyväksi.

Enjoy!

Viimeisen kymmenen vuoden aikana olemme tehneet listalta sovelluksia yrityksille Fortune 500ja yrityksille, joiden yleisö on enintään 500 käyttäjää. Koko tämän ajan insinöörimme ovat kehittäneet taustajärjestelmää pääasiassa PHP:llä. Mutta kaksi vuotta sitten jollakin oli suuri vaikutus paitsi tuotteidemme suorituskykyyn myös niiden skaalautumiseen - otimme Golangin (Go) teknologiapinoimme.

Melkein välittömästi huomasimme, että Go antoi meille mahdollisuuden rakentaa suurempia sovelluksia jopa 40-kertaisilla suorituskyvyn parannuksilla. Sen avulla pystyimme laajentamaan olemassa olevia PHP-tuotteitamme parantamalla niitä yhdistämällä molempien kielten edut.

Kerromme sinulle, kuinka Go:n ja PHP:n yhdistelmä auttaa ratkaisemaan todellisia kehitysongelmia ja kuinka siitä on tullut meille työkalu, joka voi päästä eroon joistakin PHP kuoleva malli.

Päivittäinen PHP-kehitysympäristösi

Ennen kuin puhumme siitä, kuinka voit käyttää Goa PHP-kuolevan mallin elvyttämiseen, katsotaanpa oletusarvoista PHP-kehitysympäristöäsi.

Useimmissa tapauksissa käytät sovellustasi nginx-verkkopalvelimen ja PHP-FPM-palvelimen yhdistelmällä. Edellinen palvelee staattisia tiedostoja ja ohjaa tietyt pyynnöt PHP-FPM:lle, kun taas PHP-FPM itse suorittaa PHP-koodin. Saatat käyttää vähemmän suosittua Apachen ja mod_php:n yhdistelmää. Mutta vaikka se toimii hieman eri tavalla, periaatteet ovat samat.

Katsotaanpa, kuinka PHP-FPM suorittaa sovelluskoodin. Kun pyyntö saapuu, PHP-FPM alustaa PHP-aliprosessin ja välittää pyynnön tiedot osana sen tilaa (_GET, _POST, _SERVER jne.).

Tila ei voi muuttua PHP-skriptin suorittamisen aikana, joten on vain yksi tapa saada uusi syöttötietojoukko: tyhjentämällä prosessimuisti ja alustamalla se uudelleen.

Tällä suoritusmallilla on monia etuja. Muistinkulutuksesta ei tarvitse huolehtia liikaa, kaikki prosessit ovat täysin eristettyjä, ja jos jokin niistä "kuolee", se luodaan automaattisesti uudelleen eikä se vaikuta muihin prosesseihin. Mutta tällä lähestymistavalla on myös haittoja, jotka ilmenevät, kun yritetään skaalata sovellusta.

Tavallisen PHP-ympäristön haitat ja tehottomuudet

Jos olet ammattimainen PHP-kehittäjä, tiedät mistä aloittaa uusi projekti - puitteiden valinnan avulla. Se koostuu riippuvuuden lisäyskirjastoista, ORM:ista, käännöksistä ja malleista. Ja tietysti kaikki käyttäjän syötöt voidaan kätevästi laittaa yhteen objektiin (Symfony/HttpFoundation tai PSR-7). Kehykset ovat siistejä!

Mutta kaikella on hintansa. Missä tahansa yritystason viitekehyksessä yksinkertaisen käyttäjäpyynnön tai tietokantaan pääsyn käsittelemiseksi sinun on ladattava vähintään kymmeniä tiedostoja, luotava useita luokkia ja jäsennettävä useita määrityksiä. Mutta pahinta on, että jokaisen tehtävän suorittamisen jälkeen sinun on nollattava kaikki ja aloitettava alusta: kaikki juuri aloittamasi koodi muuttuu hyödyttömäksi, sen avulla et enää käsittele toista pyyntöä. Kerro tämä kenelle tahansa ohjelmoijalle, joka kirjoittaa jollain muulla kielellä, niin näet hämmennyksen hänen kasvoillaan.

PHP-insinöörit ovat etsineet tapoja ratkaista tämä ongelma vuosia käyttämällä älykkäitä laiskalataustekniikoita, mikrokehyksiä, optimoituja kirjastoja, välimuistia jne. Mutta lopulta sinun on silti nollattava koko sovellus ja aloitettava alusta, yhä uudelleen ja uudelleen. (Kääntäjän huomautus: tämä ongelma ratkaistaan ​​osittain, kun preload PHP 7.4:ssä)

Selviääkö PHP Golla useamman kuin yhden pyynnön?

On mahdollista kirjoittaa PHP-skriptejä, jotka elävät pidempään kuin muutaman minuutin (jopa tunteja tai päiviä): esimerkiksi cron-tehtäviä, CSV-jäsentimiä, jononkatkaisijoita. Ne kaikki toimivat saman skenaarion mukaan: he hakevat tehtävän, suorittavat sen ja odottavat seuraavaa. Koodi on muistissa koko ajan, mikä säästää arvokkaita millisekunteja, koska kehyksen ja sovelluksen lataaminen edellyttää monia lisävaiheita.

Mutta pitkäikäisten skriptien kehittäminen ei ole helppoa. Mikä tahansa virhe tappaa prosessin kokonaan, muistivuotojen diagnosointi on raivostuttavaa, eikä F5-virheenkorjaus ole enää mahdollista.

Tilanne on parantunut PHP 7:n julkaisun myötä: luotettava roskankerääjä on ilmestynyt, virheiden käsittely on helpottunut ja ytimen laajennukset ovat nyt vuotamattomia. Totta, insinöörien on silti oltava varovaisia ​​muistin kanssa ja oltava tietoisia koodin tilaongelmista (onko olemassa kieltä, joka voi jättää nämä asiat huomiotta?). PHP 7:llä on kuitenkin vähemmän yllätyksiä.

Onko mahdollista ottaa mallia työskennellä pitkäikäisten PHP-skriptien kanssa, mukauttaa se triviaalimpiin tehtäviin, kuten HTTP-pyyntöjen käsittelyyn, ja siten päästä eroon tarpeesta ladata kaikki alusta alkaen jokaisen pyynnön yhteydessä?

Tämän ongelman ratkaisemiseksi meidän oli ensin otettava käyttöön palvelinsovellus, joka voi hyväksyä HTTP-pyynnöt ja ohjata ne yksitellen PHP-työntekijälle tappamatta sitä joka kerta.

Tiesimme, että voimme kirjoittaa web-palvelimen puhtaalla PHP:llä (PHP-PM) tai käyttämällä C-laajennusta (Swoole). Ja vaikka jokaisella menetelmällä on omat etunsa, molemmat vaihtoehdot eivät sopineet meille - halusimme jotain enemmän. Tarvitsimme muutakin kuin vain web-palvelimen - odotimme saavamme ratkaisun, joka säästäisi meidät PHP:n "kovaan käynnistykseen" liittyviltä ongelmilta, joita voidaan samalla helposti mukauttaa ja laajentaa tiettyihin sovelluksiin. Eli tarvitsimme sovelluspalvelimen.

Voiko Go auttaa tässä? Tiesimme, että se voisi, koska kieli kokoaa sovellukset yksittäisiksi binäärisiksi; se on monialustainen; käyttää omaa, erittäin tyylikästä rinnakkaiskäsittelymalliaan (concurrency) ja kirjastoa HTTP:n kanssa työskentelyyn; ja lopuksi tuhansia avoimen lähdekoodin kirjastoja ja integraatioita on saatavillamme.

Kahden ohjelmointikielen yhdistämisen vaikeudet

Ensinnäkin oli tarpeen määrittää, kuinka kaksi tai useampi sovellus kommunikoi keskenään.

Esimerkiksi käyttämällä erinomainen kirjasto Alex Palaestras, oli mahdollista jakaa muistia PHP- ja Go-prosessien välillä (samanlainen kuin mod_php Apachessa). Mutta tässä kirjastossa on ominaisuuksia, jotka rajoittavat sen käyttöä ongelmamme ratkaisemisessa.

Päätimme käyttää erilaista, yleisempää lähestymistapaa: rakentaa vuorovaikutusta prosessien välille pistorasioiden / putkien kautta. Tämä lähestymistapa on osoittautunut luotettavaksi viime vuosikymmeninä ja se on optimoitu hyvin käyttöjärjestelmätasolla.

Aluksi loimme yksinkertaisen binaariprotokollan tietojen vaihtamiseen prosessien välillä ja siirtovirheiden käsittelyyn. Yksinkertaisimmassa muodossaan tämäntyyppinen protokolla on samanlainen kuin verkkomerkkijono с kiinteän kokoisen paketin otsikko (tapauksessamme 17 tavua), joka sisältää tietoa paketin tyypistä, sen koosta ja binäärimaskin tietojen eheyden tarkistamiseksi.

PHP-puolella käytimme pakkaustoiminto, ja Go-puolella kirjasto koodaus/binääri.

Meistä näytti, että yksi protokolla ei riittänyt - ja lisäsimme soittomahdollisuuden net/rpc go -palvelut suoraan PHP:stä. Myöhemmin tämä auttoi meitä paljon kehityksessä, koska pystyimme helposti integroimaan Go-kirjastot PHP-sovelluksiin. Tämän työn tulos näkyy esimerkiksi toisessa avoimen lähdekoodin tuotteessamme Gorridge.

Tehtävien jakaminen useille PHP-työntekijöille

Vuorovaikutusmekanismin käyttöönoton jälkeen aloimme miettiä tehokkainta tapaa siirtää tehtäviä PHP-prosesseihin. Kun tehtävä saapuu, sovelluspalvelimen on valittava vapaa työntekijä sen suorittamiseksi. Jos työntekijä/prosessi poistuu virheestä tai "kuolee", pääsemme eroon siitä ja luomme uuden tilalle. Ja jos työntekijä/prosessi on suoritettu onnistuneesti, palautamme sen työntekijöiden joukkoon, jotka ovat käytettävissä tehtävien suorittamiseen.

RoadRunner: PHP ei ole rakennettu kuolemaan, tai Golangia auttamaan

Käytimme aktiivisten työntekijöiden joukon tallentamiseen puskuroitu kanava, poistaaksemme yllättäen "kuolleet" työntekijät poolista, lisäsimme mekanismin, jolla voidaan seurata työntekijöiden virheitä ja tilaa.

Tuloksena saimme toimivan PHP-palvelimen, joka pystyy käsittelemään kaikki binäärimuodossa esitetyt pyynnöt.

Jotta sovelluksemme voisi toimia verkkopalvelimena, meidän piti valita luotettava PHP-standardi edustamaan kaikkia saapuvia HTTP-pyyntöjä. Meidän tapauksessamme me vain muuttaa net/http-pyyntö osoitteesta Siirry muotoon PSR-7joten se on yhteensopiva useimpien nykyään saatavilla olevien PHP-kehysten kanssa.

Koska PSR-7:ää pidetään muuttumattomana (jotkut sanovat teknisesti, ettei se ole), kehittäjien on kirjoitettava sovelluksia, jotka eivät periaatteessa käsittele pyyntöä globaalina kokonaisuutena. Tämä sopii hyvin pitkäikäisten PHP-prosessien käsitteeseen. Lopullinen toteutuksemme, jota ei ole vielä nimetty, näytti tältä:

RoadRunner: PHP ei ole rakennettu kuolemaan, tai Golangia auttamaan

Esittelyssä RoadRunner - korkean suorituskyvyn PHP-sovelluspalvelin

Ensimmäinen testitehtävämme oli API-taustajärjestelmä, joka ajoittain puhkeaa arvaamattomasti (paljon useammin kuin tavallisesti). Vaikka nginx oli riittävä useimmissa tapauksissa, kohtasimme säännöllisesti 502 virhettä, koska emme pystyneet tasapainottamaan järjestelmää tarpeeksi nopeasti odotettuun kuormituksen kasvuun nähden.

Tämän ratkaisun korvaamiseksi otimme käyttöön ensimmäisen PHP/Go-sovelluspalvelimemme vuoden 2018 alussa. Ja sai heti uskomattoman vaikutuksen! Emme vain päässeet eroon 502-virheestä kokonaan, vaan pystyimme vähentämään palvelimien määrää kahdella kolmasosalla, mikä säästää paljon rahaa ja päänsärkylääkkeitä insinööreille ja tuotepäälliköille.

Vuoden puoliväliin mennessä olimme parantaneet ratkaisuamme, julkaisseet sen GitHubissa MIT-lisenssillä ja nimenneet sen RoadRunner, mikä korostaa sen uskomatonta nopeutta ja tehokkuutta.

Kuinka RoadRunner voi parantaa kehityspinoasi

Sovellus RoadRunner antoi meille mahdollisuuden käyttää Middleware net/http:tä Go-puolella JWT-vahvistuksen suorittamiseen ennen kuin pyyntö saavuttaa PHP:n, sekä käsitellä WebSocketteja ja aggregaattitilaa maailmanlaajuisesti Prometheuksessa.

Sisäänrakennetun RPC:n ansiosta voit avata minkä tahansa Go-kirjaston API:n PHP:lle kirjoittamatta laajennuskääreitä. Vielä tärkeämpää on, että RoadRunnerilla voit ottaa käyttöön uusia ei-HTTP-palvelimia. Esimerkkejä ovat käsittelijöiden suorittaminen PHP:ssä AWS Lambda, luoden luotettavia jononkatkaisijoita ja jopa lisäämällä gRPC sovelluksiimme.

PHP- ja Go-yhteisöjen avulla paransimme ratkaisun vakautta, paransimme sovellusten suorituskykyä jopa 40-kertaiseksi joissakin testeissä, paransimme virheenkorjaustyökaluja, otettiin käyttöön integraatio Symfony-kehykseen ja lisäsimme HTTPS-, HTTP/2-, laajennuksia ja PSR-17:ää.

Johtopäätös

Jotkut ihmiset ovat edelleen kiinni vanhentuneesta käsityksestä PHP:stä hitaana ja raskaana kielenä, joka sopii vain WordPressin lisäosien kirjoittamiseen. Nämä ihmiset saattavat jopa sanoa, että PHP:llä on tällainen rajoitus: kun sovellus kasvaa tarpeeksi suureksi, sinun on valittava "kypsempi" kieli ja kirjoitettava uudelleen monien vuosien aikana kertynyt koodikanta.

Kaikkeen tähän haluan vastata: ajattele uudelleen. Uskomme, että vain sinä asetat rajoituksia PHP:lle. Voit viettää koko elämäsi siirtymällä kielestä toiseen yrittäen löytää tarpeisiisi täydellisen sopivan tai voit alkaa ajatella kieliä työkaluina. PHP:n kaltaisen kielen oletetut puutteet voivat itse asiassa olla syy sen menestykseen. Ja jos yhdistät sen johonkin toiseen kieleen, kuten Go, luot paljon tehokkaampia tuotteita kuin jos käyttäisit vain yhtä kieltä.

Kun olemme työskennelleet Go:n ja PHP:n kanssa, voimme sanoa, että rakastamme niitä. Emme aio uhrata toista toisen puolesta - päinvastoin, etsimme tapoja saada vielä enemmän arvoa tästä kaksoispinosta.

UPD: toivotamme tervetulleeksi RoadRunnerin luojan ja alkuperäisen artikkelin kirjoittajan - Lachesis

Lähde: will.com

Lisää kommentti