RoadRunner: PHP er ikke bygget til at dø, eller Golang til undsætning

RoadRunner: PHP er ikke bygget til at dø, eller Golang til undsætning

Hej Habr! Vi er aktive hos Badoo arbejder på PHP ydeevne, da vi har et ret stort system på dette sprog, og præstationsproblemet er et pengebesparende problem. For mere end ti år siden oprettede vi PHP-FPM til dette, som først var et sæt patches til PHP, og senere kom ind i den officielle distribution.

I de senere år har PHP gjort store fremskridt: skraldesamleren er blevet bedre, stabilitetsniveauet er steget – i dag kan du uden problemer skrive dæmoner og langlivede scripts i PHP. Dette gjorde det muligt for Spiral Scout at gå længere: RoadRunner, i modsætning til PHP-FPM, rydder ikke op i hukommelsen mellem anmodninger, hvilket giver en ekstra ydeevnegevinst (selvom denne tilgang komplicerer udviklingsprocessen). Vi eksperimenterer i øjeblikket med dette værktøj, men vi har endnu ingen resultater at dele. For at gøre det sjovere at vente på dem, vi udgiver oversættelsen af ​​RoadRunner-meddelelsen fra Spiral Scout.

Tilgangen fra artiklen er tæt på os: Når vi løser vores problemer, bruger vi også oftest en masse PHP og Go, for at få fordelene ved begge sprog og ikke opgive det ene til fordel for det andet.

God fornøjelse!

I de sidste ti år har vi lavet ansøgninger til virksomheder fra listen Fortune 500, og for virksomheder med et publikum på højst 500 brugere. Hele denne tid har vores ingeniører udviklet backend hovedsageligt i PHP. Men for to år siden havde noget stor indflydelse, ikke kun på ydeevnen af ​​vores produkter, men også på deres skalerbarhed – vi introducerede Golang (Go) i vores teknologistack.

Næsten øjeblikkeligt opdagede vi, at Go gav os mulighed for at bygge større applikationer med op til 40x ydeevneforbedringer. Med det var vi i stand til at udvide vores eksisterende PHP-produkter og forbedre dem ved at kombinere fordelene ved begge sprog.

Vi fortæller, hvordan kombinationen af ​​Go og PHP er med til at løse reelle udviklingsproblemer, og hvordan det er blevet til et værktøj for os, der kan slippe af med nogle af de problemer, der er forbundet med PHP døende model.

Dit daglige PHP-udviklingsmiljø

Før vi taler om, hvordan du kan bruge Go til at genoplive PHP-modellen, lad os tage et kig på dit standard PHP-udviklingsmiljø.

I de fleste tilfælde kører du din applikation ved hjælp af en kombination af nginx-webserveren og PHP-FPM-serveren. Førstnævnte serverer statiske filer og omdirigerer specifikke anmodninger til PHP-FPM, mens PHP-FPM selv udfører PHP-kode. Du bruger muligvis den mindre populære kombination af Apache og mod_php. Men selvom det fungerer lidt anderledes, er principperne de samme.

Lad os tage et kig på, hvordan PHP-FPM udfører applikationskode. Når en anmodning kommer ind, initialiserer PHP-FPM en PHP underordnet proces og sender detaljerne for anmodningen som en del af dens tilstand (_GET, _POST, _SERVER osv.).

Tilstanden kan ikke ændre sig under udførelsen af ​​et PHP-script, så den eneste måde at få et nyt sæt inputdata på er ved at rydde proceshukommelsen og initialisere den igen.

Denne udførelsesmodel har mange fordele. Du skal ikke bekymre dig for meget om hukommelsesforbrug, alle processer er fuldstændig isolerede, og hvis en af ​​dem "dør", bliver den automatisk genskabt, og det vil ikke påvirke resten af ​​processerne. Men denne tilgang har også ulemper, der viser sig, når man forsøger at skalere applikationen.

Ulemper og ineffektivitet ved et almindeligt PHP-miljø

Hvis du er en professionel PHP-udvikler, så ved du, hvor du skal starte et nyt projekt – med valget af et framework. Den består af afhængighedsinjektionsbiblioteker, ORM'er, oversættelser og skabeloner. Og selvfølgelig kan al brugerinput nemt sættes i ét objekt (Symfony/HttpFoundation eller PSR-7). Rammer er seje!

Men alt har sin pris. For at behandle en simpel brugeranmodning eller adgang til en database i enhver virksomhedsniveau skal du indlæse mindst snesevis af filer, oprette adskillige klasser og analysere flere konfigurationer. Men det værste er, at efter at have fuldført hver opgave, bliver du nødt til at nulstille alt og starte forfra: al den kode, du lige har startet, bliver ubrugelig, med dens hjælp vil du ikke længere behandle en anden anmodning. Fortæl dette til enhver programmør, der skriver på et andet sprog, og du vil se forvirring i hans ansigt.

