RoadRunner: PHP nije napravljen da umre, ili Golang da spašava

RoadRunner: PHP nije napravljen da umre, ili Golang da spašava

Hej Habr! Aktivni smo na Badoou radi na performansama PHP-a, pošto imamo prilično veliki sistem na ovom jeziku i pitanje performansi je pitanje uštede novca. Za to smo prije više od deset godina kreirali PHP-FPM, koji je u početku bio skup zakrpa za PHP, a kasnije je ušao u zvaničnu distribuciju.

Posljednjih godina PHP je napravio veliki napredak: sakupljač smeća je poboljšan, nivo stabilnosti je povećan - danas možete bez problema pisati demone i dugovječne skripte u PHP-u. Ovo je omogućilo Spiral Scout-u da ide dalje: RoadRunner, za razliku od PHP-FPM-a, ne čisti memoriju između zahtjeva, što daje dodatni dobitak u performansama (iako ovaj pristup komplikuje proces razvoja). Trenutno eksperimentiramo s ovim alatom, ali još uvijek nemamo rezultate za podijeliti. Da njihovo čekanje bude zabavnije, objavljujemo prijevod RoadRunner najave iz Spiral Scout-a.

Pristup iz članka nam je blizak: prilikom rješavanja naših problema najčešće koristimo gomilu PHP-a i Go-a, uzimajući prednosti oba jezika i ne napuštajući jedan u korist drugog.

Uživajte!

U posljednjih deset godina kreirali smo aplikacije za kompanije sa liste Fortune 500, i za preduzeća s publikom od najviše 500 korisnika. Sve ovo vreme naši inženjeri su razvijali backend uglavnom u PHP-u. Ali prije dvije godine, nešto je imalo veliki utjecaj ne samo na performanse naših proizvoda, već i na njihovu skalabilnost - uveli smo Golang (Go) u naš tehnološki stog.

Gotovo odmah smo otkrili da nam Go omogućava izradu većih aplikacija sa do 40x poboljšanja performansi. Sa njim smo bili u mogućnosti da proširimo naše postojeće PHP proizvode, poboljšavajući ih kombinovanjem prednosti oba jezika.

Reći ćemo vam kako kombinacija Go i PHP-a pomaže u rješavanju stvarnih razvojnih problema i kako se za nas pretvorila u alat koji može riješiti neke od problema povezanih sa PHP model umiranja.

Vaše svakodnevno PHP okruženje za razvoj

Prije nego što razgovaramo o tome kako možete koristiti Go da oživite PHP model koji umire, hajde da pogledamo vaše zadano PHP okruženje za razvoj.

U većini slučajeva svoju aplikaciju pokrećete koristeći kombinaciju nginx web servera i PHP-FPM servera. Prvi opslužuje statičke datoteke i preusmjerava specifične zahtjeve na PHP-FPM, dok sam PHP-FPM izvršava PHP kod. Možda koristite manje popularnu kombinaciju Apache i mod_php. Ali iako radi malo drugačije, principi su isti.

Pogledajmo kako PHP-FPM izvršava kod aplikacije. Kada dođe zahtjev, PHP-FPM inicijalizira PHP podređeni proces i prosljeđuje detalje zahtjeva kao dio njegovog stanja (_GET, _POST, _SERVER, itd.).

Stanje se ne može promijeniti tokom izvršavanja PHP skripte, tako da je jedini način da dobijete novi skup ulaznih podataka brisanje procesne memorije i ponovno je inicijaliziranje.

Ovaj model izvedbe ima mnoge prednosti. Ne morate previše brinuti o potrošnji memorije, svi procesi su potpuno izolirani, a ako jedan od njih "umre", automatski će se ponovo kreirati i neće utjecati na ostale procese. Ali ovaj pristup ima i nedostatke koji se pojavljuju kada pokušavate skalirati aplikaciju.

Nedostaci i neefikasnosti redovnog PHP okruženja

Ako ste profesionalni PHP programer, onda znate odakle da započnete novi projekat - izborom okvira. Sastoji se od biblioteka za ubrizgavanje zavisnosti, ORM-ova, prevoda i šablona. I, naravno, sav korisnički unos može se jednostavno staviti u jedan objekat (Symfony/HttpFoundation ili PSR-7). Okviri su cool!

Ali sve ima svoju cijenu. U bilo kojem okviru na nivou preduzeća, da biste obradili jednostavan korisnički zahtev ili pristup bazi podataka, moraćete da učitate najmanje desetine datoteka, kreirate brojne klase i analizirate nekoliko konfiguracija. Ali najgore je to što ćete nakon završetka svakog zadatka morati sve resetirati i početi ispočetka: sav kod koji ste upravo pokrenuli postaje beskoristan, uz njegovu pomoć više nećete obrađivati ​​drugi zahtjev. Recite ovo svakom programeru koji piše na nekom drugom jeziku i videćete zbunjenost na njegovom licu.

