Përdorim mcrouter për shkallëzimin horizontal të memcached

Përdorim mcrouter për shkallëzimin horizontal të memcached

Zhvillimi i projekteve me ngarkesë të lartë në çdo gjuhë kërkon një qasje të veçantë dhe përdorimin e mjeteve speciale, por kur flasim për aplikacione në PHP, situata mund të përkeqësohet aq shumë saqë duhet të zhvillohet, për shembull, një server aplikacionesh. Në këtë artikull do flasim për një dhimbje të njohur nga të gjithë lidhur me ruajtjen e shpërndarë të sesioneve dhe memcached dhe se si i zgjodhëm këto probleme në një projekt "nën mbikëqyrje".

Protagonisti i historisë është një aplikacion në PHP, i bazuar në kornizën Symfony 2.3, e cila nuk është në planet e biznesit të azhurnohet. Përveç ruajtjes së zakonshme të sesioneve, në këtë projekt përdoreshin me shumë akt të fuqishëm politika "keshimi i gjithçkaje" në memcached: përgjigjet për kërkesat në DB dhe serverat API, flamujt e ndryshëm, bllokimet për sinkronizimin e ekzekutimit të kodit dhe shumë të tjera. Në një situatë të tillë, dështimi i memcached bëhet fatal për funksionimin e aplikacionit. Për më tepër, humbja e caches sjell pasoja të rënda: DB fillon të thyehet, shërbimet API - ndalojnë kërkesat etj. Stabilizimi i situatës mund të zgjasë disa minuta, ndërsa në këtë kohë shërbimi do të ngadalësohet ose mund të bëhet krejtësisht i paarritshëm.

Na nevojitej të siguronim mundësinë e skalimit horizontal të aplikacionit me pak mundësi, domethënë me ndryshime minimale në kodin burimor dhe ruajtjen e plotë të funksionalitetit. Të bëjmë që cache të jetë jo vetëm i qëndrueshëm ndaj dështimeve, por gjithashtu të përpiqemi të minimizojmë humbjet e të dhënave prej tij.

Çfarë ka të keqe me vetë memcached?

Në përgjithësi, zgjerimi memcached për PHP "nga kutia" mbështet ruajtjen e shpërndarë të të dhënave dhe sesioneve. Mekanizmi i hash-it të qëndrueshëm të çelësave lejon vendosjen e barabartë të të dhënave në shumë servera, duke adresuar në mënyrë të qartë çdo çelës të caktuar në një server të grupit, dhe mjetet e ndihmës së dështimit sigurojnë një akses të lartë në shërbimin e caches (por, fatkeqësisht, jo të dhënat).

Për sa i përket sesioneve, gjërat janë pak më mirë: mund të konfigurohet memcached.sess_number_of_replicas, si rezultat të cilit të dhënat do të ruhen menjëherë në disa servera, dhe në rast të dështimit të një ekzistence memcached, të dhënat do të jepen nga të tjerët. Megjithatë, nëse serveri rikthehet në punë pa të dhëna (siç ndodh zakonisht pas një rishtimi), disa çelësa do të shpërndahen në favor të tij. Në fakt, kjo do të thotë humbje të të dhënave të sesionit, pasi nuk ka mundësi "të shkojmë" në një replikë tjetër në rast të një humbjeje.

Mjetet standarde të bibliotekës janë kryesisht të drejtuara për skalimin horizontal: ato lejojnë të rrisni cache në përmasa gjigante dhe të sigurojnë akses në të nga kodi i vendosur në servera të ndryshëm. Megjithatë, në situatën tonë, volumi i të dhënave të ruajtura nuk tejkalon disa gigabajt, dhe performanca e një ose dy nodes është mjaft e mirë. Prandaj, nga ato që janë të dobishme, mjetet standarde mund të sigurojnë vetëm disponueshmërinë e memcached duke ruajtur të paktën një ekzemplar me cache në gjendje pune. Megjithatë, madje as kjo mundësi nuk arritëm ta shfrytëzojmë... Këtu është e rëndësishme të përmendim se korniza e përdorur në projekt është shumë e vjetër, duke e bërë të pamundur të bëja aplikacionin të punonte me një grup serverash. Po ashtu, mos harrojmë për humbjet e të dhënave të sesioneve: klienti përjetonte një reagim nervor nga dalja masive e përdoruesve.

