RoadRunner: PHP nije napravljen da umre, ili Golang u pomoć

RoadRunner: PHP nije napravljen da umre, ili Golang u pomoć

Hej Habr! Aktivni smo na Badoou radi na performansama PHP-a, budući da imamo prilično velik sustav na ovom jeziku i problem performansi je problem uštede novca. Prije više od deset godina za to smo kreirali PHP-FPM, koji je isprva bio set zakrpa za PHP, a kasnije je ušao u službenu distribuciju.

Posljednjih godina PHP je napravio velik napredak: skupljač smeća je poboljšan, razina stabilnosti je porasla - danas možete pisati demone i dugotrajne skripte u PHP-u bez ikakvih problema. To je omogućilo Spiral Scoutu da ide dalje: RoadRunner, za razliku od PHP-FPM-a, ne čisti memoriju između zahtjeva, što daje dodatnu dobit u performansama (iako ovaj pristup komplicira proces razvoja). Trenutačno eksperimentiramo s ovim alatom, ali još nemamo nikakvih rezultata za dijeljenje. Da im čekanje bude zabavnije, objavljujemo prijevod RoadRunner najave Spiral Scouta.

Pristup iz članka nam je blizak: kada rješavamo svoje probleme, također najčešće koristimo hrpu PHP-a i Go-a, dobivajući prednosti oba jezika i ne napuštajući jedan u korist drugog.

Uživajte!

U zadnjih desetak godina izradili smo aplikacije za tvrtke s liste Fortune 500, te za tvrtke s publikom od najviše 500 korisnika. Cijelo to vrijeme naši su inženjeri 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 niz.

Gotovo odmah smo otkrili da nam Go omogućuje izradu većih aplikacija s do 40x poboljšanjima performansi. Njime smo uspjeli proširiti postojeće proizvode napisane u PHP-u, poboljšavajući ih kombinirajući prednosti obaju jezika.

Reći ćemo vam kako kombinacija Goa i PHP-a pomaže u rješavanju stvarnih razvojnih problema i kako se za nas pretvorila u alat koji se može riješiti nekih problema povezanih s PHP umirući model.

Vaše svakodnevno PHP razvojno okruženje

Prije nego što pričamo o tome kako možete koristiti Go za oživljavanje PHP modela koji umire, pogledajmo vaše zadano PHP razvojno okruženje.

U većini slučajeva svoju aplikaciju pokrećete koristeći kombinaciju nginx web poslužitelja i PHP-FPM poslužitelja. Prvi služi statičke datoteke i preusmjerava specifične zahtjeve na PHP-FPM, dok PHP-FPM sam izvršava PHP kod. Možda koristite manje popularnu kombinaciju Apachea 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 tijekom izvršavanja PHP skripte, tako da postoji samo jedan način za dobivanje novog skupa ulaznih podataka: brisanjem memorije procesa i njezinim ponovnim inicijaliziranjem.

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

Nedostaci i neučinkovitosti običnog PHP okruženja

Ako ste profesionalni PHP programer, onda znate odakle započeti novi projekt - s izborom okvira. Sastoji se od biblioteka ubrizgavanja ovisnosti, ORM-ova, prijevoda i predložaka. I, naravno, sav korisnički unos može se jednostavno staviti u jedan objekt (Symfony/HttpFoundation ili PSR-7). Frameworks su cool!

Ali sve ima svoju cijenu. U bilo kojem okviru na razini poduzeća, da biste obradili jednostavan korisnički zahtjev ili pristup bazi podataka, morat ćete učitati najmanje desetke datoteka, stvoriti brojne klase i analizirati nekoliko konfiguracija. Ali najgora stvar je što ćete nakon završetka svakog zadatka morati sve resetirati i krenuti ispočetka: sav kod koji ste upravo pokrenuli postaje beskoristan, uz njegovu pomoć više nećete obraditi drugi zahtjev. Recite ovo bilo kojem programeru koji piše na nekom drugom jeziku, i vidjet ćete zbunjenost na njegovom licu.

