Օգտագործելով mcrouter՝ memcached հորիզոնական մասշտաբով

Օգտագործելով mcrouter՝ memcached հորիզոնական մասշտաբով

Ցանկացած լեզվով մեծ ծանրաբեռնված նախագծերի մշակումը պահանջում է հատուկ մոտեցում և հատուկ գործիքների կիրառում, բայց երբ խոսքը վերաբերում է PHP-ում հավելվածներին, իրավիճակն այնքան կարող է սրվել, որ պետք է զարգացնել, օրինակ. սեփական հավելվածի սերվեր. Այս գրառման մեջ մենք կխոսենք բաշխված նիստերի պահպանման և տվյալների քեշավորման հետ կապված ծանոթ ցավի մասին memcached-ում, և թե ինչպես մենք լուծեցինք այդ խնդիրները մեկ «բաժնի» նախագծում:

Միջոցառման հերոսը PHP հավելվածն է, որը հիմնված է symfony 2.3 շրջանակի վրա, որն ընդհանրապես ներառված չէ թարմացման բիզնես ծրագրերում։ Ի լրումն բավականին ստանդարտ նիստերի պահեստավորման, այս նախագիծը լիովին օգտագործեց «ամեն ինչ պահելու» քաղաքականություն memcached-ում. տվյալների բազայի և API սերվերների հարցումների պատասխաններ, տարբեր դրոշներ, կոդի կատարման համաժամացման կողպեքներ և շատ ավելին: Նման իրավիճակում memcached-ի խափանումը մահացու է դառնում հավելվածի աշխատանքի համար: Բացի այդ, քեշի կորուստը հանգեցնում է լուրջ հետևանքների. DBMS-ը սկսում է պայթել, API ծառայությունները սկսում են արգելել հարցումները և այլն: Իրավիճակի կայունացումը կարող է տևել տասնյակ րոպեներ, և այս ընթացքում ծառայությունը սարսափելի դանդաղ կամ ամբողջովին անհասանելի կլինի։

Պետք էր ապահովել փոքր ջանքերով հավելվածը հորիզոնական մասշտաբավորելու հնարավորությունը, այսինքն. սկզբնական կոդի նվազագույն փոփոխություններով և ամբողջական ֆունկցիոնալությամբ պահպանված: Դարձրեք քեշը ոչ միայն անհաջողությունների նկատմամբ, այլև փորձեք նվազագույնի հասցնել տվյալների կորուստը դրանից:

Ի՞նչն է սխալ ինքնին memcached-ի հետ:

Ընդհանուր առմամբ, PHP-ի համար memcached ընդլայնումն աջակցում է բաշխված տվյալների և նիստերի պահեստավորմանը տուփից դուրս: Բանալների հետևողական հեշինգի մեխանիզմը թույլ է տալիս հավասարաչափ տեղադրել տվյալները բազմաթիվ սերվերների վրա՝ յուրօրինակ կերպով ուղղելով յուրաքանչյուր հատուկ բանալի խմբի որոշակի սերվերին, իսկ ներկառուցված ձախողման գործիքներն ապահովում են քեշավորման ծառայության բարձր հասանելիություն (բայց, ցավոք, ոչ մի տվյալ).

Գործերը մի փոքր ավելի լավ են նստաշրջանի պահեստավորման դեպքում. կարող եք կարգավորել memcached.sess_number_of_replicas, որի արդյունքում տվյալները կպահվեն միանգամից մի քանի սերվերների վրա, իսկ մեկ memcached օրինակի ձախողման դեպքում տվյալները կփոխանցվեն մյուսներից։ Այնուամենայնիվ, եթե սերվերը վերադառնա առցանց առանց տվյալների (ինչպես սովորաբար տեղի է ունենում վերագործարկումից հետո), որոշ բանալիներ կվերաբաշխվեն նրա օգտին: Իրականում սա կնշանակի նստաշրջանի տվյալների կորուստ, քանի որ բաց թողնելու դեպքում այլ կրկնօրինակի «գնալու» հնարավորություն չկա։