PHP inženjeri godinama traže načine da riješe ovaj problem, koristeći pametne tehnike lijenog učitavanja, mikroframeworks, optimizirane biblioteke, keš memoriju, itd. Ali na kraju ipak morate resetirati cijelu aplikaciju i početi iznova, iznova i iznova. (Napomena prevodioca: ovaj problem će biti djelimično riješen dolaskom preload u PHP 7.4)

Može li PHP sa Go preživjeti više od jednog zahtjeva?

Moguće je pisati PHP skripte koje traju duže od nekoliko minuta (do sati ili dana): na primjer, cron zadaci, CSV parseri, razbijači redova. Svi rade po istom scenariju: preuzimaju zadatak, izvršavaju ga i čekaju sljedeći. Kod se stalno nalazi u memoriji, štedeći dragocjene milisekunde jer su potrebni mnogi dodatni koraci za učitavanje okvira i aplikacije.

Ali razvoj dugotrajnih skripti nije lak. Svaka greška potpuno ubija proces, dijagnosticiranje curenja memorije je bijesno, a F5 otklanjanje grešaka više nije moguće.

Situacija se poboljšala izdavanjem PHP-a 7: pojavio se pouzdan sakupljač smeća, postalo je lakše rukovati greškama, a ekstenzije kernela su sada otporne na curenje. Istina, inženjeri i dalje moraju biti pažljivi sa memorijom i biti svjesni problema sa stanjem u kodu (da li postoji jezik koji može zanemariti ove stvari?). Ipak, PHP 7 nam sprema manje iznenađenja.

Da li je moguće uzeti model rada sa dugovječnim PHP skriptama, prilagoditi ga trivijalnijim zadacima kao što je obrada HTTP zahtjeva i tako se riješiti potrebe da se sve učitava ispočetka sa svakim zahtjevom?

Da bismo riješili ovaj problem, prvo smo morali implementirati serversku aplikaciju koja može prihvatiti HTTP zahtjeve i preusmjeriti ih jedan po jedan na PHP radnika bez da ga svaki put ubije.

Znali smo da možemo napisati web server u čistom PHP-u (PHP-PM) ili koristeći C ekstenziju (Swoole). I iako svaka metoda ima svoje prednosti, obje opcije nam nisu odgovarale - htjeli smo nešto više. Trebalo nam je više od samog web servera – očekivali smo da ćemo dobiti rješenje koje bi nas moglo spasiti od problema povezanih sa “teškim startom” u PHP-u, koje bi se u isto vrijeme moglo lako prilagoditi i proširiti za određene aplikacije. Odnosno, trebao nam je server aplikacija.

Može li Go pomoći s ovim? Znali smo da može jer jezik kompajlira aplikacije u pojedinačne binarne datoteke; to je cross-platforma; koristi sopstveni, veoma elegantan, model paralelne obrade (koncurrency) i biblioteku za rad sa HTTP; i konačno, hiljade biblioteka otvorenog koda i integracija će nam biti dostupne.

Poteškoće kombinovanja dva programska jezika

Prije svega, bilo je potrebno odrediti kako će dvije ili više aplikacija međusobno komunicirati.

Na primjer, korištenjem odlična biblioteka Alex Palaestras, bilo je moguće dijeliti memoriju između PHP i Go procesa (slično mod_php u Apacheu). Ali ova biblioteka ima karakteristike koje ograničavaju njenu upotrebu za rešavanje našeg problema.

Odlučili smo da koristimo drugačiji, uobičajeniji pristup: da izgradimo interakciju između procesa kroz utičnice/cevovode. Ovaj pristup se pokazao kao pouzdan tokom proteklih decenija i dobro je optimizovan na nivou operativnog sistema.

Za početak smo kreirali jednostavan binarni protokol za razmjenu podataka između procesa i rukovanje greškama u prijenosu. U svom najjednostavnijem obliku, ovaj tip protokola je sličan netstring с zaglavlje paketa fiksne veličine (u našem slučaju 17 bajtova), koji sadrži informacije o tipu paketa, njegovoj veličini i binarnu masku za provjeru integriteta podataka.

Na strani PHP-a koju smo koristili funkcija pakovanja, a na strani Go, biblioteka kodiranje/binarni.

Činilo nam se da jedan protokol nije dovoljan - i dodali smo mogućnost pozivanja net/rpc go usluge direktno iz PHP-a. Kasnije nam je to mnogo pomoglo u razvoju, jer smo lako mogli integrirati Go biblioteke u PHP aplikacije. Rezultat ovog rada može se vidjeti, na primjer, u našem drugom proizvodu otvorenog koda Goridge.

Distribucija zadataka među više PHP radnika

Nakon implementacije mehanizma interakcije, počeli smo razmišljati o najefikasnijem načinu prijenosa zadataka u PHP procese. Kada stigne zadatak, poslužitelj aplikacija mora odabrati slobodnog radnika koji će ga izvršiti. Ako radnik/proces izađe s greškom ili "umre", riješimo ga se i kreiramo novi da ga zamijenimo. A ako je radnik/proces uspješno završen, vraćamo ga u skup radnika dostupnih za obavljanje zadataka.

