Коришћење мцроутер-а за хоризонтално скалирање мемцацхед-а

Коришћење мцроутер-а за хоризонтално скалирање мемцацхед-а

Развој пројеката високог оптерећења на било ком језику захтева посебан приступ и употребу специјалних алата, али када је реч о апликацијама у ПХП-у, ситуација се може толико погоршати да морате да развијете нпр. сопствени сервер апликација. У овој белешци ћемо говорити о познатом болу са складиштењем дистрибуираних сесија и кеширањем података у мемцацхед-у и како смо решили ове проблеме у једном пројекту „одељења“.

Јунак ове прилике је ПХП апликација заснована на симфони 2.3 фрамеворку, која уопште није укључена у пословне планове за ажурирање. Поред сасвим стандардног складиштења сесија, овај пројекат је у потпуности искористио политика „кеширања свега“. у мемцацхед-у: одговори на захтеве бази података и АПИ серверима, разне заставице, браве за синхронизовање извршавања кода и још много тога. У таквој ситуацији, квар мемцацхед-а постаје фаталан за рад апликације. Поред тога, губитак кеша доводи до озбиљних последица: ДБМС почиње да пуца по шавовима, АПИ сервиси почињу да забрањују захтеве итд. Стабилизација ситуације може потрајати десетине минута, а за то време услуга ће бити ужасно спора или потпуно недоступна.

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

Шта није у реду са самим мемцацхедом?

Генерално, екстензија мемцацхед за ПХП подржава дистрибуиране податке и складиштење сесија без употребе. Механизам за доследно хеширање кључева вам омогућава да равномерно поставите податке на многе сервере, јединствено адресирајући сваки одређени кључ на одређени сервер из групе, а уграђени алати за прелазак на грешку обезбеђују високу доступност услуге кеширања (али, нажалост, нема података).

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

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

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

мцроутер

Ово је мемцацхед рутер који је развио Фацебоок да би решио своје проблеме. Подржава мемцацхед текстуални протокол, који дозвољава сцале мемцацхед инсталације до лудих размера. Детаљан опис мцроутера можете пронаћи у ово саопштење. Између осталог широка функционалност може да уради оно што нам треба:

  • реплицирати запис;
  • вратите се на друге сервере у групи ако дође до грешке.

Да пређемо на посао!

мцроутер конфигурација

Идем право на конфигурацију:

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

Зашто три базена? Зашто се сервери понављају? Хајде да схватимо како то функционише.

  • У овој конфигурацији, мцроутер бира путању на коју ће бити послат захтев на основу команде рекуест. Момак му ово каже OperationSelectorRoute.
  • ГЕТ захтеви иду руковаоцу RandomRouteкоји насумично бира скуп или руту међу објектима низа children. Сваки елемент овог низа је заузврат руковалац MissFailoverRoute, који ће пролазити кроз сваки сервер у пулу све док не добије одговор са подацима, који ће бити враћен клијенту.
  • Ако бисмо користили искључиво MissFailoverRoute са скупом од три сервера, онда би сви захтеви прво долазили до прве мемкеширане инстанце, а остали би примали захтеве на резидуалној основи када нема података. Такав приступ би довео до прекомерно оптерећење на првом серверу на листи, па је одлучено да се генеришу три скупа са адресама у различитим секвенцама и да се бирају насумично.
  • Сви остали захтеви (а ово је запис) се обрађују помоћу AllMajorityRoute. Овај обрађивач шаље захтеве свим серверима у групи и чека одговоре од најмање Н/2 + 1 њих. Од употребе AllSyncRoute јер су операције писања морале бити напуштене, пошто овај метод захтева позитиван одговор од све сервере у групи - иначе ће се вратити SERVER_ERROR. Иако ће мцроутер додати податке у доступне кеш меморије, позива ПХП функцију вратиће грешку и генерисаће обавештење. AllMajorityRoute није тако строг и дозвољава да се до половине јединица повуче из употребе без горе описаних проблема.

Главни недостатак Ова шема је да ако заиста нема података у кешу, онда ће се за сваки захтев клијента Н захтева за мемцацхед стварно извршити - до свима сервери у базену. Можемо смањити број сервера у групама, на пример, на два: жртвујући поузданост складиштења, добијамоовећа брзина и мање оптерећење од захтева до кључева који недостају.

NB: Такође можете пронаћи корисне везе за учење мцроутер-а документацију на вики и питања пројекта (укључујући затворене), који представљају читаво складиште различитих конфигурација.

Прављење и покретање мцроутера

Наша апликација (и сам мемцацхед) ради у Кубернетесу - сходно томе, мцроутер се такође налази тамо. За монтажа контејнера користимо верф, конфигурација за коју ће изгледати овако:

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' ]

(верф.иамл)

... и скицирај га Хелм цхарт. Занимљиво је да постоји само генератор конфигурације заснован на броју реплика (ако неко има лаконичнију и елегантнију опцију, поделите је у коментарима):

{{- $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-мцроутер.иамл)

Убацујемо га у тестно окружење и проверавамо:

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

Претраживање текста грешке није дало никакве резултате, али за упит „мицроутер пхп„У првом плану је био најстарији нерешени проблем пројекта – недостатак подршке мемцацхед бинарни протокол.

NB: АСЦИИ протокол у мемцацхед-у је спорији од бинарног, а стандардна средства конзистентног хеширања кључа раде само са бинарним протоколом. Али то не ствара проблеме за конкретан случај.

Трик је у торби: све што треба да урадите је да пређете на АСЦИИ протокол и све ће функционисати... Међутим, у овом случају, навика тражења одговора у документацију на пхп.нет одиграо окрутну шалу. Тамо нећете наћи тачан одговор... осим ако, наравно, не скролујете до краја, где у одељку „Белешке које су додали корисници“ биће веран и неправедно одбијен одговор.

Да, тачан назив опције је memcached.sess_binary_protocol. Мора бити онемогућен, након чега ће сесије почети да раде. Остаје само да се контејнер са мцроутер-ом стави у под са ПХП-ом!

Закључак

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

ПС

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

Извор: ввв.хабр.цом

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