Vi anvÀnder mcrouter för horisontell skalning av memcached

Vi anvÀnder mcrouter för horisontell skalning av memcached

Utveckling av högbelastade projekt pÄ vilket sprÄk som helst krÀver en speciell metodik och anvÀndning av speciella verktyg, men nÀr det handlar om applikationer i PHP kan situationen förvÀrras sÄ mycket att man exempelvis mÄste utveckla en egen applikationsserver. I denna anteckning kommer vi att prata om en bekant smÀrta med distribuerad lagring av sessioner och cachning av data i memcached samt hur vi löste dessa problem i ett "projet" av vÄrd.

Huvudpersonen Àr en PHP-applikation baserad pÄ ramverket Symfony 2.3, vilket inte ingÄr i företagsplanerna att uppdatera. Förutom den helt standardmÀssiga sessionlagringen anvÀndes i detta projekt politiken "cachning av allt" i memcached: svar pÄ frÄgor till databasen och API-servrar, olika flaggor, lÄs för synkronisering av kodens utförande och mycket mer. I en sÄdan situation blir ett fel i memcached fatalt för applikationens funktion. Dessutom leder förlust av cache till allvarliga konsekvenser: databasen börjar braka, API-tjÀnsterna blockerar förfrÄgningar, etc. Stabilisering av situationen kan ta mÄnga minuter, och under denna tid kommer tjÀnsten att vara extremt seg eller till och med bli otillgÀnglig.

Vi behövde sÀkerstÀlla möjligheten till horisontell skalning av applikationen med minimala insatser, dvs. med minimiÀndringar i kÀllkoden och fullstÀndig bevarande av funktionaliteten. Att göra cachen inte bara felfri, utan ocksÄ att minimera förluster av data frÄn den.

Vad Àr fel med memcached sjÀlv?

Generellt sett stödjer memcached-tillÀgget för PHP "ur lÄdan" distribuerad lagring av data och sessioner. Mekanismen för konsekvent hashning av nycklar gör det möjligt att jÀmnt fördela data över mÄnga servrar genom att entydigt adressera varje specifik nyckel till en viss server i gruppen, och de inbyggda failover-mekanismerna sÀkerstÀller hög tillgÀnglighet för cache-tjÀnsten (men tyvÀrr, inte för datan).

Sessionlagring fungerar nÄgot bÀttre: det gÄr att stÀlla in memcached.sess_number_of_replicas, vilket innebÀr att data kommer att sparas pÄ flera servrar, och i hÀndelse av att en instans av memcached gÄr ner kommer datan att hÀmtas frÄn andra. Men om servern kommer tillbaka utan data (som ofta hÀnder efter en omstart), kommer en del nycklar att omfördelas till dess fördel. Faktum Àr att detta skulle innebÀra förlust av sessionsdata, eftersom det inte finns nÄgon möjlighet att "gÄ" till en annan replik vid en miss.

De standardverktyg som biblioteket erbjuder Ă€r frĂ€mst inriktade pĂ„ horisontell skalerbarhet: de tillĂ„ter att öka cacheminnet till gigantiska storlekar och ge Ă„tkomst till det frĂ„n kod som körs pĂ„ olika servrar. Men i vĂ„r situation överstiger den mĂ€ngd data som lagras inte nĂ„gra fĂ„ gigabyte, och prestandan hos en eller tvĂ„ noder Ă€r mer Ă€n tillrĂ€cklig. DĂ€rmed skulle den enda anvĂ€ndbara funktionen vara att sĂ€kerstĂ€lla tillgĂ„ng till memcached sĂ„ lĂ€nge minst en instans av cacheminnet Ă€r i drift. Även denna möjlighet kunde man dock inte utnyttja
 HĂ€r bör man pĂ„minna om ramverkets Ă„lder som anvĂ€ndes i projektet, vilket gjorde det omöjligt att fĂ„ applikationen att fungera med en serverpool. Vi fĂ„r heller inte glömma förlusten av sessionsdata: den massiva utloggningen av anvĂ€ndare fick kunden att bli nervös.