RoadRunner: PHP nije napravljen da umre, ili Golang da spašava

Za skladištenje bazena aktivnih radnika koristili smo baferovani kanal, da bismo uklonili neočekivano “mrtve” radnike iz bazena, dodali smo mehanizam za praćenje grešaka i stanja radnika.

Kao rezultat, dobili smo funkcionalan PHP server sposoban da obradi sve zahtjeve predstavljene u binarnom obliku.

Da bi naša aplikacija počela raditi kao web server, morali smo odabrati pouzdan PHP standard koji će predstavljati sve dolazne HTTP zahtjeve. U našem slučaju, mi samo transformirati net/http zahtjev iz Go to format PSR-7tako da je kompatibilan sa većinom PHP okvira koji su danas dostupni.

Budući da se PSR-7 smatra nepromjenjivim (neki bi rekli da tehnički nije), programeri moraju pisati aplikacije koje u principu ne tretiraju zahtjev kao globalni entitet. Ovo se dobro uklapa u koncept dugotrajnih PHP procesa. Naša konačna implementacija, koja tek treba da bude imenovana, izgledala je ovako:

RoadRunner: PHP nije napravljen da umre, ili Golang da spašava

Predstavljamo RoadRunner - PHP aplikacijski server visokih performansi

Naš prvi testni zadatak bio je API backend, koji periodično nepredvidivo puca (mnogo češće nego inače). Iako je nginx u većini slučajeva bio dovoljan, redovno smo nailazili na 502 greške jer nismo mogli dovoljno brzo balansirati sistem za očekivano povećanje opterećenja.

Da bismo zamijenili ovo rješenje, postavili smo naš prvi PHP/Go aplikacijski server početkom 2018. I odmah dobio neverovatan efekat! Ne samo da smo se u potpunosti riješili greške 502, već smo uspjeli smanjiti broj servera za dvije trećine, uštedivši mnogo novca i tableta protiv glavobolje za inženjere i menadžere proizvoda.

Do sredine godine smo poboljšali naše rješenje, objavili ga na GitHubu pod MIT licencom i nazvali ga roadrunner, čime se ističe njegova nevjerovatna brzina i efikasnost.

Kako RoadRunner može poboljšati vaš razvojni stog

Aplikacija roadrunner omogućilo nam je da koristimo Middleware net/http na strani Go da izvršimo JWT verifikaciju prije nego što zahtjev stigne do PHP-a, kao i da globalno rukujemo WebSockets i agregatno stanje u Prometheusu.

Zahvaljujući ugrađenom RPC-u, možete otvoriti API bilo koje Go biblioteke za PHP bez pisanja omotača ekstenzija. Što je još važnije, sa RoadRunnerom možete postaviti nove servere koji nisu HTTP. Primjeri uključuju pokretanje rukovatelja u PHP-u AWS Lambda, stvaranje pouzdanih prekidača redova, pa čak i dodavanje gRPC na naše aplikacije.

Uz pomoć PHP i Go zajednica, poboljšali smo stabilnost rješenja, povećali performanse aplikacije do 40 puta u nekim testovima, poboljšali alate za otklanjanje grešaka, implementirali integraciju sa Symfony frameworkom i dodali podršku za HTTPS, HTTP/2, dodaci i PSR-17.

zaključak

Neki ljudi su još uvijek uhvaćeni u zastarjelim pojmovima PHP-a kao sporog, nezgrapnog jezika koji je dobar samo za pisanje dodataka za WordPress. Ovi ljudi bi čak mogli reći da PHP ima takvo ograničenje: kada aplikacija postane dovoljno velika, morate odabrati "zreliji" jezik i prepisati bazu koda nakupljenu tokom mnogo godina.

Na sve ovo želim da odgovorim: razmislite ponovo. Vjerujemo da samo vi postavljate ograničenja za PHP. Možete provesti cijeli svoj život prelazeći s jednog jezika na drugi, pokušavajući pronaći savršen spoj za vaše potrebe, ili možete početi razmišljati o jezicima kao o alatima. Navodne mane jezika kao što je PHP mogu zapravo biti razlog njegovog uspeha. A ako ga kombinirate s drugim jezikom kao što je Go, tada ćete stvoriti mnogo moćnije proizvode nego da ste ograničeni na korištenje bilo kojeg jezika.

Pošto smo radili sa gomilom Go i PHP-a, možemo reći da ih volimo. Ne planiramo da žrtvujemo jedno zbog drugog – naprotiv, tražićemo načine da dobijemo još veću vrednost od ovog dvostrukog steka.

UPD: pozdravljamo kreatora RoadRunner-a i koautora originalnog članka - Lachesis

izvor: www.habr.com

Dodajte komentar