Az mcrouter használata a memcached vízszintes méretezéséhez
A nagy terhelésű projektek bármilyen nyelven történő fejlesztése speciális megközelítést és speciális eszközök használatát igényel, de ha PHP-s alkalmazásokról van szó, akkor a helyzet annyira eldurvulhat, hogy fejleszteni kell pl. saját alkalmazásszerver. Ebben a jegyzetben az elosztott munkamenet-tárolás és az adatgyorsítótárazás ismerős fájdalmairól fogunk beszélni a memcached-ben, és arról, hogyan oldottuk meg ezeket a problémákat egyetlen „ward” projektben.
Az alkalom hőse egy symfony 2.3-as keretrendszerre épülő PHP alkalmazás, amely egyáltalán nem szerepel a frissítendő üzleti tervekben. A meglehetősen szabványos munkamenet-tárolás mellett ez a projekt teljes mértékben kihasználta "mindent gyorsítótárazni" irányelv a memcachedben: válaszok az adatbázis- és API-kiszolgálók kérésére, különféle jelzők, kódvégrehajtási szinkronizálási zárak és még sok más. Ilyen helyzetben a memcached meghibásodása végzetessé válik az alkalmazás működése szempontjából. Ezenkívül a gyorsítótár elvesztése súlyos következményekkel jár: a DBMS elkezd szétrobbanni, az API-szolgáltatások elkezdik tiltani a kéréseket stb. A helyzet stabilizálása több tíz percig is eltarthat, és ezalatt a szolgáltatás rettenetesen lassú lesz, vagy teljesen elérhetetlen lesz.
Biztosítanunk kellett az alkalmazás vízszintes méretezésének képessége kis erőfeszítéssel, azaz minimális változtatásokkal a forráskódban és a teljes funkcionalitás megőrzésével. Tedd a gyorsítótárat ne csak ellenállóvá a hibákkal szemben, hanem próbáld meg minimalizálni az adatvesztést is.
Mi a baj magával a memcached-el?
Általánosságban elmondható, hogy a PHP memcached bővítménye már a dobozból is támogatja az elosztott adat- és munkamenet-tárolást. A konzisztens kulcskivonatolás mechanizmusa lehetővé teszi az adatok egyenletes elhelyezését sok szerveren, egyedileg címezve minden egyes kulcsot a csoport egy adott szerveréhez, a beépített feladatátvételi eszközök pedig biztosítják a gyorsítótárazási szolgáltatás magas rendelkezésre állását (de sajnos nincs adat).
A munkamenet-tárolóval kicsit jobb a helyzet: konfigurálható memcached.sess_number_of_replicas, melynek eredményeként az adatok egyszerre több szerveren is tárolásra kerülnek, és egy memcached példány meghibásodása esetén az adatok másokról is átkerülnek. Ha azonban a szerver adatok nélkül tér vissza az internetre (ahogy ez általában újraindítás után történik), a kulcsok egy része újra elosztásra kerül a javára. Valójában ez azt fogja jelenteni a munkamenet adatainak elvesztése, hiszen kihagyás esetén nincs mód másik replikára „menni”.
A szabványos könyvtári eszközök főként az vízszintes méretezés: lehetővé teszik a gyorsítótár gigantikus méretűre növelését, és hozzáférést biztosítanak hozzá a különböző szervereken tárolt kódból. A mi helyzetünkben azonban a tárolt adatok mennyisége nem haladja meg a több gigabájtot, és egy-két csomópont teljesítménye is elég. Ennek megfelelően az egyetlen hasznos szabványos eszköz a memcached elérhetőségének biztosítása lehet, miközben legalább egy gyorsítótár-példányt működőképes állapotban tartanak. Azonban még ezt a lehetőséget sem sikerült kihasználni... Itt érdemes felidézni a projektben használt keretrendszer ősi voltát, ami miatt nem sikerült szerverkészlettel működőképessé tenni az alkalmazást. Ne feledkezzünk meg a munkameneti adatok elvesztéséről sem: az ügyfél szeme megrándult a felhasználók tömeges kijelentkezésétől.
Ideális esetben szükség volt rá rekordok replikációja a gyorsítótárban tárolt és megkerülő replikákban hiba vagy tévedés esetén. Segített nekünk megvalósítani ezt a stratégiát mcrouter.
mcrouter
Ez egy memcached router, amelyet a Facebook fejlesztett ki a problémák megoldására. Támogatja a memcached szöveg protokollt, amely lehetővé teszi méretarányos memcached telepítések őrült méretekben. A mcrouter részletes leírása itt található ezt a bejelentést. Többek között széles körű funkcionalitás megteheti, amire szükségünk van:
rekord replikációja;
hiba esetén lépjen vissza a csoport többi kiszolgálójára.
Miért három medence? Miért ismétlődnek a szerverek? Kitaláljuk, hogyan működik.
Ebben a konfigurációban az mcrouter a request parancs alapján kiválasztja azt az elérési utat, amelyre a kérés elküldésre kerül. A srác ezt elmondja neki OperationSelectorRoute.
A GET kérések a kezelőhöz mennek RandomRouteamely véletlenszerűen kiválaszt egy készletet vagy útvonalat a tömbobjektumok közül children. Ennek a tömbnek minden eleme egy kezelő MissFailoverRoute, amely végigmegy a készlet minden kiszolgálóján, amíg nem kap választ adatokkal, amelyeket visszaküld a kliensnek.
Ha kizárólagosan használnánk MissFailoverRoute egy három szerverből álló készlettel, akkor minden kérés először az első memcached példányhoz érkezne, a többi pedig maradék alapon kapná a kéréseket, amikor nincs adat. Egy ilyen megközelítés ahhoz vezetne túlzott terhelés a lista első szerverén, ezért úgy döntöttek, hogy három különböző szekvenciában lévő címekkel rendelkező készletet generálnak, és véletlenszerűen választják ki őket.
Az összes többi kérést (és ez egy rekord) a segítségével dolgozzuk fel AllMajorityRoute. Ez a kezelő kéréseket küld a készletben lévő összes szervernek, és ezek közül legalább N/2 + 1 válaszra vár. Használatból AllSyncRoute Az írási műveleteket el kellett hagyni, mivel ez a módszer pozitív választ igényel minden szerverek a csoportban – különben vissza fog térni SERVER_ERROR. Bár az mcrouter hozzáadja az adatokat az elérhető gyorsítótárakhoz, a hívó PHP függvény hibát ad vissza és értesítést generál. AllMajorityRoute nem olyan szigorú, és lehetővé teszi akár az egységek felének forgalomból való kivonását a fent leírt problémák nélkül.
Fő hátránya Ez a séma az, hogy ha valóban nincs adat a gyorsítótárban, akkor a klienstől érkező minden egyes kérés esetén ténylegesen végrehajtódik N kérés a memcached-re. mindenkinek szerverek a medencében. A készletekben lévő szerverek számát például kettőre csökkenthetjük: a tárolási megbízhatóság feláldozásával kapjukоnagyobb sebesség és kevesebb terhelés a kérésektől a hiányzó kulcsokig.
NB: Hasznos linkeket is találhat az mcrouter tanulásához dokumentáció a wikin и projekt kérdései (beleértve a zártakat is), amelyek különféle konfigurációk egész raktárát képviselik.
mcrouter építése és működtetése
Az alkalmazásunk (és maga a memcached is) Kubernetesben fut - ennek megfelelően az mcrouter is ott található. Mert konténer összeállítás használunk werf, amelynek a konfigurációja így fog kinézni:
NB: A cikkben megadott listák az adattárban vannak közzétéve lapos/mcrouter.
... és vázolja fel Helm diagram. Az az érdekes, hogy csak a replikák száma alapján van konfigurációs generátor (ha valakinek van lakonikusabb és elegánsabb lehetősége, ossza meg kommentben):
# 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 >
A hiba szövegének keresése nem hozott eredményt, de a „microuter php"Az előtérben a projekt legrégebbi megoldatlan problémája volt - támogatás hiánya memcached bináris protokoll.
NB: Az ASCII protokoll a memcachedben lassabb, mint a bináris, és a konzisztens kulcskivonat szabványos eszközei csak a bináris protokollal működnek. De ez nem okoz problémát egy konkrét esetben.
A trükk a zsákban van: csak át kell váltanod az ASCII protokollra és minden menni fog.... Ebben az esetben azonban a válaszkeresés szokása dokumentáció a php.net oldalon kegyetlen tréfát játszott. Ott nem találja meg a helyes választ... persze hacsak nem görgeti a végére, hol a részben "Felhasználói megjegyzések" hűséges lesz és igazságtalanul negatívan szavazott válasz.
Igen, az opció helyes neve memcached.sess_binary_protocol. Le kell tiltani, ezután a munkamenetek működni kezdenek. Már csak az mcrouterrel ellátott konténert kell egy PHP-s podba tenni!
Következtetés
Így csupán infrastrukturális változtatásokkal meg tudtuk oldani a problémát: megoldódott a memcached hibatűrés problémája, és nőtt a cache tárolás megbízhatósága. Az alkalmazás számára nyilvánvaló előnyök mellett ez mozgásteret adott a platformon végzett munka során: ha minden komponens rendelkezik tartalékkal, az adminisztrátor élete jelentősen leegyszerűsödik. Igen, ennek a módszernek is vannak hátrányai, lehet, hogy „mankónak” tűnhet, de ha pénzt takarít meg, eltemeti a problémát és nem okoz újakat - miért ne?