PHP-ingeniører har ledt efter måder at løse dette problem på i årevis ved at bruge smarte lazy-loading-teknikker, microframeworks, optimerede biblioteker, cache osv. Men i sidste ende skal du stadig nulstille hele applikationen og starte forfra, igen og igen . (Oversætterens note: dette problem vil være delvist løst med fremkomsten af preload i PHP 7.4)

Kan PHP med Go overleve mere end én anmodning?

Det er muligt at skrive PHP-scripts, der lever længere end et par minutter (op til timer eller dage): for eksempel cron-opgaver, CSV-parsere, købrydere. De arbejder alle efter det samme scenarie: de henter en opgave, udfører den og venter på den næste. Koden ligger i hukommelsen hele tiden og sparer dyrebare millisekunder, da der er mange ekstra trin, der kræves for at indlæse rammen og applikationen.

Men det er ikke let at udvikle scripts med lang levetid. Enhver fejl dræber processen fuldstændigt, diagnosticering af hukommelseslækager er irriterende, og F5-fejlretning er ikke længere mulig.

Situationen er blevet bedre med udgivelsen af ​​PHP 7: en pålidelig skraldeopsamler er dukket op, det er blevet lettere at håndtere fejl, og kerneudvidelser er nu lækagesikre. Sandt nok skal ingeniører stadig være forsigtige med hukommelsen og være opmærksomme på tilstandsproblemer i kode (er der et sprog, der kan ignorere disse ting?). Alligevel har PHP 7 færre overraskelser i vente for os.

Er det muligt at tage modellen med at arbejde med langlivede PHP-scripts, tilpasse det til mere trivielle opgaver som at behandle HTTP-anmodninger og dermed slippe for behovet for at indlæse alt fra bunden med hver anmodning?

For at løse dette problem skulle vi først implementere en serverapplikation, der kunne acceptere HTTP-anmodninger og omdirigere dem én efter én til PHP-arbejderen uden at dræbe den hver gang.

Vi vidste, at vi kunne skrive en webserver i ren PHP (PHP-PM) eller bruge en C-udvidelse (Swoole). Og selvom hver metode har sine egne fordele, passede begge muligheder os ikke - vi ville have noget mere. Vi havde brug for mere end blot en webserver – vi forventede at få en løsning, der kunne redde os fra problemerne forbundet med en “hård start” i PHP, som samtidig nemt kunne tilpasses og udvides til specifikke applikationer. Det vil sige, vi havde brug for en applikationsserver.

Kan Go hjælpe med dette? Vi vidste, det kunne, fordi sproget kompilerer applikationer til enkelte binære filer; det er på tværs af platforme; bruger sin egen, meget elegante, parallelle behandlingsmodel (samtidig) og et bibliotek til at arbejde med HTTP; og endelig vil tusindvis af open source-biblioteker og integrationer være tilgængelige for os.

Vanskelighederne ved at kombinere to programmeringssprog

Først og fremmest var det nødvendigt at bestemme, hvordan to eller flere applikationer vil kommunikere med hinanden.

For eksempel ved at bruge fremragende bibliotek Alex Palaestras, var det muligt at dele hukommelse mellem PHP- og Go-processer (svarende til mod_php i Apache). Men dette bibliotek har funktioner, der begrænser dets brug til at løse vores problem.

Vi besluttede at bruge en anden, mere almindelig tilgang: at opbygge interaktion mellem processer gennem sockets / pipelines. Denne tilgang har vist sig at være pålidelig i løbet af de sidste årtier og er blevet godt optimeret på operativsystemniveau.

Til at begynde med skabte vi en simpel binær protokol til udveksling af data mellem processer og håndtering af transmissionsfejl. I sin enkleste form ligner denne type protokol netstreng с pakkehoved i fast størrelse (i vores tilfælde 17 bytes), som indeholder information om typen af ​​pakke, dens størrelse og en binær maske for at kontrollere integriteten af ​​dataene.

På PHP-siden brugte vi pakkefunktion, og på Go-siden, biblioteket kodning/binær.

Det forekom os, at én protokol ikke var nok – og vi tilføjede muligheden for at ringe net/rpc go tjenester direkte fra PHP. Senere hjalp dette os meget i udviklingen, da vi nemt kunne integrere Go-biblioteker i PHP-applikationer. Resultatet af dette arbejde kan for eksempel ses i vores andet open-source produkt Goridge.

Fordeling af opgaver på tværs af flere PHP-arbejdere

Efter implementering af interaktionsmekanismen begyndte vi at tænke på den mest effektive måde at overføre opgaver til PHP-processer på. Når en opgave ankommer, skal applikationsserveren vælge en ledig arbejder til at udføre den. Hvis en arbejder/proces afsluttes med en fejl eller "dør", slipper vi af med den og opretter en ny til at erstatte den. Og hvis arbejderen/processen er gennemført med succes, returnerer vi den til puljen af ​​arbejdere, der er tilgængelige til at udføre opgaver.

