RoadRunner: PHP není stavěno na to, aby zemřelo, nebo Golang na záchranu

RoadRunner: PHP není stavěno na to, aby zemřelo, nebo Golang na záchranu

Čau Habr! Jsme aktivní na Badoo pracovat na výkonu PHP, protože máme v tomto jazyce poměrně rozsáhlý systém a problém s výkonem je otázkou úspory peněz. Před více než deseti lety jsme pro to vytvořili PHP-FPM, což byla nejprve sada patchů pro PHP a později vstoupila do oficiální distribuce.

V posledních letech udělalo PHP velký pokrok: zlepšil se garbage collector, zvýšila se úroveň stability – dnes můžete v PHP bez problémů psát démony a skripty s dlouhou životností. To umožnilo Spiral Scout jít dále: RoadRunner, na rozdíl od PHP-FPM, nečistí paměť mezi požadavky, což poskytuje další zvýšení výkonu (ačkoli tento přístup komplikuje proces vývoje). V současné době s tímto nástrojem experimentujeme, ale zatím nemáme žádné výsledky, které bychom mohli sdílet. Aby bylo čekání na ně zábavnější, zveřejňujeme překlad oznámení RoadRunner od Spiral Scout.

Přístup z článku je nám blízký: při řešení našich problémů také nejčastěji používáme spoustu PHP a Go, získáváme výhody obou jazyků a neopouštíme jeden ve prospěch druhého.

Užijte si to!

V posledních deseti letech jsme ze seznamu vytvořili aplikace pro firmy Fortune 500a pro firmy s publikem maximálně 500 uživatelů. Celou tu dobu naši inženýři vyvíjeli backend hlavně v PHP. Ale před dvěma lety mělo něco velký dopad nejen na výkon našich produktů, ale také na jejich škálovatelnost – zavedli jsme Golang (Go) do našeho technologického zásobníku.

Téměř okamžitě jsme zjistili, že Go nám umožňuje vytvářet větší aplikace s až 40násobným zvýšením výkonu. Díky němu jsme byli schopni rozšířit stávající produkty napsané v PHP a vylepšit je kombinací výhod obou jazyků.

Prozradíme vám, jak kombinace Go a PHP pomáhá řešit skutečné vývojové problémy a jak se pro nás proměnila v nástroj, který vás dokáže zbavit některých problémů spojených s PHP model umírání.

Vaše každodenní vývojové prostředí PHP

Než budeme mluvit o tom, jak můžete použít Go k oživení modelu umírání PHP, podívejme se na vaše výchozí vývojové prostředí PHP.

Ve většině případů spouštíte svou aplikaci pomocí kombinace webového serveru nginx a serveru PHP-FPM. První z nich obsluhuje statické soubory a přesměrovává specifické požadavky na PHP-FPM, zatímco PHP-FPM sám spouští PHP kód. Možná používáte méně oblíbenou kombinaci Apache a mod_php. Ale ačkoli to funguje trochu jinak, principy jsou stejné.

Pojďme se podívat na to, jak PHP-FPM spouští aplikační kód. Když přijde požadavek, PHP-FPM inicializuje podřízený proces PHP a předá podrobnosti o požadavku jako součást jeho stavu (_GET, _POST, _SERVER atd.).

Stav se nemůže během provádění PHP skriptu změnit, takže existuje pouze jeden způsob, jak získat novou sadu vstupních dat: vymazáním paměti procesu a její reinicializací.

Tento model provedení má mnoho výhod. Se spotřebou paměti si nemusíte příliš lámat hlavu, všechny procesy jsou zcela izolované a pokud některý z nich „umře“, automaticky se znovu vytvoří a na zbytek procesů to nebude mít vliv. Tento přístup má ale také nevýhody, které se objevují při pokusu o škálování aplikace.

Nevýhody a neefektivity běžného prostředí PHP

Pokud jste profesionální vývojář PHP, pak víte, kde začít nový projekt - s výběrem frameworku. Skládá se z knihoven vkládání závislostí, ORM, překladů a šablon. A samozřejmě všechny uživatelské vstupy lze pohodlně vložit do jednoho objektu (Symfony/HttpFoundation nebo PSR-7). Frameworky jsou skvělé!

Ale všechno má svou cenu. V jakémkoli rámci na podnikové úrovni budete muset ke zpracování jednoduchého uživatelského požadavku nebo přístupu k databázi načíst alespoň desítky souborů, vytvořit četné třídy a analyzovat několik konfigurací. Nejhorší ale je, že po dokončení každého úkolu budete muset vše resetovat a začít znovu: veškerý kód, který jste právě iniciovali, se stane zbytečným, s jeho pomocí už nezpracujete další požadavek. Řekněte to kterémukoli programátorovi, který píše v jiném jazyce, a na jeho tváři uvidíte zmatek.