Idealisht, nevojitej repikimi i shkrimeve në memcached dhe kalimi përmes replikave në rast të humbjes ose gabimit. Realizimi i kësaj strategjie na ndihmoi mcrouter.

mcrouter

Ky është një ruter për memcached, i zhvilluar nga Facebook për të zgjidhur problemet e saj. Ai mbështet protokollin tekstual memcached, i cili lejon të shkallëzoni instalimet memcached në përmasa të çmendura. Një përshkrim i detajuar i mcrouter mund të gjendet në këtu. Përveç funksionaliteteve të tjera të gjera ai mund të bëjë atë që na nevojitet: të repikoni shkrimin;

  • të bëjë fallback në serverat e tjerë të grupit në rast të ndonjë gabimi.
  • Përpara!

Konfigurimi i mcrouter

Do të kaloj menjëherë te konfigurimi:

{ "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" ] } } } }

Përse tre grupe? Pse përsëriten serverat? Le të shqyrtojmë se si funksionon kjo.

Në këtë konfigurim, mcrouter zgjedh rrugën se ku do të dërgohet kërkesa në varësi të komandës së kërkesës. Këtë ia thotë tipi

  • В данной конфигурации mcrouter выбирает путь, по которому будет отправлен запрос исходя из команды запроса. Об этом ему говорит тип OperationSelectorRoute.
  • Kërkesat GET shkojnë te menaxheri RandomRoute, i cili zgjedh rastësisht një grup ose rrugë nga objektet në array children. Çdo element i këtij array është gjithashtu një menaxher MissFailoverRoute, i cili do të kalojë përmes çdo serveri në grup derisa të marrë një përgjigje me të dhëna, e cila do të kthehet te klienti.
  • Nëse do të përdornim ekskluzivisht MissFailoverRoute me një grup prej tre serverash, të gjitha kërkesat do të vinin fillimisht te instanca e parë memcached, ndërsa të tjerat do të merrnin kërkesa sipas parimit të mbetur, kur të dhënat mungonin. Ky qasje do të çonte në ngarkesë të tepërt në serverin e parë të listës, prandaj ishte vendosur të gjenerohej tre grupe me adresat në një rend të ndryshëm dhe të zgjidheshin ato rastësisht.
  • Të gjitha kërkesat e tjera (pra, ato të shkrimit) trajtohen përmes AllMajorityRoute. Ky menaxher dërgon kërkesat në të gjitha serverat e grupit dhe pret përgjigje nga të paktën N/2 + 1 prej tyre. Përdorimi i AllSyncRoute për operacionet e shkrimit u desh të braktisej, pasi kjo metodë kërkon një përgjigje pozitive nga të gjitha serverat e grupit - në rast të kundërt, do të kthejë SERVER_ERROR. Ndërsa mcrouter do të ruajë të dhënat në cache-t e disponueshëm, funksioni thirrës PHP do të kthejë një gabim dhe do të gjenerojë një njoftim. AllMajorityRoute nuk është aq rigoroz dhe lejon që deri në gjysmën e nyjave të jashtohen nga përdorimi pa problemet e përmendura më sipër.

Mina kryesore e këtij skenari është se, nëse të dhënat nuk janë në cache, atëherë për çdo kërkesë nga klienti do të ekzekutohen praktikisht N kërkesa në memcached - për me të gjitha serverat në grup. Mund të zvogëlojmë numrin e serverave në grupe, për shembull, në dy: duke sakrifikuar besueshmërinë e ruajtjes, ne do të fitojmë mëoshpejtësi dhe më pak ngarkesë nga kërkesat për çelësat që mungojnë.

