Uzante mcrouter por skali memcached horizontale

Uzante mcrouter por skali memcached horizontale

Disvolvi altŝarĝajn projektojn en iu ajn lingvo postulas specialan aliron kaj la uzon de specialaj iloj, sed kiam temas pri aplikaĵoj en PHP, la situacio povas plimalboniĝi, ke vi devas disvolvi, ekzemple, propra aplika servilo. En ĉi tiu noto ni parolos pri la konata doloro kun distribuita sesio-stokado kaj datuma kaŝmemoro en memcached kaj kiel ni solvis ĉi tiujn problemojn en unu "hospitala" projekto.

La heroo de la okazo estas PHP-apliko bazita sur la kadro symfony 2.3, kiu tute ne estas inkluzivita en la komercaj planoj por ĝisdatigi. Krom sufiĉe norma sesio-stokado, ĉi tiu projekto plene uzis politiko pri "ĉio konservado". en memcached: respondoj al petoj al la datumbazo kaj API-serviloj, diversaj flagoj, seruroj por sinkronigi kodekzekuton kaj multe pli. En tia situacio, rompo de memcached iĝas fatala al la funkciado de la aplikaĵo. Krome, kaŝmemorperdo kondukas al gravaj sekvoj: la DBMS komencas krevi ĉe la kudroj, API-servoj komencas malpermesi petojn, ktp. Stabiligi la situacion povas daŭri dekojn da minutoj, kaj dum ĉi tiu tempo la servo estos terure malrapida aŭ tute neatingebla.

Ni bezonis provizi la kapablo horizontale skali la aplikon kun malmulte da peno, t.e. kun minimumaj ŝanĝoj al la fontkodo kaj plena funkcieco konservita. Faru la kaŝmemoron ne nur imuna al misfunkciadoj, sed ankaŭ provu minimumigi datuman perdon de ĝi.

Kio estas malbona kun memcached mem?

Ĝenerale, la memcached etendo por PHP subtenas distribuitajn datumojn kaj sean stokadon el la skatolo. La mekanismo por konsekvenca ŝlosilhashing permesas vin distribui datumojn egale sur multaj serviloj, unike traktante ĉiun specifan ŝlosilon al specifa servilo de la grupo, kaj enkonstruitaj malsukcesigaj iloj certigas altan haveblecon de la kaŝmemorservo (sed, bedaŭrinde, neniuj datumoj).

Aferoj estas iom pli bonaj kun sesio-stokado: vi povas agordi memcached.sess_number_of_replicas, kiel rezulto de kiu la datumoj estos stokitaj sur pluraj serviloj samtempe, kaj en la okazo de malsukceso de unu memcached-instanco, la datumoj estos transdonitaj de aliaj. Tamen, se la servilo revenas enrete sen datumoj (kiel kutime okazas post rekomenco), kelkaj el la ŝlosiloj estos redistribuitaj en ĝia favoro. Fakte ĉi tio signifos perdo de seancaj datumoj, ĉar ne ekzistas maniero "iri" al alia kopio en kazo de maltrafo.

Normaj bibliotekaj iloj celas ĉefe horizontala skalo: ili permesas vin pliigi la kaŝmemoron al gigantaj grandecoj kaj havigi aliron al ĝi de kodo gastigita sur malsamaj serviloj. Tamen, en nia situacio, la volumo de stokitaj datumoj ne superas plurajn gigabajtojn, kaj la agado de unu aŭ du nodoj sufiĉas. Sekve, la nuraj utilaj normaj iloj povus esti certigi la haveblecon de memcached konservante almenaŭ unu kaŝmemoro en funkcia stato. Tamen ne eblis profiti eĉ ĉi tiun ŝancon... Ĉi tie indas rememori la antikvaĵon de la kadro uzata en la projekto, tial estis neeble ekfunkciigi la aplikaĵon kun aro da serviloj. Ni ankaŭ ne forgesu pri la perdo de sesiaj datumoj: la okulo de la kliento ekmoviĝis pro la amasa elsaluto de uzantoj.

Ideale ĝi estis postulata reproduktado de rekordoj en memcached kaj preterirantaj kopioj en kazo de eraro aŭ eraro. Helpis nin efektivigi ĉi tiun strategion mcrouter.

mcrouter

Ĉi tio estas memcached-enkursigilo evoluigita de Facebook por solvi ĝiajn problemojn. Ĝi subtenas la memcached tekstprotokolo, kiu permesas skalo memcached instalaĵoj al frenezaj proporcioj. Detala priskribo de mcrouter troviĝas en ĉi tiu anonco. Inter aliaj aferoj larĝa funkcieco ĝi povas fari tion, kion ni bezonas:

  • kopii rekordon;
  • faru falo al aliaj serviloj en la grupo se eraro okazas.

Eklaboru!

mcrouter-agordo

Mi iros rekte al la agordo:

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