RoadRunner: PHP er ikke bygget til at dø, eller Golang til undsætning

Til at opbevare puljen af ​​aktive arbejdere brugte vi bufferet kanal, for at fjerne uventet "døde" arbejdere fra puljen, tilføjede vi en mekanisme til sporing af fejl og arbejdernes tilstand.

Som et resultat fik vi en fungerende PHP-server, der er i stand til at behandle alle anmodninger præsenteret i binær form.

For at vores applikation kunne begynde at fungere som en webserver, var vi nødt til at vælge en pålidelig PHP-standard til at repræsentere alle indkommende HTTP-anmodninger. I vores tilfælde er vi bare transformere net/http-anmodning fra Gå til format PSR-7så det er kompatibelt med de fleste PHP-rammer, der er tilgængelige i dag.

Fordi PSR-7 anses for at være uforanderlig (nogle vil sige, at den teknisk set ikke er det), skal udviklere skrive applikationer, der i princippet ikke behandler anmodningen som en global enhed. Dette passer fint med konceptet med langlivede PHP-processer. Vores endelige implementering, som endnu ikke er navngivet, så således ud:

RoadRunner: PHP er ikke bygget til at dø, eller Golang til undsætning

Introduktion til RoadRunner - højtydende PHP-applikationsserver

Vores første testopgave var en API-backend, som periodisk brister uforudsigeligt (meget oftere end normalt). Selvom nginx var tilstrækkeligt i de fleste tilfælde, stødte vi regelmæssigt på 502 fejl, fordi vi ikke kunne balancere systemet hurtigt nok til den forventede stigning i belastningen.

For at erstatte denne løsning implementerede vi vores første PHP/Go-applikationsserver i begyndelsen af ​​2018. Og fik straks en utrolig effekt! Ikke alene slap vi fuldstændigt af med 502-fejlen, men vi var også i stand til at reducere antallet af servere med to tredjedele, hvilket sparede mange penge og hovedpinepiller for ingeniører og produktchefer.

I midten af ​​året havde vi forbedret vores løsning, offentliggjort den på GitHub under MIT-licensen og givet den navnet RoadRunner, hvilket understreger dens utrolige hastighed og effektivitet.

Hvordan RoadRunner kan forbedre din udviklingsstack

Ansøgning RoadRunner tilladt os at bruge Middleware net/http på Go-siden til at udføre JWT-verifikation, før anmodningen når PHP, samt håndtere WebSockets og aggregeret tilstand globalt i Prometheus.

Takket være den indbyggede RPC kan du åbne API'en for alle Go-biblioteker til PHP uden at skrive udvidelsespakker. Endnu vigtigere, med RoadRunner kan du implementere nye ikke-HTTP-servere. Eksempler inkluderer kørende handlere i PHP AWS Lambda, skabe pålidelige købrydere og endda tilføje gRPC til vores applikationer.

Ved hjælp af PHP- og Go-fællesskaberne forbedrede vi stabiliteten af ​​løsningen, øgede applikationsydelsen op til 40 gange i nogle tests, forbedrede fejlfindingsværktøjer, implementerede integration med Symfony-rammeværket og tilføjede understøttelse af HTTPS, HTTP/2, plugins og PSR-17.

Konklusion

Nogle mennesker er stadig fanget i den forældede opfattelse af PHP som et langsomt, uhåndterligt sprog, der kun er godt til at skrive plugins til WordPress. Disse mennesker vil måske endda sige, at PHP har en sådan begrænsning: Når applikationen bliver stor nok, skal du vælge et mere "modent" sprog og omskrive den kodebase, der er akkumuleret over mange år.

Til alt dette vil jeg svare: tænk om igen. Vi mener, at det kun er dig, der sætter nogen begrænsninger for PHP. Du kan bruge hele dit liv på at skifte fra et sprog til et andet, forsøge at finde det perfekte match til dine behov, eller du kan begynde at tænke på sprog som værktøjer. De formodede fejl ved et sprog som PHP kan faktisk være årsagen til dets succes. Og hvis du kombinerer det med et andet sprog som Go, så vil du skabe meget mere kraftfulde produkter, end hvis du var begrænset til at bruge et hvilket som helst sprog.

Efter at have arbejdet med en masse Go og PHP, kan vi sige, at vi elsker dem. Vi planlægger ikke at ofre det ene for det andet – tværtimod vil vi lede efter måder at få endnu mere værdi ud af denne dobbelte stack.

UPD: vi byder skaberen af ​​RoadRunner og medforfatteren til den originale artikel velkommen - Lachesis

Kilde: www.habr.com

Tilføj en kommentar