Duke përdorur mcrouter për të shkallëzuar memcached horizontalisht

Duke përdorur mcrouter për të shkallëzuar memcached horizontalisht

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 bëhet fjalë për aplikacionet në PHP, situata mund të rëndohet aq shumë sa duhet të zhvilloni, për shembull, serverin e vet të aplikacionit. Në këtë shënim do të flasim për dhimbjen e njohur me ruajtjen e sesioneve të shpërndara dhe ruajtjen e të dhënave në memcached dhe si i zgjidhëm këto probleme në një projekt "repart".

Heroi i rastit është një aplikacion PHP i bazuar në kornizën symfony 2.3, i cili nuk është fare i përfshirë në planet e biznesit për t'u përditësuar. Përveç ruajtjes mjaft standarde të sesioneve, ky projekt përdori plotësisht politika e "ruajtjes së çdo gjëje". në memcached: përgjigjet ndaj kërkesave ndaj bazës së të dhënave dhe serverëve API, flamuj të ndryshëm, bravë për sinkronizimin e ekzekutimit të kodit dhe shumë më tepër. Në një situatë të tillë, një avari e memcached bëhet fatale për funksionimin e aplikacionit. Për më tepër, humbja e cache-it çon në pasoja të rënda: DBMS fillon të shpërthejë në shtresat, shërbimet API fillojnë të ndalojnë kërkesat, etj. Stabilizimi i situatës mund të zgjasë dhjetëra minuta dhe gjatë kësaj kohe shërbimi do të jetë tmerrësisht i ngadalshëm ose plotësisht i padisponueshëm.

Ne duhej të siguronim aftësia për të shkallëzuar horizontalisht aplikacionin me pak përpjekje, d.m.th. me ndryshime minimale në kodin burimor dhe funksionalitet të plotë të ruajtur. Bëni cache jo vetëm rezistente ndaj dështimeve, por gjithashtu përpiquni të minimizoni humbjen e të dhënave prej saj.

Çfarë nuk shkon me vetë memcached?

Në përgjithësi, zgjerimi memcached për PHP mbështet të dhënat e shpërndara dhe ruajtjen e sesioneve jashtë kutisë. Mekanizmi i hashimit të qëndrueshëm të çelësave ju lejon të vendosni në mënyrë të barabartë të dhënat në shumë serverë, duke adresuar në mënyrë unike çdo çelës specifik në një server specifik nga grupi, dhe mjetet e integruara të dështimit sigurojnë disponueshmëri të lartë të shërbimit të memorizimit (por, për fat të keq, nuk ka të dhëna).

Gjërat janë pak më të mira me ruajtjen e sesioneve: mund ta konfiguroni memcached.sess_number_of_replicas, si rezultat i së cilës të dhënat do të ruhen në disa serverë njëherësh, dhe në rast të dështimit të një shembulli memcached, të dhënat do të transferohen nga të tjerët. Megjithatë, nëse serveri kthehet në linjë pa të dhëna (siç ndodh zakonisht pas një rinisjeje), disa nga çelësat do të rishpërndahen në favor të tij. Në fakt kjo do të thotë humbja e të dhënave të sesionit, pasi nuk ka asnjë mënyrë për të "shkuar" në një kopje tjetër në rast të humbjes.

Mjetet standarde të bibliotekës synojnë kryesisht horizontale shkallëzimi: ato ju lejojnë të rrisni cache-në në madhësi gjigante dhe të siguroni akses në të nga kodi i pritur në serverë të ndryshëm. Sidoqoftë, në situatën tonë, vëllimi i të dhënave të ruajtura nuk i kalon disa gigabajt, dhe performanca e një ose dy nyjeve është mjaft e mjaftueshme. Prandaj, të vetmet mjete standarde të dobishme mund të jenë sigurimi i disponueshmërisë së memcached duke ruajtur të paktën një shembull të memories në gjendje pune. Megjithatë, as kjo mundësi nuk ishte e mundur të përfitohej... Këtu ia vlen të kujtojmë lashtësinë e kornizës së përdorur në projekt, prandaj ishte e pamundur që aplikacioni të funksiononte me një grup serverësh. Le të mos harrojmë gjithashtu humbjen e të dhënave të sesionit: syri i klientit u tund nga dalja masive e përdoruesve.

Idealisht ishte e nevojshme përsëritja e të dhënave në kopje të memcached dhe anashkaluar në rast të një gabimi ose gabimi. Na ndihmoi në zbatimin e kësaj strategjie mcrouter.

mcrouter

Ky është një ruter memcached i zhvilluar nga Facebook për të zgjidhur problemet e tij. Ai mbështet protokollin e tekstit memcached, i cili lejon shkallë instalimet memcached në përmasa të çmendura. Një përshkrim i hollësishëm i mcrouter mund të gjendet në këtë njoftim. Ndër të tjera funksionalitet të gjerë mund të bëjë atë që na nevojitet:

  • repliko rekorde;
  • bëni rikthim në serverët e tjerë në grup nëse ndodh një gabim.

Shkoni në punë!

konfigurimi i mcrouter-it

Do të shkoj direkt 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"
       ]
     }
   }
 }
}