Ստանդարտ գրադարանային գործիքները հիմնականում ուղղված են հորիզոնական scaling. դրանք թույլ են տալիս մեծացնել քեշը հսկայական չափերի և մուտք գործել դրան տարբեր սերվերների վրա տեղակայված կոդից: Այնուամենայնիվ, մեր իրավիճակում պահվող տվյալների ծավալը չի ​​գերազանցում մի քանի գիգաբայթը, և մեկ կամ երկու հանգույցների կատարումը միանգամայն բավարար է: Համապատասխանաբար, միակ օգտակար ստանդարտ գործիքները կարող են լինել memcached-ի հասանելիությունն ապահովելը` միաժամանակ պահպանելով առնվազն մեկ քեշի օրինակ աշխատանքային վիճակում: Սակայն նույնիսկ այս հնարավորությունից հնարավոր չեղավ օգտվել... Այստեղ արժե հիշել նախագծում օգտագործված ֆրեյմքի հնությունը, ինչի պատճառով էլ անհնար էր հավելվածը սերվերների լողավազանի հետ աշխատել։ Չմոռանանք նաև սեսիայի տվյալների կորստի մասին. հաճախորդի աչքը ցնցվեց օգտատերերի զանգվածային ելքից:

Իդեալում դա պահանջվում էր գրառումների կրկնօրինակում memcached և շրջանցող կրկնօրինակներում սխալի կամ սխալի դեպքում. Օգնեց մեզ իրականացնել այս ռազմավարությունը մակրոուտեր.

մակրոուտեր

Սա memcached երթուղիչ է, որը մշակվել է Facebook-ի կողմից իր խնդիրները լուծելու համար: Այն աջակցում է memcached տեքստային արձանագրությանը, որը թույլ է տալիս մասշտաբային memcached տեղադրումներ խելագար չափերի. Մակրոուտերի մանրամասն նկարագրությունը կարելի է գտնել այստեղ այս հայտարարությունը. Ի թիվս այլ բաների լայն ֆունկցիոնալություն այն կարող է անել այն, ինչ մեզ անհրաժեշտ է՝

  • կրկնօրինակ գրառում;
  • սխալի դեպքում հետադարձ կապ կատարեք խմբի մյուս սերվերներին:

Անցիր գործի!

mcrouter կոնֆիգուրացիա

Ես անմիջապես կանցնեմ կազմաձևին.

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

Ինչու՞ երեք լողավազան: Ինչու են սերվերները կրկնվում: Եկեք պարզենք, թե ինչպես է այն աշխատում:

  • Այս կոնֆիգուրացիայում mcrouter-ը ընտրում է այն ուղին, որով հարցումը կուղարկվի՝ հիմնվելով հարցման հրամանի վրա: Տղան նրան ասում է սա OperationSelectorRoute.
  • GET հարցումները գնում են մշակողին RandomRouteորը պատահականորեն ընտրում է լողավազան կամ երթուղի զանգվածի օբյեկտների միջև children. Այս զանգվածի յուրաքանչյուր տարր իր հերթին մշակող է MissFailoverRoute, որը կանցնի լողավազանի յուրաքանչյուր սերվերի միջով, մինչև այն ստանա պատասխան տվյալների հետ, որը կվերադարձվի հաճախորդին։
  • Եթե ​​օգտագործեինք բացառապես MissFailoverRoute երեք սերվերների լողավազանով, այնուհետև բոլոր հարցումները կգան առաջինը memcached օրինակին, իսկ մնացածները կստանան հարցումներ մնացորդային հիմունքներով, երբ տվյալներ չկան: Նման մոտեցումը կհանգեցներ Չափազանց ծանրաբեռնվածություն ցուցակի առաջին սերվերի վրա, ուստի որոշվեց գեներացնել երեք լողավազան՝ տարբեր հաջորդականությամբ հասցեներով և ընտրել դրանք պատահականորեն։
  • Բոլոր մյուս հարցումները (և սա ռեկորդ է) մշակվում են՝ օգտագործելով AllMajorityRoute. Այս մշակողը հարցումներ է ուղարկում լողավազանի բոլոր սերվերներին և սպասում դրանցից առնվազն N/2 + 1-ի պատասխաններին: Օգտագործումից AllSyncRoute գրելու գործողություններից պետք է հրաժարվել, քանի որ այս մեթոդը պահանջում է դրական պատասխան Ամբողջ սերվերներ խմբում, հակառակ դեպքում այն ​​կվերադառնա SERVER_ERROR. Չնայած mcrouter-ը կավելացնի տվյալները հասանելի քեշերին, զանգահարող PHP ֆունկցիան կվերադարձնի սխալ և կստեղծի ծանուցում: AllMajorityRoute այնքան էլ խիստ չէ և թույլ է տալիս ագրեգատների մինչև կեսը հանել ծառայությունից՝ առանց վերը նկարագրված խնդիրների։

Հիմնական թերությունը Այս սխեման այն է, որ եթե քեշում իսկապես տվյալներ չկան, ապա հաճախորդի յուրաքանչյուր հարցման համար իրականում կկատարվի N հարցումներ memcached-ին. ամբողջ սերվերներ լողավազանում: Մենք կարող ենք կրճատել սերվերների թիվը լողավազաններում, օրինակ, երկուսի. զոհաբերելով պահեստավորման հուսալիությունը, մենք ստանում ենք.оավելի բարձր արագություն և ավելի քիչ բեռ՝ պահանջներից մինչև բացակայող ստեղներ:

NBԿարող եք նաև գտնել օգտակար հղումներ mcrouter սովորելու համար փաստաթղթեր վիքիում и նախագծի խնդիրները (ներառյալ փակները), որը ներկայացնում է տարբեր կոնֆիգուրացիաների մի ամբողջ պահեստ:

Մակրոուտերի կառուցում և գործարկում

Մեր հավելվածը (և ինքն իրեն memcached) աշխատում է Kubernetes-ում, համապատասխանաբար, այնտեղ է գտնվում նաև mcrouter-ը: Համար կոնտեյների հավաքում մենք օգտագործում ենք վերֆ, որի կազմաձևը կունենա հետևյալ տեսքը.

NBՀոդվածում տրված ցանկերը հրապարակված են պահոցում 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)

... և ուրվագծեք այն Սաղավարտի աղյուսակ. Հետաքրքիրն այն է, որ գոյություն ունի միայն կոնֆիգուրացիայի գեներատոր՝ հիմնված կրկնօրինակների քանակի վրա (եթե որևէ մեկն ունի ավելի լակոնիկ և էլեգանտ տարբերակ, կիսվեք մեկնաբանություններում):

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

Մենք այն գլորում ենք թեստային միջավայր և ստուգում.

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

Սխալի տեքստի որոնումը ոչ մի արդյունք չտվեց, այլ հարցման «mcrouter php«Առաջին պլանում եղել է նախագծի ամենահին չլուծված խնդիրը. աջակցության բացակայություն memcached երկուական արձանագրություն.

NBASCII արձանագրությունը memcached-ում ավելի դանդաղ է, քան երկուականը, և ստեղների հետևողական հեշավորման ստանդարտ միջոցներն աշխատում են միայն երկուական արձանագրության հետ: Բայց սա կոնկրետ դեպքի համար խնդիրներ չի ստեղծում։

Հնարքը պայուսակի մեջ է՝ ընդամենը պետք է անցնել ASCII արձանագրությանը, և ամեն ինչ կաշխատի... Այնուամենայնիվ, այս դեպքում պատասխաններ փնտրելու սովորությունը փաստաթղթեր php.net-ում դաժան կատակ խաղաց. Այնտեղ ճիշտ պատասխանը չես գտնի... եթե, իհարկե, չոլորես մինչև վերջ, որտեղ նշված է բաժնում «Օգտատիրոջ կողմից ներկայացված նշումներ» հավատարիմ կլինի և անարդարացիորեն բացասական պատասխան.

Այո, ճիշտ տարբերակի անվանումն է memcached.sess_binary_protocol. Այն պետք է անջատվի, որից հետո նիստերը կսկսեն աշխատել։ Մնում է միայն mcrouter-ով բեռնարկղը դնել PHP-ով պատիճ:

Ամփոփում

Այսպիսով, պարզապես ենթակառուցվածքային փոփոխություններով մենք կարողացանք լուծել խնդիրը. լուծվել է memcached fault tolerance-ի խնդիրը, և մեծացել է քեշի պահպանման հուսալիությունը: Հավելվածի համար ակնհայտ առավելություններից բացի, սա հարթակի վրա աշխատելիս մանևրելու տեղ էր տալիս. երբ բոլոր բաղադրիչներն ունեն ռեզերվ, ադմինիստրատորի կյանքը զգալիորեն պարզեցվում է: Այո, այս մեթոդն ունի նաև իր թերությունները, այն կարող է նմանվել «կռունկի», բայց եթե այն խնայում է գումար, թաղում է խնդիրը և չի առաջացնում նորեր, ինչու ոչ:

PS

Կարդացեք նաև մեր բլոգում.

Source: www.habr.com

Добавить комментарий