Kasutame mcrouterit memcachedi horisontaalseks skaleerimiseks

Kasutame mcrouterit memcachedi horisontaalseks skaleerimiseks

Suure koormusega projektide arendamine igasugustes keeltes nõuab erilist lähenemist ja spetsiaalsete tööriistade kasutamist, kuid kui on juttu PHP rakendustest, võib olukord muutuda nii teravaks, et tuleb välja arendada näiteks oma rakenduste server. Selles märkmes räägime tuttavast murest seoses jaotatud sessioonide salvestamise ja andmete vahemällu salvestamisega memcachedis ning sellest, kuidas me neid probleeme ühes "alustalas" projekti lahendasime.

Jubeldaja on PHP rakendus, mis põhineb frameworkil symfony 2.3, mille uuendamine ei ole äri plaanidesse üldse kaasatud. Lisaks täiesti standardsele sessioonide salvestamisele kasutati sellel projektil samuti "kõike vahemällu salvestamise" põhimõtet memcached: vastuseid DB ja API-serverite päringutele, erinevaid lippusid, koodijooksu sünkroniseerimise blokeeringuid ja palju muud. Sellises olukorras memcached purunemine võib rakenduse töö jaoks olla fataalne. Pealegi viib vahemälu kadumine tõsiste tagajärgedeni: DB hakkab lagunema, API-teenused keelavad päringud jne. Situatsiooni stabiliseerimine võib võtta kümneid minuteid, samal ajal kui teenus töötab aeglaselt või muutub täiesti kättesaamatuks.

Me pidime tagama rakenduse horisontaalse skaleeritavuse minimaalse vaevaga, st minimaalsete muudatustega algkoodis ja täieliku funktsionaalsuse säilitamisega. Teha vahemälu mitte ainult tõrketaluvaks, vaid ka püüda minimeerida sealset andmete kadu.

Mis on valesti memcached-iga?

Esmalt, PHP memcached laiendus „kastist välja” toetab andmete ja sessioonide jaotatud salvestamist. Ühtse võtme hashimise mehanism võimaldab andmeid ühtlaselt jaotada paljudele serveritele, suunates iga konkreetse võtme kindlale serverile grupis, ja sisseehitatud failover-meetodid tagavad kõrge kättesaadavuse vahemälu teenusele (kuid kahjuks, ei andmeid).

Sessioonide salvestamisega on lood natuke paremad: saab seadistada memcached.sess_number_of_replicas, mille tulemusel andmed salvestatakse koheselt mitmesse serverisse ja kui üks memcached eksemplar ebaõnnestub, antakse andmed teistest. Siiski, kui server naaseb tööle ilma andmeteta (nagu tavaliselt pärast taaskäivitamist), jagatakse osa võtmetest tema kasuks. Tegelikult tähendaks see sessioonide andmete kadu, kuna pole võimalik „minna” teise koopia juurde, kui esialgne ei toimi.

Tehnilise teegi standardvahendid on suunatud peamiselt horisontaalsele skaleerimine: need võimaldavad suurendada vahemälu tohutute mõõtmeteni ja tagada sellele juurdepääs erinevates serverites hostitud koodist. Siiski ei ületa meie olukorras salvestatud andmete maht mitut gigabaiti, ja ühe või kahe sõlme jõudlus on täiesti piisav. Seega, kasulikud vaikimisi tööriistad võiksid tagada ainult memcached'i kättesaadavuse, hoides vähemalt üht vahemälu eksemplari töövalmis. Kuid isegi seda võimalust polnud meil võimalik kasutada... Siinkohal tuleb meenutada projekti kasutatud raamistiku vanust, mistõttu ei õnnestunud rakendust serverite kogumiga käivitada. Ärme unusta ka sessioonide andmete kaotusi: tellija kasutajate massiline väljalogimine põhjustas silmade tõmblemist.

Ideaalis oleks olnud vajalik kande rehkendamine memcached'is ja replikate vältimine õnnestumise või vea korral. Selle strateegia rakendamisel aitas meid mcrouter.

mcrouter

See on memcached'i marsruuter, mille on välja töötanud Facebook oma probleemide lahendamiseks. See toetab memcached'i tekstiprotokolli, mis võimaldab skaleerida memcached'i installatsioone uskumatult suuruseks. Üksikasjalik kirjeldus mcrouterist on saadaval selles teates. Lisaks muule laiemale funktsionaalsusele saab ta teha seda, mida me vajame:

  • salvestuse replikatsioon;
  • teha fallback grupi teistele serveritele viga tekkimise korral.

Ees on töö!

mcrouteri konfiguratsioon

Küpsen kohe konfiguratsiooni juurde:

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

