Izmantojam mcrouter horizontālai memcached mērogēšanai

Izmantojam mcrouter horizontālai memcached mērogēšanai

Augstas slodzes projektu izstrāde, izmantojot jebkuru valodu, prasa īpašu pieeju un speciālu rīku lietošanu. Taču, runājot par PHP lietojumprogrammām, situācija var kļūt tik sarežģīta, ka ir nepieciešams izstrādāt, piemēram, pašu lietojumprogrammu serveri. Šajā rakstā tiks apspriesta visiem zināmā problēma ar sesiju izkliedētu glabāšanu un datu kešošanu memcached, kā arī tas, kā mēs risinājām šīs problēmas vienā no mūsu 'pacientu' projektiem.

Svētku varonis ir PHP lietojumprogramma, kas balstās uz Symfony 2.3, kuras atjaunināšana biznesa plānos nav iekļauta. Papildus standarta sesiju glabāšanai šajā projektā aktīvi tika izmantota kēšošanas politika memcached: atbildes uz pieprasījumiem uz datu bāzēm un API serveriem, dažādi karogi, kodu izpildes sinhronizācijas bloķēšanas un daudz kas cits. Šādā situācijā memcached kļūme kļūst kritiska lietojumprogrammas darbībai. Turklāt, keša zudums rada nopietnas sekas: datu bāze sāk sabrukt, API pakalpojumi – bloķē pieprasījumus utt. Situācijas stabilizācija var aizņemt desmitiem minūšu, un šajā laikā pakalpojums darbosies ļoti lēni vai kļūs pilnīgi nepieejams.

Mums bija nepieciešams nodrošināt iespēju horizontāli paplašināt lietojumprogrammu ar minimālām izmaiņām sākotnējā kodā un pilnīgu funkcionalitātes saglabāšanu. Jāizveido kešatmiņa, kas ir ne tikai izturīga pret kļūdām, bet arī jāmēģina minimizēt datu zudumus no tās., t.i. ar minimāliem izmaiņām avota kodā un pilnīgu funkcionalitātes saglabāšanu. Izveidot kešu ne tikai izturīgu pret kļūdām, bet arī noteikti samazināt datu zudumu no tā.

Kas nav kārtībā ar pašu memcached?

Patiesībā memcached paplašinājums PHP 'no kastes' atbalsta izkliedētu datu un sesiju glabāšanu. Atbilstošā taustiņu konsekventa hashēšana ļauj vienmērīgi izvietot datus uz vairākiem serveriem, skaidri adresējot katru konkrēto taustiņu uz noteiktu serveri grupā, bet iebūvētie failover rīki nodrošina augstu kešošanas pakalpojuma pieejamību (bet, diemžēl, ne datus.).

Sesiju glabāšana ir nedaudz labāka: varat iestatīt memcached.sess_number_of_replicas, kā rezultātā dati tiks saglabāti uz vairākām serveriem, un viena memcached instances atteikuma gadījumā dati tiks nodoti no citām. Tomēr, ja servers atgriežas darbībā bez datiem (kā parasti notiek pēc restartēšanas), daļa atslēgu tiks pārdalīta par labu tam. Faktiski tas nozīmēs sesijas datu zudumu, jo nav iespēju "doties" uz citu replikāciju gadījumā, ja rodas kļūda.

Standarta bibliotēkas rīki ir vērsti, galvenokārt, uz horizontālo scalēšanu: tie ļauj palielināt kešu līdz gigantiskām dimensijām un nodrošināt piekļuvi tam no koda, kas izvietots dažādos serveros. Tomēr mūsu situācijā uzglabāto datu apjoms nepārsniedz dažus gigabaitus, un viena vai divu mezglu produktivitāte ir pilnīgi pietiekama. Attiecīgi no noderīgā standarta rīki varētu tikai nodrošināt memcached pieejamību, saglabājot vismaz vienu keša instanci darba kārtībā. Tomēr pat šo iespēju izmantot neizdevās... Jāatgādina par projekta ietvarā izmantotā rāmja novecošanu, kuru dēļ nebija iespējams likt strādāt lietojumprogrammu ar serveru baseinu. Neaizmirsīsim arī par sesiju datu zudumiem: no masveida lietotāju izrakstīšanās klients sajuta to.