PHP inženjeri su godinama tražili načine za rješavanje ovog problema, koristeći pametne tehnike odlijepljenog učitavanja, mikrookvirove, optimizirane biblioteke, predmemoriju, itd. Ali na kraju, ipak morate resetirati cijelu aplikaciju i početi ispočetka, opet i opet. (Napomena prevoditelja: ovaj problem će biti djelomično riješen pojavom preload u PHP 7.4)

Može li PHP s Goom preživjeti više od jednog zahtjeva?

Moguće je napisati PHP skripte koje žive duže od nekoliko minuta (do sati ili dana): na primjer, cron zadaci, CSV parseri, razbijači čekanja. Svi rade prema istom scenariju: dohvate zadatak, izvrše ga i čekaju sljedeći. Kod se cijelo vrijeme nalazi u memoriji, čime se štede dragocjene milisekunde jer su potrebni mnogi dodatni koraci za učitavanje okvira i aplikacije.

Ali razvijanje dugotrajnih skripti nije lako. Svaka pogreška u potpunosti ubija proces, dijagnosticiranje curenja memorije je bijesno, a otklanjanje pogrešaka F5 više nije moguće.

Situacija se popravila izdavanjem PHP-a 7: pojavio se pouzdan sakupljač smeća, postalo je lakše rješavati pogreške, a proširenja kernela sada su zaštićena od curenja. Istina, inženjeri i dalje trebaju biti oprezni s memorijom i biti svjesni problema stanja u kodu (postoji li jezik koji može ignorirati te stvari?). Ipak, PHP 7 ima manje iznenađenja za nas.

Je li moguće uzeti model rada s dugotrajnim PHP skriptama, prilagoditi ga trivijalnijim zadacima poput obrade HTTP zahtjeva i time se riješiti potrebe da se sa svakim zahtjevom sve učitava ispočetka?

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

Znali smo da možemo napisati web poslužitelj u čistom PHP-u (PHP-PM) ili koristeći C proširenje (Swoole). I iako svaka metoda ima svoje prednosti, obje opcije nam nisu odgovarale - htjeli smo nešto više. Trebali smo više od samog web poslužitelja - očekivali smo da ćemo dobiti rješenje koje bi nas moglo spasiti od problema povezanih s "teškim startom" u PHP-u, koje bi se u isto vrijeme moglo lako prilagoditi i proširiti za specifične aplikacije. Odnosno, trebao nam je aplikacijski poslužitelj.

Može li Go pomoći s ovim? Znali smo da može jer jezik kompajlira aplikacije u pojedinačne binarne datoteke; višeplatformski je; koristi vlastiti, vrlo elegantan, paralelni model obrade (concurrency) i biblioteku za rad s HTTP-om; i konačno, tisuće knjižnica i integracija otvorenog koda bit će nam dostupne.

Poteškoće kombiniranja dvaju programskih jezika

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

Na primjer, koristeći izvrsna knjižnica Alex Palaestras, bilo je moguće dijeliti memoriju između PHP i Go procesa (slično mod_php u Apacheu). Ali ova biblioteka ima značajke koje ograničavaju njezinu upotrebu za rješavanje našeg problema.

Odlučili smo upotrijebiti drugačiji, uobičajeniji pristup: izgraditi interakciju između procesa putem utičnica / cjevovoda. Ovaj pristup pokazao se pouzdanim tijekom proteklih desetljeća i dobro je optimiziran na razini operativnog sustava.

Za početak, stvorili smo jednostavan binarni protokol za razmjenu podataka između procesa i rukovanje pogreškama prijenosa. U svom najjednostavnijem obliku, ova vrsta protokola je slična netstring с zaglavlje paketa fiksne veličine (u našem slučaju 17 bajtova), koji sadrži informacije o vrsti paketa, njegovoj veličini i binarnoj maski za provjeru integriteta podataka.

Na PHP strani koju smo koristili funkcija pakiranja, a na Go strani knjižnica kodiranje/binarno.

