Жад-кэшті көлденеңінен масштабтау үшін mcrouter пайдалану

Жад-кэшті көлденеңінен масштабтау үшін mcrouter пайдалану

Кез келген тілде жоғары жүктемелі жобаларды әзірлеу ерекше тәсілді және арнайы құралдарды пайдалануды талап етеді, бірақ PHP-дегі қолданбаларға келетін болсақ, жағдай соншалықты қиындауы мүмкін, мысалы, әзірлеуге тура келеді. жеке қолданба сервері. Бұл жазбада біз таратылған сеансты сақтау және memcached-де деректерді кэштеу кезіндегі таныс ауырсыну және біз бұл мәселелерді бір «палаталық» жобада қалай шешкеніміз туралы сөйлесетін боламыз.

Іс-шараның кейіпкері - symfony 2.3 негізіне негізделген PHP қосымшасы, ол жаңартуға арналған бизнес-жоспарларға мүлдем кірмейді. Стандартты сеансты сақтаудан басқа, бұл жоба толық пайдаланылды «бәрін кэштеу» саясаты memcached: дерекқорға және API серверлеріне сұрауларға жауаптар, әртүрлі жалаушалар, кодты орындауды синхрондау құлыптары және т.б. Мұндай жағдайда жад кэшінің бұзылуы қолданбаның жұмысы үшін өлімге әкеледі. Сонымен қатар, кэш жоғалуы ауыр зардаптарға әкеледі: ДҚБЖ тігістерде жарылып кете бастайды, API қызметтері сұрауларға тыйым сала бастайды және т.б. Жағдайды тұрақтандыруға ондаған минут кетуі мүмкін және осы уақыт ішінде қызмет өте баяу немесе мүлдем қолжетімсіз болады.

қамтамасыз етуіміз керек еді қолданбаны аз күш жұмсай отырып, көлденең масштабтау мүмкіндігі, яғни. бастапқы кодқа ең аз өзгерістермен және толық функционалдылық сақталған. Кэшті сәтсіздіктерге төзімді етіп қана қоймай, одан деректердің жоғалуын азайтуға тырысыңыз.

Memcached өзіне не болды?

Жалпы алғанда, PHP үшін memcached кеңейтімі таратылған деректерді және қораптан тыс сеансты сақтауды қолдайды. Кілттерді тұрақты хэштеу механизмі топтағы белгілі бір серверге әрбір нақты кілтті бірегей түрде бағыттай отырып, деректерді көптеген серверлерге біркелкі орналастыруға мүмкіндік береді және кірістірілген ауыстырып қосу құралдары кэштеу қызметінің жоғары қолжетімділігін қамтамасыз етеді (бірақ, өкінішке орай, деректер жоқ).

Сеансты сақтаумен жұмыс сәл жақсырақ: конфигурациялауға болады memcached.sess_number_of_replicas, нәтижесінде деректер бірден бірнеше серверлерде сақталады және бір жад кэштелген данасы істен шыққан жағдайда деректер басқалардан тасымалданады. Дегенмен, егер сервер деректерсіз желіге оралса (әдетте қайта іске қосқаннан кейін орын алатындай), кейбір кілттер оның пайдасына қайта таратылады. Іс жүзінде бұл білдіреді сеанс деректерінің жоғалуы, өйткені жіберіп алған жағдайда басқа көшірмеге «баруға» мүмкіндік жоқ.

Стандартты кітапхана құралдары негізінен бағытталған көлденең масштабтау: олар кэшті үлкен өлшемдерге дейін ұлғайтуға және оған әртүрлі серверлерде орналасқан кодтан қол жеткізуге мүмкіндік береді. Дегенмен, біздің жағдайымызда сақталған деректердің көлемі бірнеше гигабайттан аспайды, ал бір немесе екі түйіннің өнімділігі жеткілікті. Тиісінше, жұмыс жағдайында кем дегенде бір кэш данасын сақтай отырып, жад кэшінің қолжетімділігін қамтамасыз ету жалғыз пайдалы стандартты құралдар болуы мүмкін. Дегенмен, тіпті бұл мүмкіндікті пайдалану мүмкін болмады... Бұл жерде жобада қолданылған фреймворктің көнелігін еске түсірген жөн, сол себепті қосымшаны серверлер пулымен жұмыс істеуге алу мүмкін болмады. Сеанс деректерінің жоғалуын да ұмытпайық: пайдаланушылардың жаппай шығуынан тұтынушының көзі жыпылықтады.

Ең дұрысы бұл қажет болды жад-кэштелген және айналып өтетін репликалардағы жазбаларды репликациялау қате немесе қате болған жағдайда. Бұл стратегияны жүзеге асыруға көмектесті mcrouter.

mcrouter