PHP inženýři už roky hledali způsoby, jak tento problém vyřešit, pomocí chytrých technik líného načítání, mikrorámců, optimalizovaných knihoven, mezipaměti atd. Ale nakonec stejně musíte celou aplikaci resetovat a začít znovu, znovu a znovu . (Pozn. překladatele: tento problém bude částečně vyřešen s příchodem předpětí v PHP 7.4)

Může PHP s Go přežít více než jeden požadavek?

Je možné psát PHP skripty, které žijí déle než několik minut (až hodiny nebo dny): například úlohy cron, analyzátory CSV, prolomení front. Všichni pracují podle stejného scénáře: načítají úkol, provedou ho a čekají na další. Kód je neustále v paměti, což šetří drahocenné milisekundy, protože k načtení frameworku a aplikace je potřeba mnoho dalších kroků.

Vyvinout trvanlivé skripty ale není snadné. Jakákoli chyba proces zcela zabije, diagnostika úniků paměti je k vzteku a ladění pomocí F5 již není možné.

Situace se zlepšila s vydáním PHP 7: objevil se spolehlivý garbage collector, bylo snazší zpracovávat chyby a rozšíření jádra jsou nyní odolná proti úniku. Je pravda, že inženýři stále musí být opatrní s pamětí a být si vědomi problémů se stavem v kódu (existuje jazyk, který dokáže tyto věci ignorovat?). Přesto má pro nás PHP 7 připraveno méně překvapení.

Je možné vzít model práce s dlouhotrvajícími PHP skripty, přizpůsobit jej triviálnějším úkolům, jako je zpracování HTTP požadavků, a zbavit se tak nutnosti načítat vše od začátku s každým požadavkem?

Abychom tento problém vyřešili, museli jsme nejprve implementovat serverovou aplikaci, která by mohla přijímat požadavky HTTP a přesměrovávat je jeden po druhém na pracovníka PHP, aniž by jej pokaždé zabil.

Věděli jsme, že můžeme napsat webový server v čistém PHP (PHP-PM) nebo pomocí rozšíření C (Swoole). A přestože každá metoda má své výhody, obě možnosti nám nevyhovovaly - chtěli jsme něco víc. Potřebovali jsme víc než jen webový server – očekávali jsme řešení, které nás ušetří problémů spojených s „tvrdým startem“ v PHP, které lze zároveň snadno přizpůsobit a rozšířit pro konkrétní aplikace. To znamená, že jsme potřebovali aplikační server.

Může s tím Go pomoci? Věděli jsme, že může, protože tento jazyk kompiluje aplikace do jednotlivých binárních souborů; je multiplatformní; používá vlastní, velmi elegantní, model paralelního zpracování (souběh) a knihovnu pro práci s HTTP; a konečně nám budou k dispozici tisíce open-source knihoven a integrací.

Potíže se spojením dvou programovacích jazyků

V první řadě bylo nutné určit, jak spolu budou dvě a více aplikací komunikovat.

Například pomocí výborná knihovna Alex Palaestras, bylo možné sdílet paměť mezi procesy PHP a Go (podobně jako mod_php v Apache). Ale tato knihovna má funkce, které omezují její použití pro řešení našeho problému.

Rozhodli jsme se použít jiný, běžnější přístup: vybudovat interakci mezi procesy prostřednictvím zásuvek / potrubí. Tento přístup se v posledních desetiletích ukázal jako spolehlivý a byl dobře optimalizován na úrovni operačního systému.

Pro začátek jsme vytvořili jednoduchý binární protokol pro výměnu dat mezi procesy a řešení chyb přenosu. Ve své nejjednodušší podobě je tento typ protokolu podobný síťový řetězec с hlavička paketu s pevnou velikostí (v našem případě 17 bajtů), který obsahuje informace o typu paketu, jeho velikosti a binární masku pro kontrolu integrity dat.

Na straně PHP jsme použili funkce balenía na straně Go knihovna kódování/binární.

Zdálo se nám, že jeden protokol nestačí – a přidali jsme možnost volat net/rpc go služby přímo z PHP. Později nám to hodně pomohlo ve vývoji, protože jsme mohli snadno integrovat knihovny Go do aplikací PHP. Výsledek této práce je vidět například na našem dalším open-source produktu Goridge.

Distribuce úkolů mezi více pracovníků PHP

Po implementaci interakčního mechanismu jsme začali přemýšlet o nejefektivnějším způsobu přenosu úkolů do procesů PHP. Když úloha dorazí, aplikační server si musí vybrat volného pracovníka, který ji provede. Pokud pracovník/proces skončí s chybou nebo „umře“, zbavíme se ho a vytvoříme nový, který jej nahradí. A pokud byl pracovník/proces úspěšně dokončen, vrátíme jej do skupiny pracovníků, kteří jsou k dispozici k provádění úkolů.

RoadRunner: PHP není stavěno na to, aby zemřelo, nebo Golang na záchranu

K uložení bazénu aktivních pracovníků jsme použili kanál s vyrovnávací pamětí, abychom z fondu odstranili neočekávaně „mrtvé“ pracovníky, přidali jsme mechanismus pro sledování chyb a stavů pracovníků.

Výsledkem je funkční PHP server schopný zpracovávat jakékoli požadavky prezentované v binární podobě.

Aby naše aplikace začala fungovat jako webový server, museli jsme zvolit spolehlivý PHP standard pro reprezentaci všech příchozích HTTP požadavků. V našem případě jen my přeměnit net/http požadavek z Přejít na formát PSR-7takže je kompatibilní s většinou dnes dostupných PHP frameworků.

Protože PSR-7 je považován za neměnný (někteří by řekli, že technicky není), vývojáři musí psát aplikace, které v zásadě nezacházejí s požadavkem jako s globální entitou. To krásně zapadá do konceptu dlouhodobých procesů PHP. Naše finální implementace, která ještě nebyla pojmenována, vypadala takto:

RoadRunner: PHP není stavěno na to, aby zemřelo, nebo Golang na záchranu

Představujeme RoadRunner - vysoce výkonný aplikační server PHP

Naším prvním testovacím úkolem byl backend API, který pravidelně generuje nepředvídatelné požadavky (mnohem častěji než obvykle). Přestože byl nginx ve většině případů dostačující, pravidelně jsme se setkávali s chybami 502, protože jsme nedokázali dostatečně rychle vyvážit systém pro očekávané zvýšení zátěže.

Jako náhradu tohoto řešení jsme na začátku roku 2018 nasadili náš první aplikační server PHP/Go. A okamžitě to mělo neuvěřitelný efekt! Nejen, že jsme úplně odstranili chybu 502, ale dokázali jsme snížit počet serverů o dvě třetiny, což inženýrům a produktovým manažerům ušetřilo spoustu peněz a prášků na bolest hlavy.

Do poloviny roku jsme naše řešení vylepšili, zveřejnili na GitHubu pod licencí MIT a pojmenovali RoadRunner, čímž zdůrazňuje jeho neuvěřitelnou rychlost a efektivitu.

Jak může RoadRunner zlepšit váš vývojový stack

přihláška RoadRunner nám umožnilo používat Middleware net/http na straně Go k provedení ověření JWT předtím, než požadavek dosáhne PHP, a také ke zpracování WebSockets a agregovaného stavu globálně v Prometheus.

Díky vestavěnému RPC můžete otevřít API všech knihoven Go pro PHP bez psaní obalů rozšíření. Ještě důležitější je, že s RoadRunner můžete nasadit nové servery bez HTTP. Příklady zahrnují spouštění handlerů v PHP AWS Lambda, vytváření spolehlivých jističů front a dokonce přidávání gRPC do našich aplikací.

S pomocí komunit PHP a Go jsme zlepšili stabilitu řešení, zvýšili výkon aplikací v některých testech až 40krát, zlepšili ladicí nástroje, implementovali integraci s frameworkem Symfony a přidali podporu pro HTTPS, HTTP/2, pluginy a PSR-17.

Závěr

Někteří lidé jsou stále chyceni v zastaralé představě PHP jako pomalého, nepraktického jazyka vhodného pouze pro psaní pluginů pro WordPress. Tito lidé by dokonce mohli říci, že PHP má takové omezení: když je aplikace dostatečně velká, musíte si vybrat „vyspělejší“ jazyk a přepsat kódovou základnu nashromážděnou za mnoho let.

Na to všechno chci odpovědět: zamyslete se znovu. Věříme, že omezení pro PHP nastavujete pouze vy. Můžete strávit celý život přecházením z jednoho jazyka do druhého, snažit se najít perfektní shodu pro vaše potřeby, nebo můžete začít uvažovat o jazycích jako o nástrojích. Předpokládané nedostatky jazyka jako PHP mohou být ve skutečnosti důvodem jeho úspěchu. A pokud to zkombinujete s dalším jazykem, jako je Go, vytvoříte mnohem výkonnější produkty, než kdybyste byli omezeni na používání jakéhokoli jednoho jazyka.

Po práci s řadou Go a PHP můžeme říci, že je milujeme. Nemáme v plánu obětovat jeden pro druhého – naopak budeme hledat cesty, jak z tohoto dual stacku získat ještě větší hodnotu.

UPD: vítáme tvůrce RoadRunner a spoluautora původního článku - Lachesis

Zdroj: www.habr.com

Přidat komentář