„Mcrouter“ naudojimas horizontaliai atmintinės talpyklos masteliui nustatyti

„Mcrouter“ naudojimas horizontaliai atmintinės talpyklos masteliui nustatyti

Kuriant didelės apkrovos projektus bet kuria kalba reikia specialaus požiūrio ir specialių įrankių naudojimo, tačiau kalbant apie programas PHP, padėtis gali taip pablogėti, kad tenka kurti, pvz. savo programų serverį. Šioje pastaboje kalbėsime apie pažįstamą skausmą paskirstytoje seanso saugykloje ir duomenų talpykloje „memcached“ ir kaip mes išsprendėme šias problemas viename „palatos“ projekte.

Šios progos herojus – PHP programa, pagrįsta symfony 2.3 sistema, kuri visiškai neįtraukta į verslo planus atnaujinti. Be gana standartinės seansų saugyklos, šis projektas visiškai išnaudojo „viską saugoti talpykloje“ politika „memcached“: atsakymai į duomenų bazės ir API serverių užklausas, įvairios vėliavėlės, kodo vykdymo sinchronizavimo užraktai ir daug daugiau. Esant tokiai situacijai, „memcached“ gedimas tampa lemtingas programos veikimui. Be to, talpyklos praradimas sukelia rimtų pasekmių: DBVS pradeda sprogti, API paslaugos pradeda uždrausti užklausas ir pan. Situacijos stabilizavimas gali užtrukti keliasdešimt minučių, o per tą laiką paslauga bus siaubingai lėta arba visiškai nepasiekiama.

Mums reikėjo aprūpinti galimybė be pastangų horizontaliai keisti programos mastelį, t.y. su minimaliais šaltinio kodo pakeitimais ir išsaugomas visas funkcionalumas. Padarykite talpyklą ne tik atsparią gedimams, bet ir stenkitės kuo labiau sumažinti duomenų praradimą.

Kas negerai su memcached?

Apskritai, atmintyje išsaugotas PHP plėtinys palaiko paskirstytus duomenis ir seansų saugyklą. Nuoseklios raktų maišos mechanizmas leidžia tolygiai talpinti duomenis daugelyje serverių, unikaliai adresuojant kiekvieną konkretų raktą į konkretų grupės serverį, o integruoti perkėlimo įrankiai užtikrina aukštą talpyklos paslaugos prieinamumą (bet, deja, nėra duomenų).

Su seansų saugykla viskas yra šiek tiek geriau: galite konfigūruoti memcached.sess_number_of_replicas, ko pasekoje duomenys bus saugomi keliuose serveriuose vienu metu, o sugedus vienam atmintyje įrašytam egzemplioriui, duomenys bus perkelti iš kitų. Tačiau jei serveris vėl prisijungs be duomenų (kaip paprastai atsitinka po paleidimo iš naujo), kai kurie raktai bus perskirstyti jo naudai. Tiesą sakant, tai reikš sesijos duomenų praradimas, nes praleidimo atveju nėra galimybės „eiti“ į kitą kopiją.

Standartinės bibliotekos priemonės daugiausia skirtos horizontaliai mastelio keitimas: jie leidžia padidinti talpyklą iki milžiniškų dydžių ir suteikti prieigą prie jos iš kodo, esančio skirtinguose serveriuose. Tačiau mūsų situacijoje saugomų duomenų kiekis neviršija kelių gigabaitų, o vieno ar dviejų mazgų našumo visiškai pakanka. Atitinkamai, vieninteliai naudingi standartiniai įrankiai galėtų būti atmintinės talpyklos prieinamumo užtikrinimas, išlaikant bent vieno talpyklos egzemplioriaus veikimo būklę. Tačiau net ir šia galimybe pasinaudoti nepavyko... Čia verta priminti projekte naudoto karkaso senumą, dėl ko nepavyko aplikacijos dirbti su serverių baseinu. Nepamirškime ir apie sesijos duomenų praradimą: kliento akys trūkčiojo nuo masinio vartotojų atsijungimo.

Idealiu atveju to reikėjo Įrašų replikavimas atmintinėje išsaugotose ir apeinančiose kopijose klaidos ar klaidos atveju. Padėjo mums įgyvendinti šią strategiją mcrouter.

mcrouter

Tai atmintyje išsaugotas maršrutizatorius, kurį „Facebook“ sukūrė savo problemoms išspręsti. Jis palaiko atmintyje įrašytą teksto protokolą, kuris leidžia mastelio atmintinės instaliacijos iki beprotiškų proporcijų. Išsamų mcrouter aprašymą galite rasti šis skelbimas. Be kita ko platus funkcionalumas tai gali padaryti tai, ko mums reikia:

  • pakartoti įrašą;
  • jei įvyktų klaida, grįžkite į kitus grupės serverius.

Į darbą!

mcrouter konfigūracija

Eisiu tiesiai į konfigūraciją:

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

