
Hej, Habr! Vi pÄ Badoo arbetar aktivt , eftersom vi har ett ganska stort system pÄ detta sprÄk och prestandafrÄgan handlar om kostnadsbesparingar. För mer Àn tio Är sedan skapade vi PHP-FPM för detta, vilket först var en uppsÀttning patchar för PHP, och senare blev en del av den officiella distributionen.
Under de senaste Ă„ren har PHP gjort stort framsteg: skrĂ€psamlaren har förbĂ€ttrats, stabiliteten har ökat â idag gĂ„r det utan större problem att skriva demoner och lĂ„nglivade skript i PHP. Detta har lett Spiral Scout till att ta ytterligare steg: RoadRunner, till skillnad frĂ„n PHP-FPM, frigör inte minnet mellan förfrĂ„gningar, vilket ger en ytterligare prestandaökning (Ă€ven om detta tillvĂ€gagĂ„ngssĂ€tt komplicerar utvecklingsprocessen). Vi experimenterar för nĂ€rvarande med detta verktyg, men vi har Ă€nnu inga resultat att dela med oss av. För att göra vĂ€ntan mer underhĂ„llande, publicerar vi en översĂ€ttning av RoadRunner-meddelandet frĂ„n Spiral Scout.
TillvÀgagÄngssÀttet i artikeln ligger oss nÀra: nÀr vi löser vÄra problem anvÀnder vi ocksÄ oftast kombinationen av PHP och Go, dÀr vi drar nytta av fördelarna med bÄda sprÄken utan att avstÄ frÄn det ena till förmÄn för det andra.
Njut!
Under de senaste tio Ă„ren har vi skapat applikationer bĂ„de för företag pĂ„ listan , sĂ„vĂ€l som för verksamheter med en publik pĂ„ högst 500 anvĂ€ndare. Under hela denna tid har vĂ„ra ingenjörer huvudsakligen utvecklat backend i PHP. Men för tvĂ„ Ă„r sedan pĂ„verkade nĂ„got starkt inte bara prestandan hos vĂ„ra produkter, utan Ă€ven deras skalbarhet â vi införde Golang (Go) i vĂ„r teknologistack.
NÀstan omedelbart upptÀckte vi att Go lÄter oss skapa större applikationer med en prestandaförbÀttring pÄ upp till 40 gÄnger. Med hjÀlp av det kunde vi utöka befintliga produkter skrivna i PHP, vilket förbÀttrade dem genom att kombinera fördelarna med bÄda sprÄken.
Vi kommer att berÀtta hur kombinationen av Go och PHP hjÀlper till att lösa verkliga utvecklingsproblem och hur den har blivit ett verktyg som kan frigöra oss frÄn vissa problem relaterade till .
Din dagliga PHP-utvecklingsmiljö
Innan vi berÀttar hur Go kan Äteruppliva PHP:s "döende"-modell, lÄt oss titta pÄ din standard PHP-utvecklingsmiljö.
I de flesta fall startar du applikationen med en kombination av webbservern nginx och PHP-FPM-servern. Den första hanterar statiska filer och vidarebefordrar specifika förfrÄgningar till PHP-FPM, som i sin tur utför PHP-koden. Kanske anvÀnder du en mindre populÀr kombination av Apache och mod_php. Men Àven om den fungerar lite annorlunda, Àr principerna desamma.
LÄt oss titta pÄ hur PHP-FPM utför applikationskoden. NÀr en förfrÄgan kommer, initialiserar PHP-FPM en underordnad PHP-process och överför förfrÄgningens detaljer som en del av dess tillstÄnd (_GET, _POST, _SERVER osv.).
TillstÄndet kan inte Àndras under körningen av PHP-skriptet, sÄ för att fÄ en ny uppsÀttning ingÄngsdata finns det bara ett sÀtt: att rengöra processens minne och initialisera den pÄ nytt.
Denna exekveringsmodell har mÄnga fördelar. Du behöver inte oroa dig mycket för minnesanvÀndning, alla processer Àr helt isolerade, och om en av dem "dör", kommer den automatiskt att Äterskapas utan att pÄverka de andra processerna. Men denna metod har ocksÄ nackdelar som visar sig nÀr man försöker skala applikationen.
Nackdelar och ineffektivitet med en vanlig PHP-miljö
Om du arbetar med professionell PHP-utveckling vet du att du ska börja ett nytt projekt med att vÀlja ett ramverk. Det bestÄr av bibliotek för beroendeinjektion, ORM:er, översÀttningar och mallar. Och sjÀlvklart kan alla anvÀndardata enkelt samlas i ett objekt (Symfony/HttpFoundation eller PSR-7). Ramverk Àr fantastiska!
Men allt har sitt pris. I vilket företagsramverk som helst mÄste du ladda minst tiotals filer, skapa mÄnga klasser och analysera flera konfigurationer för att hantera en enkel anvÀndarförfrÄgan eller databasanslutning. Men det vÀrsta Àr att efter att varje uppgift har utförts mÄste allt rensas och börja om: all kod du just har initierat blir vÀrdelös, och med den kan du inte bearbeta ytterligare en förfrÄgan. BerÀtta detta för en programmerare som skriver i ett annat sprÄk, sÄ kommer du se förundran i deras ansikte.
PHP-ingenjörer har i Ă„ratal letat efter sĂ€tt att lösa detta problem, anvĂ€nt genomtĂ€nkta metoder för "lazy" inlĂ€sning, mikroramverk, optimerade bibliotek, cache, osv. Men i slutĂ€ndan mĂ„ste man Ă€ndĂ„ Ă„terstĂ€lla hela applikationen och börja om, gĂ„ng pĂ„ gĂ„ng. (ĂversĂ€ttarens anmĂ€rkning: delvis kommer detta problem att lösas med införandet av PHP 7.4)
Kan PHP med hjÀlp av Go hantera mer Àn en begÀran?
Det gÄr att skriva PHP-skript som lever lÀngre Àn nÄgra minuter (upp till timmar eller dagar): exempelvis cron-jobb, CSV-parsare, köhanterare. Alla arbetar enligt samma schema: de plockar upp en uppgift, utför den, vÀntar pÄ nÀsta. Koden finns stÀndigt i minnet, vilket sparar dyrbar millisekund, eftersom det krÀvs mÄnga ytterligare ÄtgÀrder för att ladda ramverket och applikationen.
Men att utveckla lÄnglivade skript Àr inte sÄ enkelt. Ett enda fel kan helt döda processen, att diagnostisera minneslÀckor Àr frustrerande, och det gÄr inte lÀngre att anvÀnda F5 för felsökning.
Situationen har förbÀttrats med PHP 7: en pÄlitlig skrÀpsamlar installerades, det blev lÀttare att hantera fel, och kÀrntillÀgg Àr nu skyddade mot lÀckor. Visst mÄste ingenjörerna fortfarande hantera minnet varsamt och tÀnka pÄ kodens tillstÄndsproblem (finns det nÄgot sprÄk dÀr man kan bortse frÄn sÄdana saker?). Trots det finns det fÀrre ovÀntade överraskningar med PHP 7.
Kan man ta modellen för arbete med lÄnglivade PHP-skript, anpassa den för mer triviala uppgifter som att hantera HTTP-begÀrningar, och pÄ sÄ sÀtt undkomma behovet att ladda allt frÄn början vid varje begÀran?
För att lösa detta problem behövde man först implementera en serverapplikation som kunde ta emot HTTP-begÀrningar och dirigera dem en i taget till en PHP-arbetare, utan att döda den varje gÄng.
Vi visste att vi skulle kunna skriva en webbserver i ren PHP (PHP-PM) eller med hjĂ€lp av C-tillĂ€gg (Swoole). Och Ă€ven om varje metod har sina fördelar, var ingen av dem tillrĂ€cklig för oss â vi ville ha nĂ„got mer. Vi behövde inte bara en webbserver â vi förvĂ€ntade oss en lösning som skulle befria oss frĂ„n problemen med "tung start" i PHP, och som dessutom skulle kunna anpassas och utökas lĂ€tt för specifika applikationer. Det vill sĂ€ga, vi behövde en applikationsserver.
Kan Go hjÀlpa till med detta? Vi visste att den kunde, eftersom sprÄket kompilerar applikationer till enstaka binÀrfiler; det Àr plattformsoberoende; det anvÀnder en egen, mycket elegant modell för samtidighet (concurrency) och ett bibliotek för att hantera HTTP; och slutligen skulle vi fÄ tillgÄng till tusentals open-source-bibliotek och integrationer.
SvÄrigheterna med att sammanföra tvÄ programmeringssprÄk
Först och frÀmst var det nödvÀndigt att definiera hur tvÄ eller flera applikationer skulle kommunicera med varandra.
Till exempel genom av Alex Palastras, som kunde göra det möjligt för PHP- och Go-processer att dela minne (pÄ liknande sÀtt som mod_php i Apache). Men detta bibliotek har funktioner som begrÀnsar dess tillÀmpning för att lösa vÄrt problem.
Vi beslöt att anvÀnda en annan, mer utbredd metod: att bygga interaktionen mellan processerna genom sockets/pipelines. Denna metod har under de senaste decennierna visat sig vara pÄlitlig och har optimerats vÀl pÄ operativsystemsnivÄ.
Till att börja med skapade vi ett enkelt binÀrt protokoll för datakommunikation mellan processer och hantering av överföringsfel. I sin enklaste form pÄminner ett protokoll av denna typ om med (i vÄrt fall 17 byte), vilket innehÄller information om paketets typ, dess storlek och en binÀr mask för att kontrollera integriteten i data.
PÄ PHP-sidan anvÀnde vi , medan vi pÄ Go-sidan anvÀnde biblioteket.
Det verkade som om vi behövde mer Ă€n ett protokoll â sĂ„ vi lade till möjligheten att anropa. Senare hjĂ€lpte detta oss mycket i utvecklingen, eftersom vi enkelt kunde integrera Go-bibliotek i PHP-applikationer. Resultatet av detta arbete kan ses, till exempel, i vĂ„r andra open-source-produkt.
Uppdelning av uppgifter mellan flera PHP-arbetare
Efter implementeringen av interaktionsmekanismen började vi tÀnka pÄ hur vi effektivast kunde överföra uppgifter till PHP-processerna. NÀr en uppgift anlÀnder, mÄste applikationsservern vÀlja en ledig arbetare för att utföra den. Om arbetaren/processen avslutade arbetet med ett fel eller "dog", tar vi bort den och skapar en ny i dess stÀlle. Och om arbetaren/processen körs framgÄngsrikt, ÄterlÀmnar vi den till poolen av arbetare som Àr tillgÀngliga för att utföra uppgifter.