I idealfallet krÀvdes replikering av poster i memcached och att kringgÄ repliker vid en miss eller ett fel. För att genomföra denna strategi hjÀlpte oss mcrouter.

mcrouter

Detta Àr en router för memcached, utvecklad av Facebook för att lösa deras problem. Den stöder det textbaserade protokollet för memcached, som möjliggör att skala memcached-installationer till orimliga storlekar. En detaljerad beskrivning av mcrouter kan hittas i denna tillkÀnnagivande. Förutom andra omfattande funktionaliteter kan den göra det vi behöver:

  • replikera poster;
  • falla tillbaka pĂ„ andra servrar i gruppen vid uppkomst av fel.

LÄt oss sÀtta igÄng!

Konfiguration av mcrouter

Jag gÄr direkt till konfigurationen:

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

Varför tre pooler? Varför upprepas servrar? LÄt oss titta pÄ hur det fungerar.

  • I denna konfiguration vĂ€ljer mcrouter vĂ€gen för att skicka begĂ€ran baserat pĂ„ begĂ€rningens typ. Det informeras av typen OperationSelectorRoute.
  • GET-begĂ€rningar gĂ„r till hanteraren RandomRoute, som slumpmĂ€ssigt vĂ€ljer en pool eller vĂ€g bland objekten i arrayen children. Varje element i denna array Ă€r i sin tur en hanterare MissFailoverRoute, som skannar var och en av servrarna i poolen tills den fĂ„r ett svar med data, vilket kommer att returneras till klienten.
  • Om vi hade anvĂ€nt enbart MissFailoverRoute en pool med tre servrar skulle alla begĂ€rningar först gĂ„ till den första instansen av memcached, medan de andra skulle mottaga begĂ€rningar endast om datan saknas. Denna metod skulle ha lett till en överbelastning av den första servern i listan, vilket Ă€r anledningen till att vi beslutade att skapa tre pooler med adresser i olika sekvenser och vĂ€lja dem slumpmĂ€ssigt.
  • Alla andra begĂ€rningar (det vill sĂ€ga skrivningar) hanteras med hjĂ€lp av AllMajorityRoute. Denna hanterare skickar begĂ€rningar till alla servrar i poolen och vĂ€ntar pĂ„ svar frĂ„n minst N/2 + 1 av dem. Vi var tvungna att avstĂ„ frĂ„n att anvĂ€nda AllSyncRoute för skrivoperationer eftersom denna metod krĂ€ver ett positivt svar frĂ„n alla servergrupperna — annars returnerar den SERVER_ERROR. Även om mcrouter dĂ„ lĂ€gger datan i tillgĂ€ngliga cacheminnen, kommer den anropande PHP-funktionen att returnera ett fel och generera ett meddelande. AllMajorityRoute Ă€r inte lika strikt och tillĂ„ter att upp till hĂ€lften av noderna sĂ€tts ur drift utan ovanstĂ„ende problem.

Den största nackdelen med detta system Ă€r att om det verkligen inte finns nĂ„gon data i cachen, kommer varje begĂ€ran frĂ„n klienten praktiskt taget att utföra N begĂ€rningar till memcached — till med allt servrarna i poolen. Vi kan minska antalet servrar i poolerna, till exempel till tvĂ„: genom att offra lagringspĂ„litlighet, fĂ„r vi en...bĂ€sta resultatet (subjektiv Ă„sikt).högre hastighet och mindre belastning frĂ„n förfrĂ„gningar till saknade nycklar.

NB: Nyttiga lÀnkar för att studera mcrouter kan ocksÄ vara dokumentationen i wiki och projektets issues (inklusive stÀngda), som representerar en verklig guldgruva av olika konfigurationer.

Bygga och köra mcrouter

Applikationen (och memcached sjĂ€lv) körs i Kubernetes — dĂ€rför Ă€r mcrouter ocksĂ„ dĂ€r. För att bygga containern anvĂ€nder vi werf, konfigurationen kommer att se ut som följer:

NB: Listorna som ges i artikeln Àr publicerade i repositoryt 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: Installera förutsÀttningar
   apt:
     name: [ 'apt-transport-https', 'tzdata', 'locales' ]
     update_cache: yes
 - name: LĂ€gg till mcrouter APT-nyckel
   apt_key:
     url: https://facebook.github.io/mcrouter/debrepo/xenial/PUBLIC.KEY
 - name: LĂ€gg till mcrouter Repo
   apt_repository:
     repo: deb https://facebook.github.io/mcrouter/debrepo/xenial xenial contrib
     filename: mcrouter
     update_cache: yes
 - name: StÀll in tidszon
   timezone:
     name: "Europe/Moscow"
 - name: Se till att en lokalversion finns
   locale_gen:
     name: en_US.UTF-8
     state: present
 install:
 - name: Installera mcrouter
   apt:
     name: [ 'mcrouter' ]

(werf.yaml)


 och skriver ner Helm-chart. Intressant — hĂ€r Ă€r endast konfiggeneratorn baserat pĂ„ antal repliker (om nĂ„gon har ett mer kortfattat och elegant alternativ — dela med er i kommentarerna):

{{- $count := (pluck .Values.global.env .Values.memcached.replicas | first | default .Values.memcached.replicas._default | int) -}}
{{- $pools := dict -}}
{{- $servers := list -}}
{{- /* Fyller matrisen med tvÄ kopior av servrar: "0 1 2 0 1 2" */ -}}
{{- range until 2 -}}
 {{- range $i, $_ := until $count -}}
 {{- $servers = append $servers (printf "mc-%d.mc:11211" $i) -}}
 {{- end -}}
{{- end -}}
{{- /* Genom att vrida pÄ matrisen fÄr vi N skivor: "[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)

Rullas ut i testmiljö och kontrolleras:

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

Sökning pĂ„ felmeddelandet gav inget resultat, men med förfrĂ„gan «mcrouter php» fanns den Ă€ldsta öppna frĂ„gan in kĂ€rnan av projektet — bristen pĂ„ stöd för memcached:s binĂ€ra protokoll.

NB: ASCII-protokol i memcached Àr lÄngsammare Àn det binÀra, och de inbyggda verktygen för konsekvent hashning av nycklar fungerar endast med det binÀra protokollet. Men det skapar inga problem för det specifika fallet.

Saken Àr biff: det Àr bara att vÀxla till ASCII-protokollet sÄ fungerar allt igen.... Men i detta fall spelade vanan att söka svar i dokumentationen pÄ php.net oss ett elakt spratt. Du kommer inte att hitta det rÀtta svaret dÀr... om du inte blÀddrar till slutet, dÀr i sektionen «User contributed notes» finns det ett korrekt och orÀttvist nedröstat svar.

Ja, det rÀtta namnet pÄ alternativet Àr memcached.sess_binary_protocol. Det mÄste stÀngas av, efter vilket sessionerna börjar fungera. Det ÄterstÄr bara att placera containern med mcrouter i podden med PHP!

Slutsats

SÄledes lyckades vi, genom enbart infrastrukturella förÀndringar, lösa den stÀllda uppgiften: frÄgan om failover i memcached Àr löst, pÄlitligheten av cachelagring har ökat. Förutom de uppenbara fördelarna för applikationen har det gett utrymme för manövrering vid arbetet med plattformen: nÀr alla komponenter har reserv gör det livet lÀttare för administratören. Ja, denna metod har sina nackdelar, kan se ut som ett "krÄngligt" tillvÀgagÄngssÀtt, men om det sparar pengar, döljer problemet och inte orsakar nya - varför inte?

P.S.

LÀs Àven i vÄr blogg:

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster