Користење на mcrouter за скалирање на мемкешираниот хоризонтално

Користење на mcrouter за скалирање на мемкешираниот хоризонтално

Развивањето проекти со големо оптоварување на кој било јазик бара посебен пристап и употреба на специјални алатки, но кога станува збор за апликации во PHP, ситуацијата може да стане толку отежната што треба да развиете, на пример, сопствен сервер за апликации. Во оваа белешка ќе зборуваме за познатата болка со дистрибуираното складирање на сесии и кеширање на податоци во мемкешираната меморија и како ги решивме овие проблеми во еден проект „оддел“.

Херојот на приликата е PHP апликација базирана на рамката symfony 2.3, која воопшто не е вклучена во деловните планови за ажурирање. Покрај сосема стандардното складирање на сесии, овој проект целосно го искористи политика за „кеширање на сè“. во memcached: одговори на барања до базата на податоци и сервери API, разни знаменца, брави за синхронизирање на извршување на кодот и многу повеќе. Во таква ситуација, дефект на memcached станува фатален за работата на апликацијата. Покрај тоа, губењето на кешот води до сериозни последици: DBMS почнува да пука во рабовите, API услугите почнуваат да забрануваат барања, итн. Стабилизирањето на ситуацијата може да потрае десетици минути, а за тоа време услугата ќе биде ужасно бавна или целосно недостапна.

Требаше да обезбедиме можност за хоризонтално размерување на апликацијата со мал напор, т.е. со минимални промени во изворниот код и зачувана целосна функционалност. Направете го кешот не само отпорен на неуспеси, туку и обидете се да ја минимизирате загубата на податоци од неа.

Што не е во ред со самиот memcached?

Општо земено, мемкешираната екстензија за PHP поддржува дистрибуирани податоци и складирање на сесии надвор од кутијата. Механизмот за доследно хаширање на клучеви ви овозможува рамномерно да поставувате податоци на многу сервери, уникатно адресирање на секој специфичен клуч до одреден сервер од групата, а вградените алатки за фајловер обезбедуваат висока достапност на услугата за кеширање (но, за жал, нема податок).

Работите се малку подобри со складирањето на сесиите: можете да конфигурирате memcached.sess_number_of_replicas, како резултат на што податоците ќе се складираат на неколку сервери одеднаш, а во случај на неуспех на еден мемкеширан пример, податоците ќе бидат префрлени од други. Меѓутоа, ако серверот се врати онлајн без податоци (како што обично се случува по рестартирање), некои од клучевите ќе бидат прераспределени во негова корист. Всушност, ова ќе значи губење на податоците од сесијата, бидејќи не постои начин да се „оди“ на друга реплика во случај на промашување.

Стандардните библиотечни алатки се насочени главно кон хоризонтална скалирање: тие ви дозволуваат да го зголемите кешот до огромни големини и да обезбедите пристап до него од кодот хостиран на различни сервери. Меѓутоа, во нашата ситуација, обемот на складирани податоци не надминува неколку гигабајти, а перформансите на еден или два јазли се сосема доволни. Соодветно на тоа, единствените корисни стандардни алатки би можеле да бидат да се обезбеди достапност на мемкешираната меморија додека се одржува барем една кеш инстанца во работна состојба. Сепак, не беше можно да се искористи ниту оваа можност... Овде вреди да се потсетиме на антиката на рамката користена во проектот, поради што беше невозможно апликацијата да работи со базен од сервери. Да не заборавиме и на губењето на податоците од сесиите: окото на клиентот се грчеше од масовното одјавување на корисниците.

Идеално беше потребно репликација на записи во мемкеширани и заобиколувајќи реплики во случај на грешка или грешка. Ни помогна да ја спроведеме оваа стратегија макроутер.

макроутер

Ова е мемкеширан рутер развиен од 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

Нашата апликација (и самата мемкеширана) работи во Кубернетес - соодветно, таму се наоѓа и 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 >

Пребарувањето на текстот на грешката не даде резултати, туку барањето „mcrouter php„Во прв план беше најстариот нерешен проблем на проектот - недостаток на поддршка мемкеширан бинарен протокол.

NB: ASCII протоколот во memcached е побавен од бинарниот, а стандардните средства за доследно хеширање на клучеви работат само со бинарниот протокол. Но, тоа не создава проблеми за конкретен случај.

Трикот е во торбата: сè што треба да направите е да се префрлите на протоколот ASCII и сè ќе работи.... Меѓутоа, во овој случај, навиката да барате одговори во документација на php.net играше сурова шега. Таму нема да го најдете точниот одговор... освен ако, се разбира, не дојдете до крајот, каде што е во делот „Забелешки дадени од корисникот“ ќе биде верен и неправедно противгласен одговор.

Да, точното име на опцијата е memcached.sess_binary_protocol. Мора да се оневозможи, по што сесиите ќе почнат да работат. Останува само да се стави контејнерот со mcrouter во pod со PHP!

Заклучок

Така, со само инфраструктурни промени успеавме да го решиме проблемот: проблемот со мемкешираната толеранција на грешки е решен, а доверливоста на складирањето на кешот е зголемена. Покрај очигледните предности за апликацијата, ова даде простор за маневрирање при работа на платформата: кога сите компоненти имаат резерва, животот на администраторот е значително поедноставен. Да, овој метод има и свои недостатоци, може да изгледа како „патерица“, но ако заштедува пари, го закопа проблемот и не предизвикува нови - зошто да не?

PS

Прочитајте и на нашиот блог:

Извор: www.habr.com

Додадете коментар