Бұл Facebook өз мәселелерін шешу үшін әзірлеген жад-кэштелген маршрутизатор. Ол жад-кэштелген мәтіндік протоколды қолдайды, бұл мүмкіндік береді жад-кэштелген қондырғыларды масштабтау ақылсыз пропорцияларға. mcrouter-тің толық сипаттамасын мына жерден табуға болады бұл хабарландыру. Басқа нәрселермен қатар кең функционалдылық ол бізге қажет нәрсені жасай алады:

  • жазбаны қайталау;
  • қате орын алса, топтағы басқа серверлерге қайта оралыңыз.

Бизнеске кірісіңіз!

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 үш сервер пулы бар болса, барлық сұраулар бірінші жад-кэштелген данаға келеді, ал қалғандары деректер болмаған кезде қалдық негізде сұрауларды қабылдайды. Мұндай көзқарас әкеледі тізімдегі бірінші серверге шамадан тыс жүктеме, сондықтан әртүрлі реттіліктегі мекенжайлары бар үш пулды құру және оларды кездейсоқ таңдау туралы шешім қабылданды.
  • Барлық басқа сұраулар (және бұл жазба) пайдалану арқылы өңделеді AllMajorityRoute. Бұл өңдеуші пулдағы барлық серверлерге сұрау жібереді және олардың кем дегенде N/2 + 1 жауаптарын күтеді. Қолданудан AllSyncRoute жазу әрекеттерінен бас тартуға тура келді, өйткені бұл әдіс оң жауап беруді талап етеді всех топтағы серверлер - әйтпесе ол қайтарылады SERVER_ERROR. mcrouter деректерді қолжетімді кэштерге қосатынына қарамастан, қоңырау шалушы PHP функциясы қатені қайтарады және хабарлама жасайды. AllMajorityRoute соншалықты қатаң емес және жоғарыда сипатталған проблемаларсыз қондырғылардың жартысына дейін жұмыстан шығаруға мүмкіндік береді.

Негізгі кемшілігі Бұл схема, егер шын мәнінде кэште деректер болмаса, клиенттің әрбір сұрауы үшін N memcached сұрауы іс жүзінде орындалады - бәріне бассейндегі серверлер. Біз бассейндердегі серверлердің санын екіге дейін азайта аламыз: сақтау сенімділігін жоғалтып, біз аламызожоғары жылдамдық және сұраныстардан жетіспейтін кілттерге аз жүктеме.

NB: Сондай-ақ mcrouter үйренуге арналған пайдалы сілтемелерді таба аласыз Викидегі құжаттама и жоба мәселелері (соның ішінде жабық), әртүрлі конфигурациялардың тұтас қоймасын білдіреді.

mcrouter құру және іске қосу

Біздің қосымшамыз (және memcached өзі) Kubernetes-те жұмыс істейді - сәйкесінше, mcrouter де сонда орналасқан. Үшін контейнер құрастыру Біз қолданамыз верф, конфигурациясы келесідей болады:

NB: Мақалада келтірілген тізімдер репозиторийде жарияланған флант/микроутер.

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 >

Қате мәтінін іздеу нәтиже бермеді, бірақ «microuter php«Алдыңғы қатарда жобаның ең көне шешілмеген мәселесі болды - қолдаудың болмауы memcached екілік протокол.

NB: Memcached ішіндегі ASCII протоколы екілік протоколға қарағанда баяуырақ және тұрақты кілт хэштеуінің стандартты құралдары тек екілік протоколмен жұмыс істейді. Бірақ бұл нақты жағдай үшін қиындық тудырмайды.

Айла сөмкеде: тек ASCII протоколына ауыссаңыз болғаны, бәрі жұмыс істейді.... Дегенмен, бұл жағдайда жауап іздеу әдеті php.net сайтындағы құжаттама қатыгез әзіл ойнады. Сіз ол жерден дұрыс жауапты таба алмайсыз... егер, әрине, бөлімнің соңына дейін жылжып кетпесеңіз «Пайдаланушы қосқан жазбалар» адал болады және әділетсіз төмен дауыс берген жауап.

Иә, дұрыс опция атауы memcached.sess_binary_protocol. Оны өшіру керек, содан кейін сеанстар жұмыс істей бастайды. Тек mcrouter бар контейнерді PHP бар подводқа салу ғана қалды!

қорытынды

Осылайша, жай ғана инфрақұрылымдық өзгерістер арқылы біз мәселені шеше алдық: жад-кэштелген ақауларға төзімділік мәселесі шешілді және кэш сақтаудың сенімділігі артты. Қолданбаның айқын артықшылықтарынан басқа, бұл платформада жұмыс істеу кезінде маневр жасау мүмкіндігін берді: барлық компоненттердің резерві болған кезде, әкімшінің өмірі айтарлықтай жеңілдетілді. Иә, бұл әдістің де кемшіліктері бар, ол «балдақ» сияқты көрінуі мүмкін, бірақ егер ол ақшаны үнемдеп, мәселені көміп, жаңасын тудырмаса - неге жоқ?

PS

Біздің блогта да оқыңыз:

Ақпарат көзі: www.habr.com

пікір қалдыру