Izmantojot mcrouter, lai mērogotu memcached horizontāli

Izmantojot mcrouter, lai mērogotu memcached horizontāli

Lai izstrādātu augstas slodzes projektus jebkurā valodā, ir nepieciešama īpaša pieeja un īpašu rīku izmantošana, taču, runājot par lietojumprogrammām PHP, situācija var kļūt tik saasināta, ka jums ir jāizstrādā, piemēram, savs lietojumprogrammu serveris. Šajā piezīmē mēs runāsim par pazīstamajām grūtībām saistībā ar sadalīto sesiju krātuvi un datu kešatmiņu memcached programmā un to, kā mēs šīs problēmas atrisinājām vienā “palātas” projektā.

Notikuma varonis ir PHP lietojumprogramma, kas balstīta uz symfony 2.3 ietvaru, kas nemaz nav iekļauta atjaunināmajos biznesa plānos. Papildus diezgan standarta sesiju krātuvei šis projekts pilnībā izmantoja "visa saglabāšana kešatmiņā". in memcached: atbildes uz pieprasījumiem datu bāzei un API serveriem, dažādi karodziņi, slēdzenes koda izpildes sinhronizēšanai un daudz kas cits. Šādā situācijā memcached bojājums kļūst liktenīgs lietojumprogrammas darbībai. Turklāt kešatmiņas zudums rada nopietnas sekas: DBVS sāk plīst pie šuvēm, API pakalpojumi sāk aizliegt pieprasījumus utt. Situācijas stabilizēšana var aizņemt desmitiem minūšu, un šajā laikā pakalpojums būs šausmīgi lēns vai pilnībā nepieejams.

Mums vajadzēja nodrošināt spēja horizontāli mērogot lietojumprogrammu ar nelielu piepūli, t.i. ar minimālām izmaiņām avota kodā un saglabātu visu funkcionalitāti. Padariet kešatmiņu ne tikai izturīgu pret kļūmēm, bet arī mēģiniet samazināt datu zudumus no tās.

Kas vainas pašam memcached?

Kopumā PHP atmiņai saglabātais paplašinājums atbalsta izkliedētu datu un sesiju krātuvi. Konsekventas atslēgu jaukšanas mehānisms ļauj vienmērīgi izvietot datus daudzos serveros, unikāli adresējot katru konkrēto atslēgu konkrētam serverim no grupas, un iebūvētie kļūmjpārlēces rīki nodrošina augstu kešatmiņas pakalpojuma pieejamību (bet diemžēl nav datu).

Lietas ir nedaudz labākas ar sesiju krātuvi: varat konfigurēt memcached.sess_number_of_replicas, kā rezultātā dati tiks glabāti vairākos serveros vienlaikus, un vienas atmiņā saglabātas instances atteices gadījumā dati tiks pārsūtīti no citiem. Tomēr, ja serveris atgriežas tiešsaistē bez datiem (kā tas parasti notiek pēc restartēšanas), dažas atslēgas tiks pārdalītas tā labā. Patiesībā tas nozīmēs sesijas datu zudums, jo garām palaišanas gadījumā nav iespējas “iet” uz citu repliku.

Standarta bibliotēkas rīki galvenokārt ir paredzēti horizontāli mērogošana: tie ļauj palielināt kešatmiņu līdz milzīgiem izmēriem un nodrošina piekļuvi tai no koda, kas mitināts dažādos serveros. Taču mūsu situācijā saglabāto datu apjoms nepārsniedz vairākus gigabaitus, un viena vai divu mezglu veiktspēja ir pilnīgi pietiekama. Attiecīgi vienīgie noderīgie standarta rīki varētu būt memcached pieejamības nodrošināšana, vienlaikus uzturot vismaz vienu kešatmiņas gadījumu darba stāvoklī. Taču izmantot pat šo iespēju neizdevās... Te der atgādināt projektā izmantotā karkasa senatni, kādēļ nebija iespējams dabūt aplikāciju darbam ar serveru pūlu. Neaizmirsīsim arī par sesijas datu zudumu: klienta acis sarāvās no masveida lietotāju atteikšanās.

Ideālā gadījumā tas bija vajadzīgs Ierakstu replikācija atmiņā saglabātajās un apietajās replikās kļūdas vai kļūdas gadījumā. Palīdzēja mums īstenot šo stratēģiju mcrouter.

mcrouter

Šis ir kešatmiņā saglabāts maršrutētājs, ko Facebook izstrādājis, lai atrisinātu savas problēmas. Tas atbalsta memcached teksta protokolu, kas ļauj mēroga memcached instalācijas neprātīgās proporcijās. Detalizētu mcrouter aprakstu var atrast šis paziņojums. Cita starpā plaša funkcionalitāte tas var darīt to, kas mums nepieciešams:

  • replicēt ierakstu;
  • Ja rodas kļūda, atgriezieties pie citiem grupas serveriem.

Sāciet ķerties pie lietas!

mcrouter konfigurācija

