Mcrouteri kasutamine memcached horisontaalselt skaleerimiseks

Mcrouteri kasutamine memcached horisontaalselt skaleerimiseks

Suure koormusega projektide arendamine mis tahes keeles nõuab erilist lähenemist ja spetsiaalsete tööriistade kasutamist, kuid PHP-s rakenduste puhul võib olukord muutuda nii teravaks, et tuleb arendada näiteks enda rakendusserver. Selles märkuses räägime memcachedis hajutatud seansi salvestamise ja andmete vahemällu salvestamisest tuttavatest valudest ning sellest, kuidas me need probleemid ühes “palati” projektis lahendasime.

Sündmuse kangelaseks on symfony 2.3 raamistikul põhinev PHP-rakendus, mida uuendamise äriplaanides üldse ei sisaldu. Lisaks üsna tavalisele seansisalvestusele kasutas see projekt täielikult ära "kõik vahemällu salvestamise" poliitika memcachedis: vastused andmebaasi ja API serverite päringutele, erinevad lipud, lukud koodi täitmise sünkroonimiseks ja palju muud. Sellises olukorras saab memcachedi rike rakenduse toimimisele saatuslikuks. Lisaks põhjustab vahemälu kadumine tõsiseid tagajärgi: DBMS hakkab õmblustest lõhkema, API-teenused hakkavad päringuid keelama jne. Olukorra stabiliseerimine võib võtta kümneid minuteid ja selle aja jooksul on teenus kohutavalt aeglane või täiesti kättesaamatu.

Meil oli vaja pakkuda võimalus horisontaalselt skaleerida rakendust vähese vaevaga, st. minimaalsete muudatustega lähtekoodis ja täielik funktsionaalsus säilinud. Muutke vahemälu mitte ainult rikete suhtes vastupidavaks, vaid proovige ka minimeerida sellest tulenevat andmekadu.

Mis memcachedil endal viga on?

Üldiselt toetab PHP memcached laiendus karbist väljas hajutatud andmeid ja seansi salvestamist. Järjepideva võtmeräsimise mehhanism võimaldab teil paigutada andmeid ühtlaselt paljudesse serveritesse, adresseerides unikaalselt iga konkreetse võtme konkreetsele serverile rühmast, ning sisseehitatud tõrkesiirde tööriistad tagavad vahemällu salvestamise teenuse kõrge kättesaadavuse (kuid kahjuks andmeid pole).

Seansisalvestusega on asjad veidi paremad: saate konfigureerida memcached.sess_number_of_replicas, mille tulemusena salvestatakse andmed korraga mitmesse serverisse ning ühe memcached eksemplari rikke korral kantakse andmed üle teistelt. Kui aga server tuleb uuesti võrku ilma andmeteta (nagu tavaliselt pärast taaskäivitamist juhtub), jagatakse osa võtmeid tema kasuks ümber. Tegelikult see tähendab seansi andmete kadu, kuna möödalaskmise korral ei saa kuidagi teise koopia juurde “käida”.

Standardsed raamatukogu tööriistad on suunatud peamiselt horisontaalne skaleerimine: need võimaldavad teil suurendada vahemälu hiiglaslike suurusteni ja pakkuda sellele juurdepääsu erinevates serverites majutatud koodi kaudu. Kuid meie olukorras ei ületa salvestatud andmete maht mitut gigabaiti ja ühe või kahe sõlme jõudlus on täiesti piisav. Seetõttu võiksid ainsad kasulikud standardtööriistad olla memcachedi kättesaadavuse tagamine, säilitades samal ajal vähemalt ühe vahemälu eksemplari töökorras. Kuid isegi seda võimalust ei õnnestunud ära kasutada... Siinkohal tasub meenutada projektis kasutatud raamistiku iidsust, mistõttu ei õnnestunud rakendust serverikogumiga tööle saada. Ärgem unustagem ka seansiandmete kadumist: kliendi silm tõmbus tõmbunud kasutajate massilisest väljalogimisest.

Ideaalis oli see nõutav Kirjete replikatsioon vahemällu salvestatud ja ümbersõitvates koopiates vea või vea korral. Aitas meil seda strateegiat ellu viia mcrouter.

mcrouter

See on vahemällu salvestatud ruuter, mille Facebook on oma probleemide lahendamiseks välja töötanud. See toetab vahemällu salvestatud tekstiprotokolli, mis võimaldab mastaapsed memcached installatsioonid hullude mõõtmetega. Mcrouteri üksikasjaliku kirjelduse leiate aadressilt seda teadaannet. Muuhulgas lai funktsionaalsus see suudab teha seda, mida me vajame:

  • replikatsiooni kirje;
  • tõrke ilmnemisel pöörduge tagasi teistesse rühma serveritesse.

Asuge asja kallale!

mcrouteri konfiguratsioon

Lähen otse 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 kolm basseini? Miks servereid korratakse? Mõelgem välja, kuidas see toimib.

  • Selles konfiguratsioonis valib mcrouter päringu käsu alusel tee, kuhu päring saadetakse. Mees ütleb talle seda OperationSelectorRoute.
  • GET-päringud lähevad töötlejale RandomRoutemis valib massiiviobjektide hulgast juhuslikult basseini või marsruudi children. Iga selle massiivi element on omakorda töötleja MissFailoverRoute, mis läbib iga basseini serverit, kuni saab vastuse koos andmetega, mis tagastatakse kliendile.
  • Kui kasutaksime ainult MissFailoverRoute kolmest serverist koosneva kogumi korral jõuaksid kõik päringud esmalt esimesele vahemällu salvestatud eksemplarile ja ülejäänud saaksid päringud järelejäänud alusel, kui andmeid pole. Selline lähenemine tooks kaasa loendi esimese serveri ülemäärane koormus, mistõttu otsustati genereerida kolm erinevas järjestuses aadressidega kogumit ja valida need juhuslikult.
  • Kõiki muid päringuid (ja see on kirje) töödeldakse kasutades AllMajorityRoute. See töötleja saadab päringud kõigile puuli serveritele ja ootab vastuseid vähemalt N/2 + 1-lt. Kasutamisest AllSyncRoute kirjutamistoimingutest tuli loobuda, kuna see meetod nõuab positiivset vastust Kõik serverid rühmas - vastasel juhul naaseb SERVER_ERROR. Kuigi mcrouter lisab andmed saadaolevatesse vahemäludesse, helistab PHP funktsioon tagastab veateate ja loob teate. AllMajorityRoute ei ole nii range ja võimaldab ilma ülalkirjeldatud probleemideta kasutusest kõrvaldada kuni pooled seadmed.

Peamine puudus See skeem seisneb selles, et kui vahemälus tõesti andmeid pole, siis iga kliendi päringu puhul täidetakse tegelikult N päringut memcached'ile - kuni kõik serverid basseinis. Saame basseinides serverite arvu vähendada näiteks kaheni: ohverdades salvestuskindluse, saameоsuurem kiirus ja väiksem koormus taotlustest puuduvate võtmeteni.

NB: võite leida ka kasulikke linke mcrouteri õppimiseks dokumentatsioon wikis и projekti küsimused (ka kinnised), mis esindavad tervet erineva konfiguratsiooni ladu.

Mcrouteri ehitamine ja käitamine

Meie rakendus (ja memcached ise) töötab Kubernetesis - vastavalt asub seal ka mcrouter. Sest konteineri kokkupanek me kasutame werf, mille konfiguratsioon näeb välja selline:

NB: Artiklis toodud loetelud on avaldatud hoidlas lapik/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 visandage see Helmi diagramm. Huvitav on see, et seal on ainult konfiguratsioonigeneraator, mis põhineb koopiate arvul (kui kellelgi on mõni lakoonilisem ja elegantsem variant, siis jagage seda kommentaarides):

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

Avaldame selle 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 >

Vea teksti otsimine ei andnud tulemusi, küll aga päring "mikrouter php"Eeles oli projekti vanim lahendamata probleem - toetuse puudumine memcached binaarprotokoll.

NB: Vahemällu salvestatud ASCII-protokoll on aeglasem kui binaarne ja standardsed järjekindla võtmeräsimise vahendid töötavad ainult kahendprotokolliga. Kuid see ei tekita konkreetse juhtumi puhul probleeme.

Nipp on kotis: tuleb vaid ASCII protokollile üle minna ja kõik toimib.... Kuid sel juhul on harjumus vastuseid otsida dokumentatsioon saidil php.net mängis julma nalja. Sa ei leia sealt õiget vastust... kui muidugi ei kerida lõpuni, kus sektsioonis "Kasutaja panustatud märkmed" on ustav ja ebaõiglaselt mahahääletatud vastus.

Jah, õige valiku nimi on memcached.sess_binary_protocol. See tuleb keelata, pärast seda hakkavad seansid tööle. Jääb üle vaid panna konteiner koos mcrouteriga PHP-ga kausta!

Järeldus

Seega ainuüksi infrastruktuuri muudatustega saime probleemi lahendada: memcached tõrketaluvus on lahendatud ja vahemälu salvestamise töökindlus on suurenenud. Lisaks rakenduse ilmsetele eelistele andis see platvormil töötamisel manööverdamisruumi: kui kõigil komponentidel on reserv, on administraatori eluiga oluliselt lihtsustatud. Jah, sellel meetodil on ka omad miinused, see võib tunduda “karguna”, aga kui see säästab raha, matab probleemi enda alla ega tekita uusi – miks mitte?

PS

Loe ka meie blogist:

Allikas: www.habr.com

Lisa kommentaar