Použitie mcrouter na horizontálne škálovanie memcached
Vývoj projektov s vysokou záťažou v akomkoľvek jazyku si vyžaduje špeciálny prístup a použitie špeciálnych nástrojov, ale pokiaľ ide o aplikácie v PHP, situácia sa môže natoľko vyhrotiť, že budete musieť vyvíjať napr. vlastný aplikačný server. V tejto poznámke budeme hovoriť o známej bolesti s distribuovaným ukladaním relácií a ukladaním údajov do vyrovnávacej pamäte v memcached a ako sme tieto problémy vyriešili v jednom projekte „ward“.
Hrdinom tejto príležitosti je aplikácia PHP založená na frameworku Symfony 2.3, ktorá nie je vôbec zahrnutá v obchodných plánoch na aktualizáciu. Okrem celkom štandardného úložiska relácií tento projekt plne využil zásady „ukladanie všetkého do vyrovnávacej pamäte“. v memcached: odpovede na požiadavky na databázové a API servery, rôzne príznaky, zámky na synchronizáciu spúšťania kódu a mnoho ďalšieho. V takejto situácii sa porucha memcached stane osudnou pre fungovanie aplikácie. Okrem toho strata vyrovnávacej pamäte vedie k vážnym následkom: DBMS začne praskať vo švíkoch, služby API začnú zakazovať požiadavky atď. Stabilizácia situácie môže trvať desiatky minút a počas tejto doby bude služba strašne pomalá alebo úplne nedostupná.
Potrebovali sme poskytnúť schopnosť horizontálne škálovať aplikáciu s malým úsilím, t.j. s minimálnymi zmenami zdrojového kódu a zachovaním plnej funkčnosti. Zabezpečte, aby bola vyrovnávacia pamäť nielen odolná voči zlyhaniam, ale tiež sa snažte minimalizovať stratu údajov z nej.
Čo je zlé na samotnom memcached?
Vo všeobecnosti rozšírenie memcached pre PHP podporuje distribuované dáta a ukladanie relácií hneď po vybalení. Mechanizmus konzistentného hashovania kľúčov vám umožňuje rovnomerne umiestňovať údaje na mnohých serveroch, pričom každý konkrétny kľúč jedinečne adresujete konkrétnemu serveru zo skupiny a vstavané nástroje na prepnutie pri zlyhaní zaisťujú vysokú dostupnosť služby ukladania do vyrovnávacej pamäte (ale, bohužiaľ, žiadne dáta).
S ukladaním relácií je to trochu lepšie: môžete ho nakonfigurovať memcached.sess_number_of_replicas, v dôsledku čoho budú dáta uložené na viacerých serveroch naraz a v prípade výpadku jednej memcached inštancie budú dáta prenesené z iných. Ak sa však server vráti do režimu online bez údajov (ako sa to zvyčajne stáva po reštarte), niektoré kľúče budú prerozdelené v jeho prospech. V skutočnosti to bude znamenať strata údajov relácie, keďže neexistuje spôsob, ako „prejsť“ na inú repliku v prípade zmeškania.
Štandardné knižničné nástroje sú zamerané hlavne na horizontálne škálovanie: umožňujú vám zväčšiť vyrovnávaciu pamäť na gigantické veľkosti a poskytnúť k nej prístup z kódu hosťovaného na rôznych serveroch. V našej situácii však objem uložených dát nepresahuje niekoľko gigabajtov a výkon jedného alebo dvoch uzlov úplne postačuje. Jedinými užitočnými štandardnými nástrojmi by teda mohlo byť zabezpečenie dostupnosti memcached pri udržiavaní aspoň jednej inštancie vyrovnávacej pamäte v prevádzkovom stave. Ani túto možnosť však nebolo možné využiť... Tu je vhodné pripomenúť starobylosť frameworku použitého v projekte, a preto nebolo možné prinútiť aplikáciu pracovať s poolom serverov. Nezabúdajme ani na stratu údajov o relácii: zákazníkovi trhlo oko pri masívnom odhlásení používateľov.
V ideálnom prípade to bolo potrebné replikácia záznamov v memcached a obchádzanie replík v prípade omylu alebo omylu. Pomohli nám implementovať túto stratégiu mcrouter.
mcrouter
Ide o memcached router vyvinutý Facebookom na riešenie jeho problémov. Podporuje textový protokol memcached, ktorý umožňuje škálovať inštalácie uložené v pamäti cache do šialených rozmerov. Podrobný popis mcrouter nájdete v toto oznámenie. Okrem iného široká funkčnosť dokáže to, čo potrebujeme:
replikovať záznam;
ak sa vyskytne chyba, vráťte sa na iné servery v skupine.
Prečo tri bazény? Prečo sa servery opakujú? Poďme zistiť, ako to funguje.
V tejto konfigurácii mcrouter vyberie cestu, na ktorú bude požiadavka odoslaná na základe príkazu request. Chlapík mu to povie OperationSelectorRoute.
Požiadavky GET idú obsluhe RandomRoutektorý náhodne vyberie oblasť alebo trasu medzi objektmi poľa children. Každý prvok tohto poľa je zasa handlerom MissFailoverRoute, ktorý bude prechádzať každým serverom v oblasti, kým nedostane odpoveď s údajmi, ktoré sa vrátia klientovi.
Ak by sme používali výlučne MissFailoverRoute s fondom troch serverov by potom všetky požiadavky prichádzali ako prvé do prvej inštancie uloženej v memcached a zvyšok by dostával požiadavky na zvyškovej báze, keď neexistujú žiadne údaje. Takýto prístup by viedol k nadmerné zaťaženie prvého servera v zozname, preto bolo rozhodnuté vygenerovať tri fondy s adresami v rôznych sekvenciách a vybrať ich náhodne.
Všetky ostatné požiadavky (a toto je záznam) sú spracované pomocou AllMajorityRoute. Tento handler posiela požiadavky na všetky servery v oblasti a čaká na odpovede od aspoň N/2 + 1 z nich. Z používania AllSyncRoute pretože operácie zápisu museli byť opustené, pretože táto metóda vyžaduje kladnú odozvu všetko serverov v skupine - inak sa vráti SERVER_ERROR. Hoci mcrouter pridá údaje do dostupných vyrovnávacích pamätí, volanie funkcie PHP vráti chybu a vygeneruje upozornenie. AllMajorityRoute nie je taký prísny a umožňuje vyradiť z prevádzky až polovicu jednotiek bez vyššie popísaných problémov.
Hlavná nevýhoda Táto schéma je taká, že ak v cache naozaj nie sú žiadne dáta, tak pre každú požiadavku od klienta sa skutočne vykoná N požiadaviek na memcached - do každý servery v bazéne. Môžeme znížiť počet serverov v pooloch napríklad na dva: obetujeme spoľahlivosť úložiskaоvyššia rýchlosť a menšia záťaž z požiadaviek na chýbajúce kľúče.
NB: Môžete tiež nájsť užitočné odkazy na učenie sa mcrouter dokumentácia na wiki и problematika projektu (vrátane uzavretých), predstavujúce celý sklad rôznych konfigurácií.
Stavba a prevádzka mcrouteru
Naša aplikácia (a samotná memcached) beží v Kubernetes - podľa toho sa tam nachádza aj mcrouter. Pre zostava kontajnera používame werf, ktorého konfigurácia bude vyzerať takto:
NB: Zoznamy uvedené v článku sú zverejnené v úložisku plochý/mcrouter.
... a načrtnite to Tabuľka kormidla. Zaujímavosťou je, že existuje iba generátor konfigurácií podľa počtu replík (ak má niekto lakonickejšiu a elegantnejšiu možnosť, podeľte sa o ňu v komentároch):
Zavedieme ho do testovacieho prostredia a skontrolujeme:
# php -a
Interactive mode enabled
php > # Проверяем запись и чтение
php > $m = new Memcached();
php > $m->addServer('mcrouter', 11211);
php > var_dump($m->set('test', 'value'));
bool(true)
php > var_dump($m->get('test'));
string(5) "value"
php > # Работает! Тестируем работу сессий:
php > ini_set('session.save_handler', 'memcached');
php > ini_set('session.save_path', 'mcrouter:11211');
php > var_dump(session_start());
PHP Warning: Uncaught Error: Failed to create session ID: memcached (path: mcrouter:11211) in php shell code:1
Stack trace:
#0 php shell code(1): session_start()
#1 {main}
thrown in php shell code on line 1
php > # Не заводится… Попробуем задать session_id:
php > session_id("zzz");
php > var_dump(session_start());
PHP Warning: session_start(): Cannot send session cookie - headers already sent by (output started at php shell code:1) in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Failed to write session lock: UNKNOWN READ FAILURE in php shell code on line 1
PHP Warning: session_start(): Unable to clear session lock record in php shell code on line 1
PHP Warning: session_start(): Failed to read session data: memcached (path: mcrouter:11211) in php shell code on line 1
bool(false)
php >
Hľadanie textu chyby neprinieslo žiadne výsledky, ale dopyt „mcrouter php„V popredí bol najstarší nevyriešený problém projektu – nedostatok podpory binárny protokol memcached.
NB: Protokol ASCII v memcached je pomalší ako binárny a štandardné prostriedky konzistentného hashovania kľúčov fungujú iba s binárnym protokolom. To však nevytvára problémy pre konkrétny prípad.
Trik je vo vrecku: stačí prepnúť na protokol ASCII a všetko bude fungovať.... Avšak v tomto prípade zvyk hľadať odpovede v dokumentáciu na php.net zahral krutý vtip. Nenájdete tam správnu odpoveď... pokiaľ, samozrejme, neprejdete na koniec, kde v sekcii "Poznámky pridané používateľom" bude verný a nespravodlivo záporná odpoveď.
Áno, správny názov možnosti je memcached.sess_binary_protocol. Musí byť deaktivovaný, po ktorom začnú relácie fungovať. Zostáva len vložiť kontajner s mcrouterom do podu s PHP!
Záver
Takže iba zmenami v infraštruktúre sme dokázali vyriešiť problém: problém s toleranciou chýb memcached bol vyriešený a spoľahlivosť vyrovnávacej pamäte sa zvýšila. Okrem očividných výhod pre aplikáciu to dávalo priestor na manévrovanie pri práci na platforme: keď majú všetky komponenty rezervu, výrazne sa zjednoduší život správcu. Áno, táto metóda má aj svoje nevýhody, môže to vyzerať ako „barla“, ale ak šetrí peniaze, pochováva problém a nespôsobuje nové - prečo nie?