Utilizà mcrouter per scalà u memcached horizontale

Utilizà mcrouter per scalà u memcached horizontale

U sviluppu di prughjetti d'alta carica in ogni lingua richiede un accostu speciale è l'usu di strumenti speciali, ma quandu si tratta di applicazioni in PHP, a situazione pò esse cusì aggravata chì avete da sviluppà, per esempiu, propiu servitore di l'applicazioni. In questa nota parleremu di u dolore familiar cù l'almacenamiento di sessione distribuitu è ​​a cache di dati in memcached è cumu risolvemu sti prublemi in un prughjettu "ward".

L'eroi di l'occasione hè una applicazione PHP basata nantu à u framework symfony 2.3, chì ùn hè micca in tuttu inclusu in i piani di cummerciale per aghjurnà. In più di l'almacenamiento di sessione abbastanza standard, stu prughjettu hà fattu un usu pienu pulitica "caching everything". in memcached: risposte à e dumande à i servitori di basa di dati è API, diverse bandiere, chjusi per a sincronizazione di l'esekzione di codice è assai di più. In una tale situazione, una rottura di memcached diventa fatale per u funziunamentu di l'applicazione. Inoltre, a perdita di cache porta à cunsequenze serii: u DBMS cumencia à scoppià à e cuciture, i servizii API cumincianu à pruibisce e dumande, etc. Stabilizà a situazione pò piglià decine di minuti, è durante questu tempu u serviziu serà terribilmente lento o completamente indisponibile.

Avemu bisognu di furnisce a capacità di scala orizzontalmente l'applicazione cù pocu sforzu, i.e. cù cambiamenti minimi à u codice fonte è funziunalità cumpleta cunservata. Fate u cache micca solu resistente à i fiaschi, ma ancu pruvate à minimizzà a perdita di dati da questu.

Chì ci hè di male in memcached stessu?

In generale, l'estensione memcached per PHP soporta dati distribuiti è almacenamiento di sessione fora di a scatula. U mecanismu per l'hashing di chjave coherente permette di mette in modu uniforme dati in parechji servitori, indirizzendu in modu unicu ogni chjave specifica à un servitore specificu da u gruppu, è l'arnesi di failover integrati assicuranu una alta dispunibilità di u serviziu di caching (ma, sfurtunatamenti, senza dati).

E cose sò un pocu megliu cù u almacenamiento di sessione: pudete cunfigurà memcached.sess_number_of_replicas, in u risultatu di quale i dati seranu guardati in parechji servitori à una volta, è in casu di fallimentu di una istanza memcached, i dati seranu trasferiti da l'altri. In ogni casu, se u servitore torna in linea senza dati (cum'è di solitu succede dopu à un reiniciu), alcune di e chjave seranu redistribuite in u so favore. In fatti, questu significarà perdita di dati di sessione, Siccomu ùn ci hè manera di "andà" à una altra replica in casu di una miss.

Strumenti di biblioteca standard sò destinati principalmente à horizontale scaling: permettenu di aumentà a cache à dimensioni gigantesche è furnisce l'accessu à questu da u codice allughjatu in diversi servitori. In ogni casu, in a nostra situazione, u voluminu di dati almacenati ùn supera micca parechji gigabytes, è u rendiment di unu o dui nodi hè abbastanza. Di conseguenza, l'unicu strumenti standard utili puderia esse assicurà a dispunibilità di memcached mantenendu almenu una istanza di cache in cundizione di travagliu. In ogni casu, ùn era micca pussibule di prufittà ancu di sta opportunità... Eccu vale a pena ricurdà l'antichità di u quadru utilizatu in u prugettu, per quessa chì era impussibile di ottene l'applicazione per travaglià cù una piscina di servitori. Ùn ci scurdemu micca ancu di a perdita di dati di sessione: l'ochju di u cliente twitched from the massive logout of users.

Ideale era necessariu replicazione di record in memcached è bypassing replicas in casu di sbagliu o sbagliu. Ci hà aiutatu à implementà sta strategia mcrouter.

mcrouter

Questu hè un router memcached sviluppatu da Facebook per risolve i so prublemi. U sustegnu u protocolu di testu memcached, chì permette installazioni di scala memcached à proporzioni insane. Una descrizzione dettagliata di mcrouter pò esse truvata in stu annunziu. Frà altre cose larga funziunalità pò fà ciò chì avemu bisognu:

  • replicate record;
  • fà fallback à altri servitori in u gruppu se un errore si trova.

Mettiti à u travagliu !

cunfigurazione mcrouter

Andaraghju direttamente à a cunfigurazione:

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