Kodėl trys baseinai? Kodėl serveriai kartojasi? Išsiaiškinkime, kaip tai veikia.

  • Šioje konfigūracijoje mcrouter pasirenka kelią, kuriuo bus siunčiama užklausa, remdamasi užklausos komanda. Vaikinas jam tai sako OperationSelectorRoute.
  • GET užklausos siunčiamos tvarkytojui RandomRoutekuri atsitiktinai parenka telkinį arba maršrutą tarp masyvo objektų children. Kiekvienas šio masyvo elementas savo ruožtu yra tvarkytuvas MissFailoverRoute, kuris eis per kiekvieną telkinio serverį, kol gaus atsakymą su duomenimis, kurie bus grąžinti klientui.
  • Jei naudotume išskirtinai MissFailoverRoute su trijų serverių telkiniu, tada visos užklausos pirmiausia gautų pirmąjį atmintinėje išsaugotą egzempliorių, o likusieji gautų užklausas likutiniu pagrindu, kai nėra duomenų. Toks požiūris paskatintų per didelė pirmojo sąrašo serverio apkrova, todėl buvo nuspręsta sugeneruoti tris telkinius su adresais skirtingose ​​sekose ir pasirinkti juos atsitiktine tvarka.
  • Visos kitos užklausos (ir tai yra įrašas) apdorojamos naudojant AllMajorityRoute. Šis tvarkytuvas siunčia užklausas visiems telkinyje esantiems serveriams ir laukia atsakymų iš bent N/2 + 1 iš jų. Nuo naudojimo AllSyncRoute rašymo operacijų teko atsisakyti, nes šis metodas reikalauja teigiamo atsakymo iš visi serverių grupėje – kitaip grįš SERVER_ERROR. Nors mcrouter pridės duomenis prie turimų talpyklų, iškviečiama PHP funkcija grąžins klaidą ir sukurs pranešimą. AllMajorityRoute nėra toks griežtas ir leidžia išjungti iki pusės agregatų be aukščiau aprašytų problemų.

Pagrindinis trūkumas Ši schema yra tokia, kad jei talpykloje tikrai nėra duomenų, tada kiekvienai kliento užklausai iš tikrųjų bus vykdoma N užklausų į atmintinę. visi serveriai baseine. Mes galime sumažinti serverių skaičių telkiniuose, pavyzdžiui, iki dviejų: paaukodami saugyklos patikimumą, gaunameоdidesnis greitis ir mažesnė apkrova nuo užklausų iki trūkstamų raktų.

NB: Taip pat galite rasti naudingų nuorodų mokantis mcrouter dokumentacija wiki и projekto klausimai (įskaitant uždaras), atstovaujančias visą įvairių konfigūracijų sandėlį.

„Mcrouter“ kūrimas ir valdymas

Mūsų programa (ir pati atmintinė) veikia Kubernetes - atitinkamai ten yra ir mcrouter. Dėl konteinerio surinkimas mes naudojame werf, kurios konfigūracija atrodys taip:

NB: Straipsnyje pateikti sąrašai skelbiami saugykloje Flat/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)

... ir nubraižykite jį Vairo diagrama. Įdomu tai, kad yra tik konfigūracijos generatorius, pagrįstas kopijų skaičiumi (jei kas turi lakoniškesnį ir elegantiškesnį variantą, pasidalinkite komentaruose):

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

Iškeliame jį į bandomąją aplinką ir patikriname:

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

Klaidos teksto paieška nedavė jokių rezultatų, bet užklausa „mcrouter php„Priešakyje buvo seniausia neišspręsta projekto problema – paramos trūkumas atmintyje išsaugotas dvejetainis protokolas.

NB: Memcached ASCII protokolas yra lėtesnis nei dvejetainis, o standartinės nuoseklios raktų maišos priemonės veikia tik su dvejetainiu protokolu. Tačiau tai nesukelia problemų konkrečiu atveju.

Triukas maiše: tereikia persijungti į ASCII protokolą ir viskas veiks.... Tačiau šiuo atveju įprotis ieškoti atsakymų dokumentacija php.net suvaidino žiaurų pokštą. Ten nerasite teisingo atsakymo... nebent, žinoma, slinksite iki galo, kur skiltyje „Naudotojo pateiktos pastabos“ bus ištikimas ir nesąžiningai nuvertintas atsakymas.

Taip, teisingas parinkties pavadinimas memcached.sess_binary_protocol. Jis turi būti išjungtas, po kurio užsiėmimai pradės veikti. Belieka įdėti konteinerį su mcrouter į podelį su PHP!

išvada

Taigi tik infrastruktūros pakeitimais pavyko išspręsti problemą: buvo išspręsta atmintinės gedimų tolerancijos problema, padidintas talpyklos saugojimo patikimumas. Be akivaizdžių privalumų programai, tai suteikė erdvės manevruoti dirbant platformoje: kai visi komponentai turi rezervą, administratoriaus gyvenimas labai supaprastėja. Taip, šis metodas turi ir trūkumų, jis gali atrodyti kaip „ramentas“, bet jei sutaupo pinigų, užkasa problemą ir nesukelia naujų – kodėl gi ne?

PS

Taip pat skaitykite mūsų tinklaraštyje:

Šaltinis: www.habr.com

Pirkite patikimą prieglobą svetainėms su DDoS apsauga, VPS VDS serveriais 🔥 Įsigykite patikimą svetainių talpinimą su DDoS apsauga, VPS VDS serveriais | ProHoster