Miks on kolm puuli? Miks serverid korduvad? Uurime, kuidas see töötab.

  • Antud konfiguratsioonis valib mcrouter tee, mille kaudu päring saadetakse, lähtudes päringu tüübist. Seda ütleb talle tüüp OperationSelectorRoute.
  • GET-päringud suunduvad töötlejasse RandomRoute, mis valib juhuslikult puuli või marsruudi array objektide hulgast children. Iga selle massiivi element on omakorda töötleja MissFailoverRoute, mis läbib iga serveri sõnumist, kuni saab vastuse koos andmetega, mis tagastatakse kliendile.
  • Kui me kasutaksime ainult MissFailoverRoute kolmest serverist koosnevat paaki, siis kõik päringud läheksid esmalt esimesele memcached eksemplarile, samal ajal kui ülejäänud saaksid päringud jääkmeetodiga, kui andmed puuduvad. See lähenemine tooks kaasa liigse koormuse nimekirja esimesel serveril, seetõttu otsustati genereerida kolm paaki erinevate aadressides järjekorras ja valida neid juhuslikult.
  • Kõik ülejäänud päringud (ja see hõlmab kirjutamist) töötlevad AllMajorityRoute. See töötleja saadab päringud kõigisse paagi serveritesse ja ootab vastuseid vähemalt N/2 + 1 neist. AllSyncRoute kirjutamistegevuste jaoks pidi loobuma, kuna see meetod nõuab positiivset vastust grupi serveritelt — vastasel juhul tagastab see keskkondades ja projekti klastrites. Selline põhimõte on õige SERVER_ERROR . Kuigi mcrouter salvestab andmed kättesaadavatesse vahemä-storeidesse, tagastab PHP funktsioonvea ja genereerib märkuande. ei ole nii range ja lubab välja kukutada kuni poole sõlmedest ilma eespool kirjeldatud probleemideta. AllMajorityRoute Peamine miinus

Основной минус Selle skeemi probleem seisneb selles, et kui andmeid vahemälus pole, siis iga kliendi päringu korral tehakse tegelikult N päringut memcachedile — serveritele, kõikidest mis on puulis. Serverite arvu puulis saab vähendada, näiteks kahte, loobudes salvestamise usaldusväärsusest, saades suurema kiirus ja väiksema koormuse puuduvate võtmete päringutest.o: Kasulikud lingid mcrouteri uurimiseks võivad samuti olla

NBdokumentatsioon wiki's projekti issues ja (sealhulgas suletud), mis esindavad suurt hulka erinevaid konfiguratsioone. mcrouteri koostamine ja käivitamine

Rakendus (ja memcached) töötab meil Kuberneteses — vastavalt asub seal ka mcrouter. Containeri koostamiseks

kasutame , mille konfigureerimine võib välja näha järgmiselt: : Artiklis toodud loendid on avaldatud repos werfflant/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)

… ja viskame Helm-chart. Huvi pakkuda – siin on ainult konfiguraator, olenevalt replikate arvust (kui kellelgi on lühem ja elegantsim variant – jagage kommentaarides):

{{- $count := (pluck .Values.global.env .Values.memcached.replicas | first | default .Values.memcached.replicas._default | int) -}}
{{- $pools := dict -}}
{{- $servers := list -}}
{{- /* Täidame massiivi kahe serveri koopiaga: "0 1 2 0 1 2" */ -}}
{{- range until 2 -}}
 {{- range $i, $_ := until $count -}}
 {{- $servers = append $servers (printf "mc-%d.mc:11211" $i) -}}
 {{- end -}}
{{- end -}}
{{- /* Liikudes massiivis edasi, saame N lõike: "[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|poold" $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)

Viime tõlkime testkeskkonda ja kontrollime:

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

Veateate sisu otsimine ei andnud tulemusi, kuid päringu „mcrouter php“ esimeses reas mainiti projekti kõige vanemat lahendamata probleemi — binaarse protokolli toe puudumine memcachedis.

NB: ASCII-protokoll memcachedis on aeglasem kui binaarne protokoll, ning olemasolevad tööriistad võtme konsistentse hajutamise jaoks töötavad ainult binaarse protokolliga. Kuid see ei tekita konkreetse juhtumi jaoks probleeme.

Asi on selge: tuleb vaid vahetada ASCII-protokollile ja kõik töötab... Kuid antud juhul osutus harjumus otsida vastuseid dokumentatsioonist php.net kurvaks naljaks. Seal õiget vastust ei leia... välja arvatud juhul, kui sa jõuad lõpuni, kus sektsioonis „Kasutaja antud märkused“ on õige ja kogemata alahinnatud vastus.

Jah, valikute õige nimi on memcached.sess_binary_protocol. See tuleb välja lülitada, pärast mida seansid hakkavad tööle. Jäänud on vaid panna mcrouter konteiner PHP podi!

Kokkuvõte

Nii suutsime ainult infrastruktuuri muudatustega lahendada püstitatud ülesande: memcachedi talitlushäire küsimus on lahendatud, vahemälu säilitamise usaldusväärsus on suurenenud. Peale silmapaistvate eeliste rakendusele andis see ruumi manööverdusvõimeks platvormi arendamise ajal: kui kõikidel komponentidel on varu, lihtsustab see administraatori elu oluliselt. Jah, see meetod omab ka oma puudusi, see võib tunduda „toetav“, kuid kui see säästab raha, peidab probleemi ja ei tekita uusi - miks mitte?

P.S.

Lugege ka meie blogist:

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster