Gebruik mcrouter om memcached horisontaal te skaal

Gebruik mcrouter om memcached horisontaal te skaal

Die ontwikkeling van hoëladingsprojekte in enige taal verg 'n spesiale benadering en die gebruik van spesiale gereedskap, maar wanneer dit by toepassings in PHP kom, kan die situasie so vererger dat jy bv. eie toepassingsbediener. In hierdie nota sal ons praat oor die bekende pyn met verspreide sessieberging en datakas in memcached en hoe ons hierdie probleme in een "wyk"-projek opgelos het.

Die held van die geleentheid is 'n PHP-toepassing gebaseer op die symfonie 2.3-raamwerk, wat glad nie ingesluit is in die sakeplanne om by te werk nie. Benewens redelik standaard sessieberging, het hierdie projek ten volle gebruik gemaak "cache alles" beleid in memcached: antwoorde op versoeke aan die databasis en API-bedieners, verskeie vlae, slotte vir die sinchronisering van kode-uitvoering en nog baie meer. In so 'n situasie word 'n afbreek van memcached dodelik vir die werking van die toepassing. Boonop lei kasverlies tot ernstige gevolge: die DBMS begin uit sy nate bars, API-dienste begin versoeke verbied, ens. Die stabilisering van die situasie kan tien minute neem, en gedurende hierdie tyd sal die diens verskriklik stadig of heeltemal onbeskikbaar wees.

Ons moes voorsien die vermoë om die toepassing horisontaal te skaal met min moeite, d.w.s. met minimale veranderinge aan die bronkode en volle funksionaliteit bewaar. Maak die kas nie net bestand teen mislukkings nie, maar probeer ook om dataverlies daaruit te verminder.

Wat is fout met memcached self?

Oor die algemeen ondersteun die memcached-uitbreiding vir PHP verspreide data en sessieberging buite die boks. Die meganisme vir konsekwente sleutel-hashing laat jou toe om data eweredig oor baie bedieners te versprei, wat elke spesifieke sleutel uniek aan 'n spesifieke bediener van die groep aanspreek, en ingeboude failover-nutsgoed verseker hoë beskikbaarheid van die kasdiens (maar, ongelukkig, geen data).

Dinge is 'n bietjie beter met sessieberging: jy kan konfigureer memcached.sess_number_of_replicas, as gevolg waarvan die data tegelyk op verskeie bedieners gestoor sal word, en in die geval van 'n mislukking van een memcached-instansie, sal die data van ander oorgedra word. As die bediener egter weer aanlyn kom sonder data (soos gewoonlik na 'n herbegin gebeur), sal sommige van die sleutels in sy guns herverdeel word. In werklikheid sal dit beteken verlies van sessiedata, aangesien daar geen manier is om na 'n ander replika te "gaan" in geval van 'n mis nie.

Standaard biblioteekhulpmiddels is hoofsaaklik gemik op horisontaal skaal: hulle laat jou toe om die kas tot reusagtige groottes te vergroot en toegang daartoe te bied vanaf kode wat op verskillende bedieners aangebied word. In ons situasie oorskry die volume gestoorde data egter nie 'n paar gigagrepe nie, en die werkverrigting van een of twee nodusse is heeltemal genoeg. Gevolglik kan die enigste nuttige standaardnutsmiddels wees om die beskikbaarheid van memcached te verseker terwyl ten minste een kasgeval in werkende toestand gehou word. Dit was egter nie moontlik om selfs hierdie geleentheid te benut nie... Hier is dit die moeite werd om die oudheid van die raamwerk wat in die projek gebruik is te onthou, en daarom was dit onmoontlik om die toepassing met 'n poel bedieners te laat werk. Laat ons ook nie vergeet van die verlies van sessiedata nie: die kliënt se oog het geruk van die massiewe uitmelding van gebruikers.

Ideaal gesproke was dit vereis replikasie van rekords in gemcached en omseilende replikas in die geval van 'n fout of fout. Het ons gehelp om hierdie strategie te implementeer mcrouter.

mcrouter

Dit is 'n memcached router wat deur Facebook ontwikkel is om sy probleme op te los. Dit ondersteun die memcached teksprotokol, wat dit toelaat skaal memcached installasies tot waansinnige afmetings. 'n Gedetailleerde beskrywing van mcrouter kan gevind word in hierdie aankondiging. Onder andere wye funksionaliteit dit kan doen wat ons nodig het:

  • repliseer rekord;
  • doen terugval na ander bedieners in die groep as 'n fout voorkom.

Vir besigheid!

mcrouter-konfigurasie

Ek gaan reguit na die konfigurasie:

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

Hoekom drie swembaddens? Waarom word bedieners herhaal? Kom ons vind uit hoe dit werk.

  • In hierdie opstelling kies mcrouter die pad waarheen die versoek gestuur sal word, gebaseer op die versoekopdrag. Die ou sê dit vir hom OperationSelectorRoute.
  • GET-versoeke gaan na die hanteerder RandomRoutewat lukraak 'n poel of roete tussen skikkingsvoorwerpe kies children. Elke element van hierdie skikking is op sy beurt 'n hanteerder MissFailoverRoute, wat deur elke bediener in die swembad gaan totdat dit 'n antwoord met data ontvang, wat aan die kliënt teruggestuur sal word.
  • As ons uitsluitlik gebruik het MissFailoverRoute met 'n poel van drie bedieners, dan sal alle versoeke eerste na die eerste memcached-instansie kom, en die res sal versoeke op 'n oorblywende basis ontvang wanneer daar geen data is nie. So 'n benadering sou lei tot oormatige las op die eerste bediener in die lys, so daar is besluit om drie poele met adresse in verskillende volgordes te genereer en hulle lukraak te kies.
  • Alle ander versoeke (en dit is 'n rekord) word verwerk deur AllMajorityRoute. Hierdie hanteerder stuur versoeke na alle bedieners in die swembad en wag vir antwoorde van ten minste N/2 + 1 van hulle. Van gebruik AllSyncRoute vir skryfbewerkings moes laat vaar word, aangesien hierdie metode 'n positiewe reaksie van vereis alle bedieners in die groep - anders sal dit terugkeer SERVER_ERROR. Alhoewel mcrouter die data by beskikbare kas sal voeg, is die roepende PHP-funksie sal 'n fout terugstuur en sal kennisgewing genereer. AllMajorityRoute is nie so streng nie en laat toe dat tot die helfte van die eenhede buite werking gestel word sonder die probleme hierbo beskryf.

Belangrikste nadeel Hierdie skema is dat as daar werklik geen data in die kas is nie, vir elke versoek van die kliënt N versoeke na memcached werklik uitgevoer sal word - om al bedieners in die swembad. Ons kan die aantal bedieners in poele verminder, byvoorbeeld tot twee: om bergingsbetroubaarheid op te offer, kry onsоhoër spoed en minder vrag van versoeke tot ontbrekende sleutels.

NB: Jy kan ook nuttige skakels vind om mcrouter te leer dokumentasie op wiki и projek kwessies (insluitend geslote), wat 'n hele pakhuis van verskillende konfigurasies verteenwoordig.

Bou en bestuur 'n mikrouter

Ons toepassing (en memcached self) loop in Kubernetes - dienooreenkomstig is mcrouter ook daar geleë. Vir houer samestelling ons gebruik werf, die konfigurasie waarvoor sal soos volg lyk:

NB: Die lyste wat in die artikel gegee word, word in die bewaarplek gepubliseer 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)

... en skets dit Roerkaart. Die interessante ding is dat daar net 'n konfigurasie-opwekker is gebaseer op die aantal replikas (as iemand 'n meer lakoniese en elegante opsie het, deel dit in die kommentaar):

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

Ons rol dit uit in die toetsomgewing en kontroleer:

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

Soek na die teks van die fout het geen resultate opgelewer nie, maar vir die navraag "mikrouter php“Op die voorgrond was die oudste onopgeloste probleem van die projek – gebrek aan ondersteuning memcached binêre protokol.

NB: Die ASCII-protokol in memcached is stadiger as die binêre een, en standaardmiddele van konsekwente sleutelhashing werk net met die binêre protokol. Maar dit skep nie probleme vir 'n spesifieke geval nie.

Die truuk is in die sak: al wat jy hoef te doen is om na die ASCII-protokol oor te skakel en alles sal werk.... Maar in hierdie geval is die gewoonte om antwoorde te soek in dokumentasie op php.net 'n wrede grap gemaak. Jy sal nie die korrekte antwoord daar vind nie ... tensy jy natuurlik blaai tot aan die einde, waar in die afdeling "Gebruiker bygedra notas" getrou sal wees en antwoord wat onregverdig afgestem is.

Ja, die korrekte opsienaam is memcached.sess_binary_protocol. Dit moet gedeaktiveer word, waarna die sessies sal begin werk. Al wat oorbly is om die houer met mcrouter in 'n peul met PHP te sit!

Gevolgtrekking

Dus, met net infrastruktuurveranderinge kon ons die probleem oplos: die probleem met memcached-fouttoleransie is opgelos, en die betroubaarheid van kasberging is verhoog. Benewens die ooglopende voordele vir die toepassing, het dit maneuverruimte gegee wanneer daar op die platform gewerk word: wanneer alle komponente 'n reserwe het, word die administrateur se lewe aansienlik vereenvoudig. Ja, hierdie metode het ook sy nadele, dit kan soos 'n "kruk" lyk, maar as dit geld spaar, die probleem begrawe en nie nuwes veroorsaak nie - hoekom nie?

PS

Lees ook op ons blog:

Bron: will.com

Voeg 'n opmerking