Perchè trè piscine? Perchè i servitori sò ripetuti? Scupritemu cumu funziona.

  • In questa cunfigurazione, mcrouter selezziunate u percorsu à quale a dumanda serà mandata basatu annantu à u cumandimu di dumanda. U tippu li dice questu OperationSelectorRoute.
  • E dumande GET passanu à u gestore RandomRoutechì sceglie aleatoriamente una piscina o una strada trà l'uggetti di array children. Ogni elementu di sta matrice hè à u turnu un gestore MissFailoverRoute, chì passerà per ogni servitore in a piscina finu à riceve una risposta cù dati, chì serà tornatu à u cliente.
  • Se avemu usatu esclusivamente MissFailoverRoute cù una piscina di trè servitori, allora tutte e dumande venenu prima à a prima istanza di memcached, è u restu riceve richieste nantu à una basa residuale quandu ùn ci hè micca dati. Un tali approcciu porta à carica eccessiva in u primu servitore in a lista, cusì hè statu decisu di generà trè piscine cù indirizzi in sequenze differenti è selezziunate in modu aleatoriu.
  • Tutti l'altri dumande (è questu hè un registru) sò trattati cù l'usu AllMajorityRoute. Stu handler manda richieste à tutti i servitori in a piscina è aspetta risposte da almenu N / 2 + 1 di elli. Da usu AllSyncRoute per l'operazioni di scrittura duvia esse abbandunatu, postu chì stu metudu necessita una risposta pusitiva da всех servitori in u gruppu - altrimenti torna SERVER_ERROR. Ancu mcrouter aghjunghje i dati à i cache dispunibuli, a funzione PHP di chjama torna un errore è generà avvisu. AllMajorityRoute ùn hè micca cusì strettu è permette finu à a mità di l'unità per esse fora di serviziu senza i prublemi descritti sopra.

Principale svantaghju Stu schema hè chì s'ellu ùn ci hè veramente micca dati in a cache, allora per ogni dumanda da u cliente N richieste à memcached seranu in realtà eseguite - à à tutti servitori in a piscina. Pudemu riduce u nùmeru di servitori in piscine, per esempiu, à dui: sacrificà a fiducia di almacenamiento, avemuоpiù veloce è menu carica da e dumande à e chjave mancanti.

NB: Pudete ancu truvà ligami utili per amparà mcrouter documentazione nantu à wiki и prublemi di prughjettu (inclusi quelli chjusi), chì rapprisentanu un magazzinu tutale di diverse cunfigurazioni.

Custruisce è gestisce mcrouter

A nostra applicazione (è memcached stessu) corre in Kubernetes - per quessa, mcrouter hè ancu situatu quì. Per assemblea di cuntainer avemu aduprà werf, a cunfigurazione per quale sarà cusì:

NB: I listini dati in l'articulu sò publicati in u repository 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)

... è disegnala Graficu di Helm. L'interessante hè chì ci hè solu un generatore di cunfigurazione basatu annantu à u numeru di repliche (se qualchissia hà una opzione più laconica è elegante, sparte in i cumenti):

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

U stendemu in l'ambiente di prova è verificate:

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

A ricerca di u testu di l'errore ùn hà micca datu risultati, ma per a quistione "mcrouter php"In prima linea era u prublema più anticu senza risolve di u prugettu - mancanza di sustegnu protocolu binariu memcached.

NB: U protokollu ASCII in memcached hè più lento cà u binari, è i mezi standard di l'hashing di chjave coherente solu funzionanu cù u protokollu binariu. Ma questu ùn crea micca prublemi per un casu specificu.

U truccu hè in u saccu: tuttu ciò chì duvete fà hè di passà à u protokollu ASCII è tuttu funziona.... Tuttavia, in questu casu, l'abitudine di circà risposte in documentazione nantu à php.net hà fattu una burla crudele. Ùn truverete micca a risposta curretta quì ... salvu chì, sicuru, scorri finu à a fine, induve in a sezione "Noti cuntribuiti da l'utilizatori" sarà fideli è risposta ingiustamente criticata.

Iè, u nome di l'opzione curretta hè memcached.sess_binary_protocol. Deve esse disattivatu, dopu chì e sessioni cumincianu à travaglià. Tuttu ciò chì resta hè di mette u cuntinuu cù mcrouter in un pod cù PHP!

cunchiusioni

Cusì, cù solu cambiamenti infrastrutturali pudemu risolve u prublema: u prublema cù a tolleranza di difetti di memcached hè stata risolta, è l'affidabilità di l'almacenamiento di cache hè stata aumentata. In più di i vantaghji evidenti per l'applicazione, questu hà datu spaziu per a manuvra quandu travaglia nantu à a piattaforma: quandu tutti i cumpunenti anu una riserva, a vita di l'amministratore hè assai simplificata. Iè, stu metudu hà ancu i so svantaghji, pò esse cum'è una "crutch", ma s'ellu si risparmia soldi, enterra u prublema è ùn causa micca novi - perchè micca?

PS

Leghjite puru nant'à u nostru blog:

Source: www.habr.com

Add a comment