
Bár bármilyen nyelven készült magas terhelésű projektek fejlesztése különleges megközelítést és speciális eszközök alkalmazását igényli, amikor PHP alkalmazásokról van szó, a helyzet annyira fokozódhat, hogy például . Jelen cikkben a már jól ismert problémával foglalkozunk, amely a munkamenetek elosztott tárolásával és a memcached adatok gyorsítótárazásával kapcsolatos, és arról, hogyan oldottuk meg ezeket a kihívásokat egy "betegek" projektben.
Az ünnepelt a PHP alapú alkalmazás, amely a symfony 2.3 keretrendszeren alapul, amelyet a vállalkozás tervei között sem szerepel frissíteni. A hagyományos munkamenet tároláson túl a projektben teljes mértékben alkalmazták a "mindent gyorsítótárazás" politikát a memcached-ben: válaszok az adatbázis és API szerverekhez intézett kérésekre, különféle zászlók, blokkok a kód végrehajtásának szinkronizálására és sok másra. Ilyen helyzetben a memcached meghibásodása végzetes következményekkel jár az alkalmazás működésében. Továbbá, a gyorsítótár elvesztése súlyos következményekkel jár: az adatbázis lehetetlenné válik, az API-szolgáltatások elutasítják a kéréseket, stb. A helyzet stabilizálása akár több percet is igénybe vehet, és ez idő alatt a szolgáltatás rendkívül lelassul, vagy akár elérhetetlenné válik.
Biztosítani kellett, hogy az alkalmazás vízszintes skálázásának lehetősége minimális összes változással, azaz a forráskód minimális módosítása mellett, és a funkciók teljes megőrzésével. A gyorsítótárat nemcsak a meghibásodásokkal szemben kell ellenállóvá tenni, hanem törekedni arra is, hogy minimalizáljuk a benne lévő adatok elvesztését.
Miért nem megfelelő magával a memcached-del?
Általában a memcached PHP bővítménye "dobozból" támogatja az adatok és munkamenetek elosztott tárolását. A kulcsok konzisztens hash-elési mechanizmusa lehetővé teszi, hogy az adatokat egyenlően helyezzük el több szerveren, minden egyes kulcsot egy csoportból egy adott szerverhez rendelve, a beépített hibaátalakító eszközök pedig magas rendelkezésre állást biztosítanak a gyorsítótárazási szolgáltatáshoz (de sajnos, nem az adatokhoz).
. A munkamenetek tárolása egy kicsit jobban alakul: beállítható, memcached.sess_number_of_replicas, ennek következtében az adatok több szerverre lesznek mentve, és ha az egyik memcached példány meghibásodik, az adatok a másikról érkeznek. Azonban, ha a szerver visszatér az üzembe, de adatok nélkül (ahogyan ez általában egy újraindítás után történik), néhány kulcsot újraosztanak a javára. Ez lényegében azt jelentheti az ülések adatainak elvesztését, mivel nincs lehetőség „elmenni” egy másik replikához hiba esetén.
A könyvtár alapvető eszközei elsősorban vízszintes skálázásra irányulnak: lehetővé teszik a cache óriási méretekre történő bővítését és a különböző szervereken elhelyezett kódból való hozzáférés biztosítását. Azonban a jelen helyzetben a tárolt adatok mennyisége nem haladja meg néhány gigabájtot, és egy-két csomópont elegendő teljesítményt nyújt. Ennek megfelelően az alapvető eszközök legfeljebb azt biztosíthatnák, hogy a memcached elérhetősége megmaradjon, ha legalább egy cache példány működő állapotban van. Azonban még ezzel a lehetőséggel sem tudtunk élni… Emlékeztetni kell arra, hogy a projektben használt keretrendszer régisége miatt nem tudtuk működésre bírni az alkalmazást szerverekből álló csoporttal. Ne feledkezzünk meg az ülések adatainak elvesztéséről sem: a megrendelőnél a felhasználók tömeges kijelentkezése miatt remegtek a szemek.
Ideális esetben a memcached-ben történő feljegyzés replikálása és a replikák megkerülése hiba vagy probléma esetén. Ezt a stratégiát segített megvalósítani .
mcrouter
Ez egy memcached router, amelyet a Facebook fejlesztett ki, hogy megoldja a problémáit. Támogatja a memcached szöveges protokollját, amely lehetővé teszi a memcached telepítések skálázását őrült méretekig. A mcrouter részletes leírását megtalálhatja . Ezen kívül nyújt, és pontosan azt tudja, amire szükségünk van:
- a feljegyzés replikálása;
- a hiba esetén a csoport más szervereire történő visszaállás.
Lássunk neki!
mcrouter konfigurációja
Azonnal áttérek a konfigurációra:
{
"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 van három medence? Miért ismétlődnek a szerverek? Nézzük meg, hogyan működik ez.
- Ebben a konfigurációban a mcrouter kiválasztja azt az utat, amelyen a kérés készült a kérés parancsa alapján. Ezt mondja meg neki a típus:
OperationSelectorRoute. - A GET-kérések a kezelőbe kerülnek:
RandomRoute, amely véletlenszerűen választja ki a medencét vagy utat a tömb objektumai közül.children. Ennek a tömbnek minden eleme egy kezelő:MissFailoverRoute, amely végigmegy az összes szerveren a medencében, amíg választ nem kap az adatokból, ami vissza fog térni az ügyfélnek. - Ha csak
MissFailoverRoutea három szerverből álló medencét használnánk, akkor minden kérés először az első memcached példányra kerülne, míg a többiek az adatok hiánya miatt tartalékos alapon kapnák a kéréseket. Ez a megközelítés túlnyomó terhelést okozna az első szerveren a listán,ezért úgy döntöttek, hogy három medencét generálnak különböző címsorokkal, és véletlenszerűen választanak belőlük. - Minden további kérés (ez írás) az
AllMajorityRoutesegítségével kerül feldolgozásra. Ez a kezelő a medence összes szerverére küldi a kéréseket, és válaszokat vár legalább az N/2 + 1-től. AzAllSyncRoutehasználatát az írási műveletekhez el kellett vetni, mert ez a módszer pozitív választ igényel a csoport szervereitől — ellenkező esetben visszaadja a adattal, tehát csak a TXE jelző nem lesz elegendő. SERVER_ERROR. Bár a mcrouter az elérhető gyorsítótárakban tárolja az adatokat, a PHP hívó funkcióhibát fog visszaadni , és generál egy értesítést. kevésbé szigorú, és lehetővé teszi, hogy akár felének a csomópontokat üzemeltessük a fent említett problémák nélkül.AllMajorityRouteA fő hátránya
ennek a sémának az, hogy ha az adatok valóban nem állnak rendelkezésre a gyorsítótárban, akkor minden egyes ügyfélszolgálati kérés ténylegesen N kérdéshez fog vezetni a memcachedhez — a medencében lévő szerverekhez. Csökkenthetjük a szerverek számát a medencékben, például kettőre: a tárolás megbízhatóságának feláldozásával kevesebb erőforrást nyerhetünk. minden Csökkenthető a poolokban lévő szerverek száma, például kettőre: a tárolás megbízhatóságának feláldozásával egy jobbónagyobb sebességet és kisebb terhelést biztosít a hiányzó kulcsokhoz intézett lekérdezések esetén.
NB: Hasznos hivatkozások a mcrouter tanulmányozásához és (beleértve a zártak is), amelyek különféle konfigurációk kincsesbányáját jelentenek.
A mcrouter összetevői és futtatása
Az alkalmazás (és magának a memcached-nek) Kubernetesben fut, így itt van a mcrouter helye is. A konténer összeszereléséhez a következőt használjuk , a konfiguráció pedig a következőképpen néz ki:
NB: A cikkben bemutatott listák a repóban találhatóak .
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: Előfeltételek telepítése
apt:
name: [ 'apt-transport-https', 'tzdata', 'locales' ]
update_cache: yes
- name: mcrouter APT kulcs hozzáadása
apt_key:
url: https://facebook.github.io/mcrouter/debrepo/xenial/PUBLIC.KEY
- name: mcrouter repó hozzáadása
apt_repository:
repo: deb https://facebook.github.io/mcrouter/debrepo/xenial xenial contrib
filename: mcrouter
update_cache: yes
- name: Időzóna beállítása
timezone:
name: "Europe/Moscow"
- name: Ellenőrizzük, hogy létezik-e egy nyelv
locale_gen:
name: en_US.UTF-8
state: present
install:
- name: mcrouter telepítése
apt:
name: [ 'mcrouter' ]()
… és létrehozunk egy vázlatot a konfigurációhoz Helm-chart. Az érdekes az, hogy itt csak a konfiguráció generátor van a példányok számától függően (ha valakinek van egy tömörebb és elegánsabb változata — osszátok meg a megjegyzésekben):
{{- $count := (pluck .Values.global.env .Values.memcached.replicas | first | default .Values.memcached.replicas._default | int) -}}
{{- $pools := dict -}}
{{- $servers := list -}}
{{- \/\* Két másolatot töltünk be a szerverekből: "0 1 2 0 1 2" *\/ -}}
{{- range until 2 -}}
{{- range $i, $_ := until $count -}}
{{- $servers = append $servers (printf "mc-%d.mc:11211" $i) -}}
{{- end -}}
{{- end -}}
{{- \/\* A tömbön mozogva N szeletet kapunk: "[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|poold" $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 }}
}
}
}
}()
Bevezetjük 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üzenet szövegére keresve nem találtunk eredményt, azonban a "" keresésnél a projekt legidősebb nyitott problémája került előtérbe — memcached bináris protokoll.
NB: Az ASCII protokoll a memcached-ben lassabb, mint a bináris, és az alapértelmezett kulcsok konzisztens hash-elése csak a bináris protokollal működik. De ez egy konkrét esetben nem okoz problémát.
A dolog biztos: csak az ASCII-protokollra kell váltani, és minden működni fog…. Azonban ebben az esetben a php.net dokumentációban keresett válaszok szokása „Felhasználói hozzájárulások megjegyzések” szekcióban lesz helyes és méltatlanul leárazott válasz. .
memcached.sess_binary_protocol . Ezt ki kell kapcsolni, és az ülések működni fognak. Csak annyit kell tennie, hogy a mcrouter konténert a PHP podjába helyezi!Így az infrastruktúra-változtatásokkal sikerült megoldani a kitűzött feladatot: a memcached magas rendelkezésre állásának kérdése megoldódott, a cache tárolásának megbízhatósága nőtt. Az alkalmazásnak nyújtott nyilvánvaló előnyök mellett ez mozgásteret biztosított a platform fejlesztésében: amikor minden komponensnek van tartalékja, az adminisztrátor élete sokkal egyszerűbb. Igen, ennek a módszernek is megvannak a hátrányai, és „tákolmánynak” tűnhet, de ha pénzt takarít meg, eltünteti a problémát, és nem okoz újakat — miért ne?
Zárás
„Gyakorlat dapp-al”
P.S.
Olvasson további információkat a blogunkban:
- (symfony-demo példáján) 1. rész (egyszerű alkalmazások összeszerelése): és ;
- «».
Forrás: habr.com