Ideālā gadījumā bija nepieciešama rekliācijas ieraksta memcached un replikāciju apiet gadījumā, ja rodas kļūda vai kļūda. Šīs stratēģijas īstenošana mums palīdzēja mcrouter.

mcrouter

Tas ir memcached maršrutētājs, ko izstrādājusi Facebook, lai atrisinātu savas problēmas. Tas atbalsta memcached teksta protokolu, kas ļauj skalēšanas memcached instalācijas līdz nepamatītiem izmēriem. Detalizētu aprakstu par mcrouter var atrast šajā paziņojumā. Papildus citai plašai funkcionalitātei tas var paveikt to, kas mums nepieciešams:

  • reklēšanas ierakstu;
  • veikt fallback uz citām serveru grupas gadījumā kļūdas rašanas.

Uz priekšu!

mcrouter konfigurācija

Pāriesim uz konfigurāciju:

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

Kāpēc trīs baseini? Kāpēc serveri atkārtojas? Apskatīsim, kā tas darbojas.

  • Šajā konfigurācijā mcrouter izvēlas ceļu, pa kuru tiks nosūtīts pieprasījums, pamatojoties uz pieprasījuma komandu. Par to viņam saka tips OperationSelectorRoute.
  • GET pieprasījumi nonāk apstrādē RandomRoute, kas nejauši izvēlas baseinu vai maršrutu no masīva objektu children. Katrs šī masīva elements savukārt ir apstrādātājs MissFailoverRoute, kas izies cauri katram serverim baseinā, kamēr neiegūs atbildi ar datiem, kas tiks atgriezta klientam.
  • Ja mēs izmantotu tikai MissFailoverRoute ar trīs serveriem bāzē, tad visi pieprasījumi vispirms nonāktu pirmajā memcached instancē, bet pārējie saņemtu pieprasījumus pēc atlikuma principa, kad dati nav pieejami. Šāda pieeja novestu pie pārmērīgas slodzes pirmajam saraksta serverim, tāpēc tika nolemts ģenerēt trīs baseinus ar adresēm atšķirīgā secībā un izvēlēties tos nejauši.
  • Visi pārējie pieprasījumi (t.i., rakstīšana) tiek apstrādāti, izmantojot AllMajorityRoute. Šis apstrādātājs sūta pieprasījumus visiem serveriem baseinā un gaida atbildes, vismaz no N/2 + 1 no tiem. Izmantošana AllSyncRoute rakstīšanas operācijām bija jāatmet, jo šī metode prasa pozitīvu atbildi no visi grupas serveriem — pretējā gadījumā tā atgriež SERVER_ERROR. Lai gan šajā gadījumā mcrouter saglabās datus pieejamajos kešos, taču izsaucošā PHP funkcija atgriezīs kļūdu un ģenerēs paziņojumu. AllMajorityRoute nav tik stingrs un ļauj izslēgt līdz puscitu mezglu bez iepriekš aprakstītām problēmām.

Galvenais trūkums šai shēmai ir tas, ka, ja kešā patiešām nav datu, tad katram pieprasījumam no klienta faktiski tiks veikti N pieprasījumi uz memcached — uz visiem servery bāzē. Var samazināt serveru skaitu bāzēs, piemēram, līdz diviem: upurējot uzglabāšanas uzticamību, mēs iegūsim bolielāka ātruma un mazāka slodze uz pieprasījumiem uz neesošām atslēgām.

Piezīme: Noderīgas saites mcrouter izpētei var būt arī dokumentācija wiki un projekta problēmu ziņas (tostarp slēgtās), kas ir īsta dārgumu krātuve dažādām konfigurācijām.

mcrouter montāža un palaišana