NB: Lidhjet e dobishme për të studiuar mcrouter gjithashtu mund të jenë dokumenti në wiki dhe problemet e projektit (përfshirë ato të mbyllura), që përfaqësojnë një thes të plotë konfigurimesh të ndryshme.

Ndërtimi dhe nisja e mcrouter

Aplikacioni (dhe vetë memcached) punon në Kubernetes - për rrjedhojë, edhe aty është vendi i mcrouter. Për ndërtimin e kontejnerit ne përdorim werf, konfigurimi i të cilit do të duket kështu:

NB: Listat e paraqitura në artikull janë publikuar në repositorin flant/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: Instaloni paraprakët
   apt:
     name: [ 'apt-transport-https', 'tzdata', 'locales' ]
     update_cache: yes
 - name: Shto çelësin APT të mcrouter
   apt_key:
     url: https://facebook.github.io/mcrouter/debrepo/xenial/PUBLIC.KEY
 - name: Shto Repo-in e mcrouter
   apt_repository:
     repo: deb https://facebook.github.io/mcrouter/debrepo/xenial xenial contrib
     filename: mcrouter
     update_cache: yes
 - name: Cakto kohën
   timezone:
     name: "Europe/Moscow"
 - name: Siguro që një gjuhë ekziston
   locale_gen:
     name: en_US.UTF-8
     state: present
 install:
 - name: Instaloni mcrouter
   apt:
     name: [ 'mcrouter' ]

(werf.yaml)

… dhe hedhim një skicë Helm-chart. Nga e re — këtu është vetëm gjeneratori i konfigurimit në varësi të numrit të replika (nëse dikush ka një variant më të shkurtër dhe elegant — ndani në komentet):

{{- $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)

E publikojmë në mjedisin testues dhe e kontrollojmë:

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

Kërkimi për tekstin e gabimit nuk dha rezultat, megjithatë, me kërkesën "mcrouter php" në vendet e para doli problemi më i vjetër i pazgjidhur i projektit — mungesa e mbështetjes protokollit binar memcached.

NB: Protokolli ASCII në memcached është më i ngadalshëm se ai binar, si dhe mjetet standarde për heshim të qëndrueshëm të çelësave punojnë vetëm me protokollin binar. Por kjo nuk krijon probleme për rastin specifik.

Çështja është e thjeshtë: mbetet vetëm të kalosh te protokolli ASCII dhe gjithçka do të funksionojë…. Megjithatë, në këtë rast, zakoni për të kërkuar përgjigje në dokumentacionin në php.net ka luajtur një këqyrje. Nuk do të gjeni përgjigje të saktë atje… nëse, sigurisht, nuk shfletoni deri në fund, ku në seksionin «Shënimet e kontribuuesve» do të jetë përgjigjja e saktë dhe e pamiratuar..

Po, emri i saktë i opsionit është memcached.sess_binary_protocol. Duhet ta çaktivizoni atë, pas së cilës sesionet do të fillojnë të funksionojnë. Kjo është, vetëm duhet të vendosni kontejnerin me mcrouter në pod me PHP!

Përfundimi

Në këtë mënyrë, me ndihmën vetëm të ndryshimeve infrastrukturore arritëm të zgjidhim detyrën e vendosur: çështja e qëndrueshmërisë të memcached është zgjidhur, besueshmëria e ruajtjes së cache është rritur. Përveç përfitimeve të qarta për aplikacionin, kjo krijoi hapësirë për manovrim gjatë punëve mbi platformën: kur të gjithë komponentët kanë rezerva, jeta e administratorit bëhet shumë më e lehtë. Po, kjo metodë ka dhe disavantazhet e saj, mund të duket si një «përpjekje improvizuese», por nëse ajo kursen para, fsheh problemin dhe nuk shkakton të reja — pse jo?

P.S.

Lexoni gjithashtu në blogun tonë:

Burimi: habr.com

Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS 🔥 Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS | ProHoster