Činilo nam se da jedan protokol nije dovoljan - i dodali smo mogućnost poziva net/rpc go usluge izravno iz PHP-a. Kasnije nam je to puno pomoglo u razvoju, jer smo mogli lako integrirati Go biblioteke u PHP aplikacije. Rezultat ovog rada može se vidjeti, primjerice, u našem drugom open-source proizvodu Gorige.

Distribucija zadataka na više PHP radnika

Nakon implementacije mehanizma interakcije, počeli smo razmišljati o najučinkovitijem načinu prijenosa zadataka u PHP procese. Kada stigne zadatak, aplikacijski poslužitelj mora odabrati slobodnog radnika koji će ga izvršiti. Ako radnik/proces izađe s greškom ili "umre", mi ga se rješavamo i stvaramo novi koji će ga zamijeniti. A ako je radnik/proces uspješno završen, vraćamo ga u skup radnika koji su dostupni za obavljanje zadataka.

RoadRunner: PHP nije napravljen da umre, ili Golang u pomoć

Za pohranjivanje bazena aktivnih radnika koristili smo se međuspremnik kanala, kako bismo uklonili neočekivano "mrtve" radnike iz skupa, dodali smo mehanizam za praćenje pogrešaka i stanja radnika.

Kao rezultat, dobili smo PHP poslužitelj koji radi i može obraditi sve zahtjeve predstavljene u binarnom obliku.

Kako bi naša aplikacija počela raditi kao web poslužitelj, morali smo odabrati pouzdani PHP standard za predstavljanje svih dolaznih HTTP zahtjeva. U našem slučaju, mi samo transformirati net/http zahtjev iz Idi na format PSR-7tako da je kompatibilan s većinom PHP okvira koji su danas dostupni.

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

RoadRunner: PHP nije napravljen da umre, ili Golang u pomoć

Predstavljamo RoadRunner - PHP aplikacijski poslužitelj visokih performansi

Naš prvi testni zadatak bio je API backend, koji povremeno pršti nepredvidivim zahtjevima (mnogo češće nego inače). Iako je nginx bio dovoljan u većini slučajeva, redovito smo nailazili na pogreške 502 jer nismo mogli dovoljno brzo balansirati sustav za očekivano povećanje opterećenja.

Kako bismo zamijenili ovo rješenje, postavili smo naš prvi PHP/Go aplikacijski poslužitelj početkom 2018. I odmah dobio nevjerojatan učinak! Ne samo da smo se u potpunosti riješili greške 502, već smo uspjeli smanjiti broj poslužitelja za dvije trećine, uštedivši puno novca i tableta za glavobolju za inženjere i voditelje proizvoda.

Do sredine godine unaprijedili smo naše rješenje, objavili ga na GitHubu pod licencom MIT-a i nazvali ga Roadrunner, ističući tako njegovu nevjerojatnu brzinu i učinkovitost.

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

Primjena Roadrunner omogućio nam je korištenje Middleware net/http na Go strani za izvođenje JWT provjere prije nego što zahtjev stigne do PHP-a, kao i upravljanje WebSocketima i agregatnim stanjem globalno u Prometheusu.

Zahvaljujući ugrađenom RPC-u, možete otvoriti API bilo koje Go biblioteke za PHP bez pisanja omotača proširenja. Što je još važnije, s RoadRunnerom možete postaviti nove ne-HTTP poslužitelje. Primjeri uključuju pokretanje rukovatelja u PHP-u AWS Lambda, stvaranje pouzdanih prekidača čekanja, 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 pogrešaka, implementirali integraciju sa Symfony okvirom i dodali podršku za HTTPS, HTTP/2, dodaci i PSR-17.

Zaključak

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

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

Nakon što smo radili s hrpom Goa i PHP-a, možemo reći da ih volimo. Ne planiramo žrtvovati jedno za drugo - naprotiv, tražit ćemo načine da dobijemo još veću vrijednost od ovog dual stacka.

UPD: pozdravljamo kreatora RoadRunnera i koautora originalnog članka - Lachesis

Izvor: www.habr.com

Dodajte komentar