Pse tre pishina? Pse përsëriten serverët? Le të kuptojmë se si funksionon.

  • Në këtë konfigurim, mcrouter zgjedh rrugën në të cilën do të dërgohet kërkesa bazuar në komandën e kërkesës. Djali i thotë këtë OperationSelectorRoute.
  • Kërkesat GET shkojnë te mbajtësi RandomRoutei cili zgjedh rastësisht një grup ose rrugë midis objekteve të grupit children. Çdo element i këtij grupi është nga ana tjetër një mbajtës MissFailoverRoute, i cili do të kalojë nëpër çdo server në pishinë derisa të marrë një përgjigje me të dhëna, të cilat do t'i kthehen klientit.
  • Nëse përdornim ekskluzivisht MissFailoverRoute me një grup prej tre serverash, atëherë të gjitha kërkesat do të vinin fillimisht në instancën e parë të memcached, dhe pjesa tjetër do të merrte kërkesa në bazë të mbetur kur nuk ka të dhëna. Një qasje e tillë do të çonte në ngarkesë e tepërt në serverin e parë në listë, kështu që u vendos që të gjenerohen tre grupe me adresa në sekuenca të ndryshme dhe të zgjidhen ato në mënyrë të rastësishme.
  • Të gjitha kërkesat e tjera (dhe ky është një rekord) përpunohen duke përdorur AllMajorityRoute. Ky mbajtës u dërgon kërkesa të gjithë serverëve në grup dhe pret përgjigje nga të paktën N/2 + 1 prej tyre. Nga përdorimi AllSyncRoute për të shkruar operacionet duhej të braktiseshin, pasi kjo metodë kërkon një përgjigje pozitive nga Të gjithë serverët në grup - përndryshe do të kthehet SERVER_ERROR. Megjithëse mcrouter do t'i shtojë të dhënat në memoriet e disponueshme, funksioni i thirrjes PHP do të kthejë një gabim dhe do të gjenerojë njoftim. AllMajorityRoute nuk është aq i rreptë dhe lejon që deri në gjysmën e njësive të hiqen nga shërbimi pa problemet e përshkruara më sipër.

Disavantazhi kryesor Kjo skemë është që nëse me të vërtetë nuk ka të dhëna në cache, atëherë për secilën kërkesë nga klienti N kërkesa për memcached në të vërtetë do të ekzekutohen - në të gjithë serverët në pishinë. Ne mund të zvogëlojmë numrin e serverëve në pishina, për shembull, në dy: duke sakrifikuar besueshmërinë e ruajtjes, marrimоshpejtësi më e lartë dhe më pak ngarkesë nga kërkesat për çelësat që mungojnë.

NB: Mund të gjeni gjithashtu lidhje të dobishme për të mësuar mcrouter dokumentacion në wiki и çështjet e projektit (përfshirë ato të mbyllura), që përfaqësojnë një depo të tërë me konfigurime të ndryshme.

Ndërtimi dhe funksionimi i mcrouter-it

Aplikacioni ynë (dhe vetë memcached) funksionon në Kubernetes - në përputhje me rrethanat, mcrouter ndodhet gjithashtu atje. Për montimi i kontejnerit ne përdorim werf, konfigurimi për të cilin do të duket kështu:

NB: Listimet e dhëna në artikull publikohen në depo 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: 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)

... dhe skicojeni atë Tabela e helmetit. Gjëja interesante është se ekziston vetëm një gjenerator konfigurimi bazuar në numrin e kopjeve (nëse dikush ka një opsion më lakonik dhe elegant, ndaje atë në komente):

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

Ne e hapim atë në mjedisin e testimit dhe 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 i tekstit të gabimit nuk dha asnjë rezultat, por për pyetjen "mcrouter php"Në ballë ishte problemi më i vjetër i pazgjidhur i projektit - mungesa e mbështetjes protokoll binar memcached.

NB: Protokolli ASCII në memcached është më i ngadalshëm se ai binar dhe mjetet standarde të hashimit të qëndrueshëm të çelësave funksionojnë vetëm me protokollin binar. Por kjo nuk krijon probleme për një rast specifik.

Truku është në çantë: gjithçka që duhet të bëni është të kaloni në protokollin ASCII dhe gjithçka do të funksionojë.... Sidoqoftë, në këtë rast, zakoni për të kërkuar përgjigje në dokumentacion në php.net luajti një shaka mizore. Nuk do të gjeni përgjigjen e saktë atje... nëse, sigurisht, nuk lëvizni deri në fund, ku në seksion "Shënime të kontribuuara nga përdoruesi" do të jetë besnik dhe përgjigje e kundërshtuar në mënyrë të padrejtë.

Po, emri i saktë i opsionit është memcached.sess_binary_protocol. Duhet të çaktivizohet, pas së cilës seancat do të fillojnë të funksionojnë. Gjithçka që mbetet është të vendosni kontejnerin me mcrouter në një pod me PHP!

Përfundim

Kështu, vetëm me ndryshime infrastrukturore ne arritëm ta zgjidhnim problemin: problemi me tolerancën e gabimeve memcached është zgjidhur dhe besueshmëria e ruajtjes së cache është rritur. Përveç avantazheve të dukshme për aplikacionin, kjo i dha hapësirë ​​për manovrim kur punoni në platformë: kur të gjithë komponentët kanë një rezervë, jeta e administratorit thjeshtohet shumë. Po, kjo metodë ka gjithashtu të metat e saj, mund të duket si një "paterica", por nëse kursen para, varros problemin dhe nuk shkakton të reja - pse jo?

PS

Lexoni edhe në blogun tonë:

Burimi: www.habr.com

Shto një koment