Az mcrouter használata a memcached vízszintes méretezéséhez

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.

Vágj bele az üzletbe!

mcrouter konfiguráció

Egyből megyek a konfigurációhoz:

{
 "pools": {
   "pool00": {
     "servers": [
       "mc-0.mc:11211",
       "mc-1.mc:11211",
       "mc-2.mc:11211"
   },
   "pool01": {
     "servers": [
       "mc-1.mc:11211",
       "mc-2.mc:11211",
       "mc-0.mc:11211"
   },
   "pool02": {
     "servers": [
       "mc-2.mc:11211",
       "mc-0.mc:11211",
       "mc-1.mc:11211"
 },
 "route": {
   "type": "OperationSelectorRoute",
   "default_policy": "AllMajorityRoute|Pool|pool00",
   "operation_policies": {
     "get": {
       "type": "RandomRoute",
       "children": [
         "MissFailoverRoute|Pool|pool02",
         "MissFailoverRoute|Pool|pool00",
         "MissFailoverRoute|Pool|pool01"
       ]
     }
   }
 }
}

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.

configVersion: 1
project: mcrouter
deploy:
 namespace: '[[ env ]]'
 helmRelease: '[[ project ]]-[[ env ]]'
---
image: mcrouter
from: ubuntu:16.04
mount:
- from: tmp_dir
 to: /var/lib/apt/lists
- from: build_dir
 to: /var/cache/apt
ansible:
 beforeInstall:
 - name: Install prerequisites
   apt:
     name: [ 'apt-transport-https', 'tzdata', 'locales' ]
     update_cache: yes
 - name: Add mcrouter APT key
   apt_key:
     url: https://facebook.github.io/mcrouter/debrepo/xenial/PUBLIC.KEY
 - name: Add mcrouter Repo
   apt_repository:
     repo: deb https://facebook.github.io/mcrouter/debrepo/xenial xenial contrib
     filename: mcrouter
     update_cache: yes
 - name: Set timezone
   timezone:
     name: "Europe/Moscow"
 - name: Ensure a locale exists
   locale_gen:
     name: en_US.UTF-8
     state: present
 install:
 - name: Install mcrouter
   apt:
     name: [ 'mcrouter' ]

(werf.yaml)

... é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):

{{- $count := (pluck .Values.global.env .Values.memcached.replicas | first | default .Values.memcached.replicas._default | int) -}}
{{- $pools := dict -}}
{{- $servers := list -}}
{{- /* Заполняем  массив двумя копиями серверов: "0 1 2 0 1 2" */ -}}
{{- range until 2 -}}
 {{- range $i, $_ := until $count -}}
   {{- $servers = append $servers (printf "mc-%d.mc:11211" $i) -}}
 {{- end -}}
{{- end -}}
{{- /* Смещаясь по массиву, получаем N срезов: "[0 1 2] [1 2 0] [2 0 1]" */ -}}
{{- range $i, $_ := until $count -}}
 {{- $pool := dict "servers" (slice $servers $i (add $i $count)) -}}
 {{- $_ := set $pools (printf "MissFailoverRoute|Pool|pool%02d" $i) $pool -}}
{{- end -}}
---
apiVersion: v1
kind: ConfigMap
metadata:
 name: mcrouter
data:
 config.json: |
   {
     "pools": {{- $pools | toJson | replace "MissFailoverRoute|Pool|" "" -}},
     "route": {
       "type": "OperationSelectorRoute",
       "default_policy": "AllMajorityRoute|Pool|pool00",
       "operation_policies": {
         "get": {
           "type": "RandomRoute",
           "children": {{- keys $pools | toJson }}
         }
       }
     }
   }

(10-mcrouter.yaml)

Bemutatjuk a tesztkörnyezetbe, és ellenőrizzük:

# 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?

PS

Olvassa el blogunkon is:

Forrás: will.com

Hozzászólás