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 ā€œmicrouter 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

Iegādājieties uzticamu mitināŔanu vietnēm ar DDoS aizsardzÄ«bu, VPS VDS serveriem šŸ”„ Iegādājieties uzticamu tÄ«mekļa vietņu mitināŔanu ar DDoS aizsardzÄ«bu, VPS VDS serveriem | ProHoster