För att lagra poolen av aktiva arbetare anvÀnde vi, för att ta bort ovÀntat "döda" arbetare frÄn poolen lade vi till en mekanism för att spÄra fel och arbetarnas tillstÄnd.
Som ett resultat fick vi en fungerande PHP-server som kan hantera alla förfrÄgningar som presenteras i binÀrt format.
För att vÄr applikation skulle börja fungera som en webbserver, var vi tvungna att vÀlja en pÄlitlig PHP-standard för att representera alla inkommande HTTP-förfrÄgningar. I vÄrt fall net/http-förfrÄgan frÄn Go till formatet, sÄ att den Àr kompatibel med de flesta tillgÀngliga PHP-ramverken idag.
Eftersom PSR-7 anses vara oförÀnderlig (nÄgon skulle sÀga att det tekniskt sett inte Àr sÄ), mÄste utvecklare skriva applikationer som i princip inte hanterar förfrÄgan som en global entitet. Detta passar perfekt med konceptet av lÄnglivade PHP-processer. VÄr slutliga implementering, som Ànnu inte fÄtt nÄgot namn, sÄg ut sÄ hÀr:

Vi presenterar RoadRunner â
VĂ„r första testuppgift var en API-backend, dĂ€r det periodiskt oförutsĂ€gbart uppstod toppar av förfrĂ„gningar (mycket oftare Ă€n vanligt). Ăven om nginx:s kapacitet i de flesta fall var tillrĂ€cklig, stötte vi regelbundet pĂ„ fel 502, eftersom vi inte kunde balansera systemet tillrĂ€ckligt snabbt för att möta den förvĂ€ntade ökningen av belastningen.
För att ersÀtta denna lösning implementerade vi i början av 2018 vÄr första PHP/Go-applikationsserver. Och vi fick omedelbart en otrolig effekt! Vi inte bara helt eliminerade fel 502, utan kunde ocksÄ minska antalet servrar med tvÄ tredjedelar, och sparade massor av pengar och huvudvÀrk för ingenjörer och produktchefer.
I mitten av Äret förbÀttrade vi vÄr lösning, publicerade den pÄ GitHub under MIT-licensen och döpte den till , vilket framhÀver dess otroliga hastighet och effektivitet.
Hur RoadRunner kan förbÀttra din utvecklingsstack
TillÀmpning gjorde det möjligt för oss att anvÀnda Middleware net/http pÄ Go-sidan för att genomföra JWT-verifiering innan begÀran ens nÄr PHP, samt för att hantera WebSockets och globalt aggregera tillstÄnd i Prometheus.
Tack vare den inbyggda RPC kan man öppna API:er frÄn valfria Go-bibliotek för PHP utan att skriva runtillÀgg. Vad som Àr Ànnu viktigare Àr att med RoadRunner kan man distribuera nya servrar som skiljer sig frÄn HTTP. Exempelvis kan man köra PHP-hanterare,skapa pÄlitliga köparsers och till och med lÀgga till i vÄra applikationer.
Med hjÀlp av PHP- och Go-gemenskaperna har vi förbÀttrat lösningens stabilitet, ökat prestandan i vissa tester upp till 40 gÄnger, förbÀttrat debuggningsverktygen, implementerat integration med ramverket Symfony och lagt till stöd för HTTPS, HTTP/2, plugins och PSR-17.
Slutsats
Vissa Àr fortfarande fast i den förÄldrade uppfattningen om PHP som ett lÄngsamt och skrymmande sprÄk, som bara Àr anvÀndbart för att skriva plugins till WordPress. Dessa personer kan till och med hÀvda att PHP har en sÄdan begrÀnsning: nÀr en applikation blir tillrÀckligt stor, mÄste man vÀlja ett mer "mognat" sprÄk och skriva om den kodbas som byggts upp under mÄnga Är.
AngÄende detta vill vi sÀga: tÀnk en gÄng till. Vi tror att det Àr ni sjÀlva som sÀtter begrÀnsningar för PHP. Ni kan spendera hela livet pÄ att byta mellan sprÄk i jakt pÄ den perfekta lösningen för era behov, eller sÄ kan ni börja se sprÄk som verktyg. Illusionen av sprÄkets brister som PHP kan faktiskt vara anledningen till dess framgÄng. Och om ni kombinerar det med ett annat sprÄk som Go, kommer ni att skapa betydligt kraftfullare produkter Àn om ni begrÀnsar er till att anvÀnda ett enda sprÄk.
Efter att ha arbetat med kombinationen Go och PHP kan vi sÀga att vi Àlskar dem. Vi planerar inte att offra det ena för det andra - tvÀrtom kommer vi att söka sÀtt att fÄ Ànnu mer nytta av denna dubbelstack.
UPD: vĂ€lkomna skaparen av RoadRunner och medförfattaren till den ursprungliga artikel â
KĂ€lla: habr.com