Kial tri naĝejoj? Kial serviloj ripetas? Ni eltrovu kiel ĝi funkcias.

  • En ĉi tiu agordo, mcrouter elektas la vojon al kiu la peto estos sendita surbaze de la peta komando. La ulo rakontas al li ĉi tion OperationSelectorRoute.
  • GET petoj iras al la prizorganto RandomRoutekiu hazarde elektas naĝejon aŭ itineron inter tabelobjektoj children. Ĉiu elemento de ĉi tiu tabelo estas siavice prizorganto MissFailoverRoute, kiu trairos ĉiun servilon en la naĝejo ĝis ĝi ricevos respondon kun datumoj, kiuj estos resenditaj al la kliento.
  • Se ni uzis ekskluzive MissFailoverRoute kun aro de tri serviloj, tiam ĉiuj petoj venus unue al la unua memcached-instanco, kaj la ceteraj ricevus petojn sur resta bazo kiam ne ekzistas datumoj. Tia aliro kondukus al troa ŝarĝo sur la unua servilo en la listo, do estis decidite generi tri naĝejojn kun adresoj en malsamaj sekvencoj kaj elekti ilin hazarde.
  • Ĉiuj aliaj petoj (kaj ĉi tio estas rekordo) estas procesitaj uzante AllMajorityRoute. Ĉi tiu prizorganto sendas petojn al ĉiuj serviloj en la naĝejo kaj atendas respondojn de almenaŭ N/2 + 1 el ili. De uzo AllSyncRoute por skribaj operacioj devis esti forlasitaj, ĉar ĉi tiu metodo postulas pozitivan respondon de всех serviloj en la grupo - alie ĝi revenos SERVER_ERROR. Kvankam mcrouter aldonos la datumojn al disponeblaj kaŝmemoroj, la vokanta PHP-funkcion resendos eraron kaj generos avizon. AllMajorityRoute ne estas tiel strikta kaj permesas ĝis duonon de la unuoj esti forigitaj sen la problemoj priskribitaj supre.

Ĉefa malavantaĝo Ĉi tiu skemo estas ke se vere ne estas datumoj en la kaŝmemoro, tiam por ĉiu peto de la kliento N petoj al memcached efektive estos ekzekutitaj - al al ĉiuj serviloj en la naĝejo. Ni povas redukti la nombron da serviloj en naĝejoj, ekzemple, al du: oferante konservadon fidindecon, ni ricevasоpli alta rapideco kaj malpli da ŝarĝo de petoj ĝis mankantaj ŝlosiloj.

NB: Vi povas ankaŭ trovi utilajn ligilojn por lerni mcrouter dokumentado en vikio и problemoj de projekto (inkluzive de fermitaj), reprezentante tutan magazenon de diversaj agordoj.

Konstruante kaj funkciigante mcrouter

Nia aplikaĵo (kaj memcached mem) funkcias en Kubernetes - sekve ankaŭ mcrouter troviĝas tie. Por ujo asembleo ni uzas werf, la agordo por kiu aspektos jene:

NB: La listoj donitaj en la artikolo estas publikigitaj en la deponejo 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)

... kaj skizu ĝin Helm-diagramo. La interesa afero estas, ke ekzistas nur agorda generatoro bazita sur la nombro da kopioj (se iu havas pli lakonan kaj elegantan opcion, dividu ĝin en la komentoj):

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

Ni ruliĝas ĝin en la testan medion kaj kontrolas:

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

Serĉi la tekston de la eraro ne donis rezultojn, sed la demandon “mcrouter php"En la avangardo estis la plej malnova nesolvita problemo de la projekto - manko de subteno memcached binara protokolo.

NB: La ASCII-protokolo en memcached estas pli malrapida ol la binara, kaj normaj rimedoj de konsekvenca klavhasado funkcias nur kun la binara protokolo. Sed ĉi tio ne kreas problemojn por specifa kazo.

La lertaĵo estas en la sako: ĉio, kion vi devas fari, estas ŝanĝi al la ASCII-protokolo kaj ĉio funkcios.... Tamen, en ĉi tiu kazo, la kutimo serĉi respondojn en dokumentado en php.net ludis kruelan ŝercon. Vi ne trovos la ĝustan respondon tie... krom se, kompreneble, vi rulumas ĝis la fino, kie en la sekcio "Uzanto kontribuis notojn" estos fidela kaj maljuste malaprobita respondo.

Jes, la ĝusta opcionomo estas memcached.sess_binary_protocol. Ĝi devas esti malŝaltita, post kio la sesioj ekfunkcios. Restas nur meti la ujon kun mcrouter en pod kun PHP!

konkludo

Tiel, per nur infrastrukturaj ŝanĝoj ni povis solvi la problemon: la problemo kun memcached-faŭltoleremo estis solvita, kaj la fidindeco de kaŝmemoro estis pliigita. Krom la evidentaj avantaĝoj por la aplikaĵo, ĉi tio donis spacon por manovro dum laboro sur la platformo: kiam ĉiuj komponantoj havas rezervon, la vivo de la administranto estas tre simpligita. Jes, ĉi tiu metodo ankaŭ havas siajn malavantaĝojn, ĝi povas aspekti kiel "lambastono", sed se ĝi ŝparas monon, enterigas la problemon kaj ne kaŭzas novajn - kial ne?

PS

Legu ankaŭ en nia blogo:

fonto: www.habr.com

Aldoni komenton