
Ahoj, Habr! V Badoo aktivně , protože máme dost velký systém na tomto jazyce a otázka výkonnosti je otázkou úspory peněz. Před více než deseti lety jsme vytvořili PHP-FPM, které zpočátku představovalo sadu patchů pro PHP, a později se stalo součástí oficiální distribuce.
Za poslední roky PHP značně pokročilo: vylepšil se garbage collector, zvýšila se úroveň stability — dnes na PHP lze bez větších problémů psát démony a dlouho běžící skripty. To umožnilo Spiral Scout jít dále: RoadRunner, na rozdíl od PHP-FPM, nečiští paměť mezi požadavky, což přináší další zisk ve výkonnosti (i když tento přístup komplikuje proces vývoje). V současnosti experimentujeme s tímto nástrojem, ale zatím nemáme výsledky, kterými bychom se mohli podělit. Aby čekání na ně bylo zábavnější, publikujeme překlad oznámení RoadRunner od Spiral Scout.
Přístup popsaný v článku nám je blízký: při řešení našich úkolů také častěji používáme kombinaci PHP a Go, což nám přináší výhody obou jazyků, aniž bychom se museli vzdát jednoho na úkor druhého.
Užívejte si!
V posledních deseti letech jsme vytvářeli aplikace jak pro společnosti ze seznamu , tak pro podniky s publikem ne více než 500 uživatelů. Po celou tuto dobu naši inženýři převážně vyvíjeli backend převážně v PHP. Ale před dvěma lety něco silně ovlivnilo nejen výkon našich produktů, ale také jejich škálovatelnost — zavedli jsme Golang (Go) do našeho technologického stacku.
Téměř okamžitě jsme zjistili, že Go nám umožňuje vytvářet větší aplikace se zvýšením výkonu až 40krát. S jeho pomocí jsme mohli rozšiřovat existující produkty napsané v PHP, zlepšující je kombinací výhod obou jazyků.
Povíme vám, jak kombinace Go a PHP pomáhá řešit skutečné vývojové úkoly a jak se pro nás stala nástrojem, který nám může ulehčit část problémů spojených s .
Vaše každodenní prostředí pro PHP vývojáře
Než začneme mluvit o tom, jak pomocí Go oživit model „umírání“ PHP, podívejme se na vaši standardní PHP vývojářskou prostředí.
Většinu případů spouštíte aplikaci pomocí kombinace webového serveru nginx a PHP-FPM. První zpracovává statické soubory a přesměrovává specifické dotazy na PHP-FPM, zatímco PHP-FPM vykonává PHP kód. Možná používáte méně populární kombinaci Apache a mod_php. Ale i když to funguje trochu jinak, principy zůstávají stejné.
Podívejme se, jak PHP-FPM vykonává kód aplikace. Když přijde žádost, PHP-FPM inicializuje podřízený PHP proces a detaily žádosti předává jako součást jeho stavu (_GET, _POST, _SERVER atd.).
Stav se během vykonávání PHP skriptu nemůže měnit, a proto lze získat novou sadu vstupních dat pouze jedním způsobem: vyčištěním paměti procesu a jeho novou inicializací.
Tento model vykonávání má mnoho výhod. Nemusíte se příliš obávat spotřeby paměti, všechny procesy jsou zcela izolovány, a pokud jeden z nich "umře", bude automaticky znovu vytvořen, což nijak neovlivní ostatní procesy. Tento přístup má však také nevýhody, které se projevují při pokusu o škálování aplikace.
Nevýhody a neefektivnost běžného PHP prostředí
Pokud se zabýváte profesionálním vývojem v PHP, víte, čím začít nový projekt - výběrem frameworku. Ten představuje knihovny pro vkládání závislostí, ORM, překlady a šablony. A samozřejmě, všechny vstupní uživatelské údaje můžete pohodlně umístit do jednoho objektu (Symfony/HttpFoundation nebo PSR-7). Frameworky jsou skvělé!
Ale všechno má svou cenu. V jakémkoli frameworku podnikové úrovně pro zpracování jednoduchého uživatelského dotazu nebo dotazu k databázi musíte načíst minimálně desítky souborů, vytvořit řadu tříd a zpracovat několik konfigurací. Ale nejhorší na tom je, že po vykonání každého úkolu musíte vše vyresetovat a začít znovu: veškerý kód, který jste právě iniciovali, se stává bezcenným, protože už s ním nebudete schopni zpracovat další žádost. Řekněte to jakémukoli programátorovi, který píše v jiném jazyce, a uvidíte na jeho tváři zmatení.
PHP inženýři léta hledali způsoby, jak tento problém vyřešit, používali promyšlené techniky "lenivé" načítání, mikroframerky, optimalizované knihovny, cache atd. Ale nakonec je stejně třeba všechno aplikaci resetovat a začít znovu, znova a znova. (Poznámka překladatele: částečně tento problém vyřeší vydání PHP 7.4)
Může PHP s pomocí Go zvládnout více než jeden požadavek?
Jsou možné PHP skripty, které přežijí déle než několik minut (dokonce hodiny nebo dny): například cron úlohy, CSV parsery, zpracovatele front. Všechny fungují na stejném principu: vytáhnou úkol, provedou ho, čekají na další. Kód neustále zůstává v paměti, což šetří cenné milisekundy, protože načítání frameworku a aplikace vyžaduje provádět spoustu dalších postupů.
Ale vývoj dlouhověkých skriptů není tak jednoduchý. Jakákoliv chyba zcela zabije proces, diagnostika úniků paměti přivádí k šílenství a debugging s F5 už není možné.
Situace se zlepšila s příchodem PHP 7: objevil se spolehlivý garbage collector, zpracování chyb je snazší a rozšíření jádra jsou nyní chráněna před úniky. Ale inženýři stále musí opatrně zacházet s pamětí a mít na paměti problémy se stavem v kódu (existuje vůbec jazyk, kde se těmto věcem nemusí věnovat?). A přesto je v PHP 7 méně překvapení.
Lze vzít model práce s dlouhověkými PHP skripty, přizpůsobit jej pro banalnější úkoly, jako je zpracování HTTP požadavků a tím se zbavit potřeby načítat všechno znovu při každém požadavku?
Abychom tento úkol vyřešili, bylo nejprve třeba implementovat serverovou aplikaci, schopnou přijímat HTTP požadavky a postupně je přesměrovávat na PHP pracovníka, aniž bychom jej pokaždé zabili.
Věděli jsme, že dokážeme napsat webový server v čistém PHP (PHP-PM) nebo s využitím C-rozšíření (Swoole). Ačkoliv má každý způsob své výhody, obě varianty nás neuspokojily — toužili jsme po něčem víc. Potřebovali jsme nejen webový server — plánovali jsme získat řešení, které by nás zbavilo problémů spojených s "těžkým startem" v PHP, a které by se dalo snadno přizpůsobit a rozšířit pro konkrétní aplikace. Jinými slovy, potřebovali jsme aplikační server.
Může v tom pomoci Go? Věděli jsme, že ano, 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í (concurrency) a knihovnu pro práci s HTTP; a konečně, budou nám k dispozici tisíce open-source knihoven a integrací.
Obtíže při spojování dvou programovacích jazyků
Nejprve bylo nutné určit, jak spolu budou komunikovat dvě a více aplikací.
Například pomocí Alexe Palästra se dalo realizovat sdílení paměti mezi procesy PHP a Go (analogicky jako mod_php v Apache). Ale tato knihovna má zvláštnosti, které omezují její použití k vyřešení našeho úkolu.
Rozhodli jsme se použít jiný, rozšířenější přístup: vybudovat interakci mezi procesy prostřednictvím soketů/pipeliny. Tento přístup za poslední desetiletí prokázal svou spolehlivost a byl dobře optimalizován na úrovni operačního systému.
Nejprve jsme vytvořili jednoduchý binární protokol pro výměnu dat mezi procesy a zpracování chyb při přenosu. V nejjednodušší podobě je protokol tohoto typu podobný s (v našem případě 17 bytů), která obsahuje informace o typu paketu, jeho velikosti a binární masku pro kontrolu integrity dat.
Na straně PHP jsme použili , a na straně Go knihovnu.
Jednoho protokolu se nám zdálo málo — a přidali jsme možnost volat. Později nám to velmi pomohlo ve vývoji, protože jsme mohli snadno integrovat Go knihovny do PHP aplikací. Výsledek této práce lze vidět například v našem dalším open-source produktu.
Rozdělení úkolů mezi několika PHP pracovníky
Po implementaci mechanismu interakce jsme začali přemýšlet o tom, jak efektivněji předávat úkoly PHP procesům. Když přijde úkol, aplikační server musí vybrat volného pracovníka, který jej vykoná. Pokud pracovník/proces skončil s chybou nebo "zemřel", zbavíme se ho a vytvoříme nového. A pokud pracovník/proces úspěšně dokončil práci, vrátíme ho zpět do bazénu dostupných pracovníků pro vykonání úkolů.

Pro ukládání bazénu aktivních pracovníků jsme použili, k odstranění neočekávaně "zemřelých" pracovníků jsme přidali mechanismus sledování chyb a stavů pracovníků.
V důsledku toho máme funkční PHP server, který je schopen zpracovávat jakékoli dotazy, které jsou předloženy binárně.
Aby naše aplikace začala fungovat jako webový server, museli jsme vybrat spolehlivý PHP standard pro reprezentaci jakýchkoli příchozích HTTP dotazů. V našem případě jednoduše net/http dotaz z Go do formátu, aby byl kompatibilní s většinou dostupných dnešních PHP rámců.
Jelikož je PSR-7 považován za neměnný (někdo by mohl říci, že to technicky není pravda), musejí vývojáři psát aplikace, které v zásadě nenakládají s dotazem jako s globální entitou. To se skvěle spojuje s konceptem dlouhotrvajících PHP procesů. Naše konečné provedení, které ještě nemá název, vypadalo takto:

Představujeme RoadRunner —
Naším prvním testovacím úkolem byl API backend, na kterém se periodicky nepredikovatelné vyskytovaly výkyvy dotazů (mnohem častěji než obvykle). I když většinou možnosti nginx stačily, pravidelně jsme se setkávali s chybou 502, protože jsme nemohli dostatečně rychle vyvážit systém pod očekávaným nárůstem zátěže.
Abychom nahradili toto řešení, na začátku roku 2018 jsme nasadili náš první PHP/Go server aplikací. A hned jsme získali úžasný efekt! Nejenže jsme se zcela zbavili chyby 502, ale také jsme mohli snížit počet serverů o dvě třetiny, čímž jsme ušetřili spoustu peněz a léků na bolesti hlavy pro inženýry a produktové manažery.
V polovině roku jsme vylepšili naše řešení, zveřejnili ho na GitHubu pod licencí MIT a pojmenovali , což podtrhuje jeho neuvěřitelnou rychlost a efektivitu.
Jak může RoadRunner zlepšit váš vývojový stack
Použití nám umožnilo používat middleware net/http na straně Go k provádění JWT ověření ještě před tím, než se požadavek dostane do PHP, a také pro zpracování WebSockets a globální agregaci stavů do Promethea.
Díky vestavěnému RPC lze otevírat API jakýchkoli Go knihoven pro PHP bez psaní rozšiřujících wrapperů. Co je ještě důležitější, s RoadRunnerem lze nasazovat nové servery, které se liší od HTTP. Jako příklad lze uvést spuštění PHP zpracovatelů, vytváření spolehlivých zpracovatelů front a dokonce i přidávání do našich aplikací.
S pomocí komunit PHP a Go jsme zvýšili stabilitu řešení, v některých testech jsme zvýšili výkon aplikací až 40krát, vylepšili jsme nástroje pro ladění, 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 uvězněni v zastaralém pohledu na PHP jako na pomalý a objemný jazyk, vhodný pouze pro psaní pluginů pro WordPress. Tito lidé dokonce mohou říct, že PHP má takové omezení: když se aplikace stane dostatečně velkou, musíte zvolit vyspělejší jazyk a přepsat nahromaděný kód, který máte po mnoho let.
Na to všechno bychom chtěli odpovědět: zamyslete se znovu. Domníváme se, že pouze vy si určujete nějaká omezení pro PHP. Můžete strávit celý život přechody z jednoho jazyka na druhý, abyste našli ideální kombinaci pro vaše potřeby, nebo můžete začít vnímat jazyky jako nástroje. Zdánlivé nevýhody jazyka jako PHP mohou být ve skutečnosti příčinou jeho úspěchu. A pokud ho spojíte s jiným jazykem jako Go, vytvoříte mnohem mocnější produkty, než kdybyste se omezili na používání jen jednoho jazyka.
Pracováním se spojením Go a PHP můžeme říci, že jsme si je zamilovali. Neplánujeme obětovat jeden ve prospěch druhého - naopak budeme hledat způsoby, jak ještě více využít tuto dvojitou technologii.
UPD: vítáme tvůrce RoadRunnera a spoluautora původního článku -
Zdroj: habr.com