Es pāriešu tieši 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 tiek atkārtoti? Izdomāsim, kā tas darbojas.

  • Šajā konfigurācijā mcrouter atlasa ceļu, uz kuru tiks nosūtīts pieprasījums, pamatojoties uz pieprasījuma komandu. Puisis viņam to stāsta OperationSelectorRoute.
  • GET pieprasījumi tiek nosūtīti apstrādātājam RandomRoutekas nejauši izvēlas kopu vai maršrutu starp masīva objektiem children. Katrs šī masīva elements savukārt ir apstrādātājs MissFailoverRoute, kas iet caur katru pūla serveri, līdz saņems atbildi ar datiem, kas tiks atgriezti klientam.
  • Ja mēs izmantotu tikai MissFailoverRoute ar trīs serveru kopu, tad visi pieprasījumi vispirms tiktu saņemti pirmajai kešatmiņā saglabātajai instancei, bet pārējie saņemtu pieprasījumus pēc atlikuma, kad nav datu. Šāda pieeja novestu pie pārmērīga slodze sarakstā pirmajā serverī, tāpēc tika nolemts ģenerēt trīs pūlus ar adresēm dažādās secībās un atlasīt tos nejauši.
  • Visi pārējie pieprasījumi (un tas ir ieraksts) tiek apstrādāti, izmantojot AllMajorityRoute. Šis apdarinātājs nosūta pieprasījumus visiem pūla serveriem un gaida atbildes no vismaz N/2 + 1 no tiem. No lietošanas AllSyncRoute rakstīšanas operācijām bija jāatsakās, jo šī metode prasa pozitīvu atbildi no viss serveriem grupā - pretējā gadījumā tas atgriezīsies SERVER_ERROR. Lai gan mcrouter pievienos datus pieejamajām kešatmiņām, izsaucošā PHP funkcija atgriezīs kļūdu un radīs paziņojumu. AllMajorityRoute nav tik stingra un ļauj līdz pat pusei vienību izņemt no ekspluatācijas bez iepriekš aprakstītajām problēmām.

Galvenais trūkums Šī shēma ir tāda, ka, ja kešatmiņā patiešām nav datu, tad katram klienta pieprasījumam faktiski tiks izpildīti N pieprasījumi uz memcached - uz viss serveri baseinā. Mēs varam samazināt serveru skaitu baseinos, piemēram, līdz diviem: upurējot krātuves uzticamību, mēs iegūstamоlielāks ātrums un mazāka slodze no pieprasījumiem līdz trūkstošām atslēgām.

NB: Jūs varat atrast arī noderīgas saites mcrouter apguvei dokumentācija wiki и projekta jautājumi (ieskaitot slēgtos), kas pārstāv veselu dažādu konfigurāciju noliktavu.

Mcrouter izveide un darbināšana

Mūsu aplikācija (un pati memcached) darbojas Kubernetes - attiecīgi tur atrodas arī mcrouter. Priekš konteineru montāža mēs izmantojam werf, kuras konfigurācija izskatīsies šādi:

NB: Rakstā sniegtie saraksti tiek publicēti 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: 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)

... un ieskicēt to Stūres diagramma. Interesanti ir tas, ka ir tikai konfigurācijas ģenerators, kas balstīts uz kopiju skaitu (ja kādam ir kāds lakoniskāks un elegantāks variants, padalieties komentāros):

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

Mēs to izlaižam testa 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ēšana nedeva nekādus rezultātus, bet vaicājums “mcrouter php"Priekšgalā bija vecākā neatrisinātā projekta problēma - atbalsta trūkums Memcached binārais protokols.

NB: Memcached ASCII protokols ir lēnāks nekā binārais, un standarta līdzekļi konsekventai atslēgu jaukšanai darbojas tikai ar bināro protokolu. Bet tas nerada problēmas konkrētam gadījumam.

Triks ir maisā: atliek tikai pārslēgties uz ASCII protokolu un viss darbosies.... Tomēr šajā gadījumā ieradums meklēt atbildes dokumentācija vietnē php.net izspēlēja nežēlīgu joku. Pareizo atbildi tur neatradīsi... ja vien, protams, neritināsi līdz beigām, kur sadaļā "Lietotāju iesniegtās piezīmes" būs uzticīgs un negodīgi noraidīta atbilde.

Jā, pareizais opcijas nosaukums ir memcached.sess_binary_protocol. Tas ir jāatspējo, pēc tam sesijas sāks darboties. Atliek tikai ievietot konteineru ar mcrouter podā ar PHP!

Secinājums

Tādējādi tikai ar infrastruktūras izmaiņām mēs spējām atrisināt problēmu: problēma ar atmiņā saglabāto kļūdu toleranci ir atrisināta, un ir palielināta kešatmiņas glabāšanas uzticamība. Papildus acīmredzamajām lietojumprogrammas priekšrocībām tas deva manevrēšanas iespēju, strādājot pie platformas: ja visiem komponentiem ir rezerve, administratora dzīve ir ievērojami vienkāršota. Jā, šai metodei ir arī savi trūkumi, tā var izskatīties pēc “kruķa”, bet, ja tā ietaupa naudu, aprok problēmu un nerada jaunas - kāpēc gan ne?

PS

Lasi arī mūsu emuārā:

Avots: www.habr.com

Pievieno komentāru