Lietojumprogramma (un pats memcached) darbojas Kubernetes — attiecīgi, turpat atrodas arī mcrouter. Lai izveidotu konteineru mēs izmantojam werf, konfigurācija izskatīsies šādi:

Piezīme: Rakstā pārpublicētie saraksti atrodami repozitorijā 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: Uzstādīt priekšnosacījumus
   apt:
     name: [ 'apt-transport-https', 'tzdata', 'locales' ]
     update_cache: yes
 - name: Pievienot mcrouter APT atslēgu
   apt_key:
     url: https://facebook.github.io/mcrouter/debrepo/xenial/PUBLIC.KEY
 - name: Pievienot mcrouter repozitoriju
   apt_repository:
     repo: deb https://facebook.github.io/mcrouter/debrepo/xenial xenial contrib
     filename: mcrouter
     update_cache: yes
 - name: Iestatīt laika joslu
   timezone:
     name: "Europe/Moscow"
 - name: Pārliecināties, ka pastāv valoda
   locale_gen:
     name: en_US.UTF-8
     state: present
 install:
 - name: Instalēt mcrouter
   apt:
     name: [ 'mcrouter' ]

(werf.yaml)

… un uzmetam Helm-chart. No interesantā — šeit ir tikai konfigurācijas ģenerators atkarībā no repliku skaita (ja kādam ir kāds kodolīgāks un elegants risinājums — dalieties komentāros):

{{- $count := (pluck .Values.global.env .Values.memcached.replicas | first | default .Values.memcached.replicas._default | int) -}}
{{- $pools := dict -}}
{{- $servers := list -}}
{{-  Filling the array with two copies of servers: "0 1 2 0 1 2" *\/ -}}
{{- range until 2 -}}
 {{- range $i, $_ := until $count -}}
 {{- $servers = append $servers (printf "mc-%d.mc:11211" $i) -}}
 {{- end -}}
{{- end -}}
{{-  Shifting through the array, we get N slices: "[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)

Izvietojam testēšanas vidē un pārbaudām:

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

Kļūdas teksta meklējumi rezultātu nesniedza, tomēr meklējot „mcrouter php” pirmajās rindās parādījās vecākā neatrisinātā problēma projektā — atbalstošā binārā protokola memcached trūkums. binārā protokola memcached.

Piezīme: ASCII protokols memcached ir lēnāks par bināro, un arī iebūvētie rīki konsekventa atslēgu hashēšanai darbojas tikai ar bināro protokolu. Taču tas konkrētajā gadījumā nerada problēmas.

Jautājums risināts: atliek tikai pāriet uz ASCII protokolu un viss darbosies…. Tomēr šajā gadījumā ieradums meklēt atbildes dokumentācijā php.net spēlē sliktu joks. Pareizu atbildi tur neatradīsiet… ja vien, protams, nesavilksiet līdz beigām, kur sadaļā «Lietotāju sniegtās piezīmes» būs pareiza un nepelnīti negatīvi novērtētā atbilde.

Jā, pareizais opcijas nosaukums ir memcached.sess_binary_protocol. To nepieciešams izslēgt, pēc tam sesijas sāks darboties. Atliek tikai ievietot mcrouter konteineru PHP podā!

Secinājums

Tādējādi ar vienkāršām infrastruktūras izmaiņām izdevās atrisināt anoto jautājumu: jautājums par memcached drošību ir atrisināts, un kešatmiņas glabāšanas uzticamība ir palielinājusies. Papildus acīmredzamajiem ieguvumiem lietojumprogrammā tas radīja manevrēšanas iespējas platformas uzlabošanas procesā: kad visiem komponentiem ir rezerve, administratora dzīve ievērojami atvieglojas. Jā, šai metodei ir arī savi trūkumi, tā var izskatīties kā «Ķīselis», bet, ja tas ietaupa naudu, slēpj problēmu un nerada jaunas — kāpēc gan ne?

P.S.

Lasiet arī mūsu emuāra rakstos:

Avots: habr.com

Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem 🔥 Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem | ProHoster