Paggamit ng mcrouter upang sukatin ang memcached nang pahalang

Paggamit ng mcrouter upang sukatin ang memcached nang pahalang

Ang pagbuo ng mga proyektong may mataas na karga sa anumang wika ay nangangailangan ng isang espesyal na diskarte at ang paggamit ng mga espesyal na tool, ngunit pagdating sa mga application sa PHP, ang sitwasyon ay maaaring maging labis na pinalala na kailangan mong bumuo, halimbawa, sariling application server. Sa talang ito ay pag-uusapan natin ang pamilyar na sakit sa ibinahagi na pag-iimbak ng session at data caching sa memcached at kung paano namin nalutas ang mga problemang ito sa isang "ward" na proyekto.

Ang bayani ng okasyon ay isang PHP application batay sa symfony 2.3 framework, na hindi kasama sa mga plano sa negosyo na i-update. Bilang karagdagan sa medyo karaniwang imbakan ng session, ang proyektong ito ay lubos na gumamit ng "pag-cache ng lahat" na patakaran sa memcached: mga tugon sa mga kahilingan sa database at mga server ng API, iba't ibang mga flag, mga kandado para sa pag-synchronize ng pagpapatupad ng code at marami pang iba. Sa ganoong sitwasyon, ang pagkasira ng memcached ay nagiging nakamamatay sa pagpapatakbo ng application. Bilang karagdagan, ang pagkawala ng cache ay humahantong sa mga malubhang kahihinatnan: ang DBMS ay nagsisimulang sumabog sa mga seams, ang mga serbisyo ng API ay nagsimulang mag-ban ng mga kahilingan, atbp. Ang pagpapatatag sa sitwasyon ay maaaring tumagal ng sampu-sampung minuto, at sa panahong ito ang serbisyo ay magiging napakabagal o ganap na hindi magagamit.

Kinailangan naming magbigay ang kakayahang pahalang na sukatin ang application na may kaunting pagsisikap, ibig sabihin. na may kaunting pagbabago sa source code at napanatili ang buong functionality. Gawing hindi lamang lumalaban ang cache sa mga pagkabigo, ngunit subukan din na mabawasan ang pagkawala ng data mula dito.

Ano ang mali sa memcached mismo?

Sa pangkalahatan, ang memcached extension para sa PHP ay sumusuporta sa distributed data at session storage out of the box. Ang mekanismo para sa pare-parehong pag-hash ng key ay nagbibigay-daan sa iyo na pantay-pantay na maglagay ng data sa maraming mga server, natatanging tinutugunan ang bawat partikular na key sa isang partikular na server mula sa grupo, at ang mga built-in na failover tool ay nagsisiguro ng mataas na kakayahang magamit ng serbisyo sa pag-cache (ngunit, sa kasamaang-palad, walang data).

Ang mga bagay ay medyo mas mahusay sa imbakan ng session: maaari mong i-configure memcached.sess_number_of_replicas, bilang isang resulta kung saan ang data ay maiimbak sa ilang mga server nang sabay-sabay, at sa kaganapan ng isang pagkabigo ng isang memcached na halimbawa, ang data ay ililipat mula sa iba. Gayunpaman, kung ang server ay babalik online nang walang data (tulad ng karaniwang nangyayari pagkatapos ng pag-restart), ang ilan sa mga susi ay muling ipapamahagi sa pabor nito. Sa katunayan ito ay nangangahulugan pagkawala ng data ng session, dahil walang paraan para "pumunta" sa isa pang replika kung sakaling mahuli.

Ang mga karaniwang kagamitan sa aklatan ay pangunahing nakatuon sa pahalang scaling: pinapayagan ka nitong dagdagan ang cache sa napakalaking laki at magbigay ng access dito mula sa code na naka-host sa iba't ibang mga server. Gayunpaman, sa aming sitwasyon, ang dami ng nakaimbak na data ay hindi lalampas sa ilang gigabytes, at ang pagganap ng isa o dalawang node ay sapat na. Alinsunod dito, ang tanging kapaki-pakinabang na karaniwang mga tool ay upang matiyak ang pagkakaroon ng memcached habang pinapanatili ang hindi bababa sa isang instance ng cache sa kondisyon ng pagtatrabaho. Gayunpaman, hindi posible na samantalahin ang kahit na ang pagkakataong ito... Narito ito ay nagkakahalaga ng paggunita sa antiquity ng framework na ginamit sa proyekto, kung kaya't imposibleng makuha ang application na gumana sa isang pool ng mga server. Huwag din nating kalimutan ang tungkol sa pagkawala ng data ng session: ang mata ng customer ay kumibot mula sa napakalaking pag-logout ng mga user.

Sa isip, ito ay kinakailangan pagtitiklop ng mga tala sa memcached at bypassing replicas sa kaso ng pagkakamali o pagkakamali. Nakatulong sa amin na ipatupad ang diskarteng ito mcrouter.

mcrouter

Ito ay isang memcached router na binuo ng Facebook upang malutas ang mga problema nito. Sinusuportahan nito ang memcached text protocol, na nagpapahintulot scale memcached installation sa nakakabaliw na sukat. Ang isang detalyadong paglalarawan ng mcrouter ay matatagpuan sa announcement na ito. Bukod sa iba pang mga bagay malawak na pag-andar magagawa nito ang kailangan natin:

  • kopyahin ang talaan;
  • gawin ang fallback sa ibang mga server sa grupo kung may nangyaring error.

Bumaba sa negosyo!

pagsasaayos ng mcrouter

Diretso ako sa config:

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

Bakit tatlong pool? Bakit paulit-ulit ang mga server? Alamin natin kung paano ito gumagana.

  • Sa pagsasaayos na ito, pinipili ng mcrouter ang landas kung saan ipapadala ang kahilingan batay sa utos ng kahilingan. Sinabi ito ng lalaki sa kanya OperationSelectorRoute.
  • Ang mga kahilingan sa GET ay mapupunta sa handler RandomRoutena random na pumipili ng pool o ruta sa mga array object children. Ang bawat elemento ng array na ito ay isa namang handler MissFailoverRoute, na dadaan sa bawat server sa pool hanggang makatanggap ito ng tugon na may data, na ibabalik sa kliyente.
  • Kung ginamit namin ng eksklusibo MissFailoverRoute na may isang pool ng tatlong mga server, pagkatapos ang lahat ng mga kahilingan ay mauna sa unang memcached instance, at ang iba ay makakatanggap ng mga kahilingan sa isang natitirang batayan kapag walang data. Ang ganitong paraan ay hahantong sa labis na pagkarga sa unang server sa listahan, kaya napagpasyahan na bumuo ng tatlong pool na may mga address sa iba't ibang pagkakasunud-sunod at piliin ang mga ito nang random.
  • Ang lahat ng iba pang mga kahilingan (at ito ay isang tala) ay pinoproseso gamit AllMajorityRoute. Ang handler na ito ay nagpapadala ng mga kahilingan sa lahat ng mga server sa pool at naghihintay ng mga tugon mula sa hindi bababa sa N/2 + 1 sa kanila. Mula sa paggamit AllSyncRoute para sa mga operasyon ng pagsulat ay kailangang iwanan, dahil ang pamamaraang ito ay nangangailangan ng isang positibong tugon mula sa lahat mga server sa grupo - kung hindi ay babalik ito SERVER_ERROR. Bagama't idaragdag ng mcrouter ang data sa mga available na cache, ang paggana ng PHP sa pagtawag magbabalik ng error at bubuo ng paunawa. AllMajorityRoute ay hindi masyadong mahigpit at pinapayagan ang hanggang kalahati ng mga yunit na alisin sa serbisyo nang walang mga problemang inilarawan sa itaas.

Ang pangunahing kawalan Ang scheme na ito ay kung talagang walang data sa cache, kung gayon para sa bawat kahilingan mula sa kliyente N kahilingan sa memcached ay talagang isasagawa - upang sa lahat mga server sa pool. Maaari naming bawasan ang bilang ng mga server sa mga pool, halimbawa, sa dalawa: pagsasakripisyo sa pagiging maaasahan ng storage, nakukuha naminΠΎmas mataas na bilis at mas kaunting pag-load mula sa mga kahilingan hanggang sa mga nawawalang key.

NB: Maaari ka ring makahanap ng mga kapaki-pakinabang na link para sa pag-aaral ng mcrouter dokumentasyon sa wiki ΠΈ mga isyu sa proyekto (kabilang ang mga sarado), na kumakatawan sa isang buong kamalig ng iba't ibang mga pagsasaayos.

Pagbuo at pagpapatakbo ng microuter

Ang aming application (at memcached mismo) ay tumatakbo sa Kubernetes - ayon dito, ang mcrouter ay matatagpuan din doon. Para sa pagpupulong ng lalagyan ginagamit namin werf, ang config kung saan magiging ganito:

NB: Ang mga listahang ibinigay sa artikulo ay nai-publish sa repositoryo 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)

... at i-sketch ito Helm chart. Ang kawili-wiling bagay ay mayroon lamang isang config generator batay sa bilang ng mga replika (kung sinuman ang may mas laconic at eleganteng opsyon, ibahagi ito sa mga komento):

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

Ilalabas namin ito sa kapaligiran ng pagsubok at suriin:

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

Ang paghahanap para sa teksto ng error ay hindi nagbigay ng anumang mga resulta, ngunit para sa query na "microuter php"Nasa unahan ang pinakamatandang hindi nalutas na problema ng proyekto - kakulangan ng suporta memcached binary protocol.

NB: Ang ASCII protocol sa memcached ay mas mabagal kaysa sa binary, at ang karaniwang paraan ng pare-parehong key hashing ay gumagana lamang sa binary protocol. Ngunit hindi ito lumilikha ng mga problema para sa isang partikular na kaso.

Ang trick ay nasa bag: ang kailangan mo lang gawin ay lumipat sa ASCII protocol at lahat ay gagana.... Gayunpaman, sa kasong ito, ang ugali ng paghahanap ng mga sagot sa dokumentasyon sa php.net naglaro ng malupit na biro. Hindi mo mahahanap ang tamang sagot doon... maliban kung, siyempre, mag-scroll ka hanggang sa dulo, kung saan sa seksyon "Mga tala na iniambag ng user" magiging tapat at unfairly downvoted na sagot.

Oo, ang tamang pangalan ng opsyon ay memcached.sess_binary_protocol. Dapat itong hindi pinagana, pagkatapos nito ay magsisimulang gumana ang mga session. Ang natitira na lang ay ilagay ang lalagyan na may mcrouter sa isang pod na may PHP!

Konklusyon

Kaya, sa pamamagitan lamang ng mga pagbabago sa imprastraktura, nalutas namin ang problema: ang isyu sa memcached fault tolerance ay nalutas na, at ang pagiging maaasahan ng imbakan ng cache ay nadagdagan. Bilang karagdagan sa mga halatang pakinabang para sa aplikasyon, nagbigay ito ng puwang para sa pagmamaniobra kapag nagtatrabaho sa platform: kapag ang lahat ng mga sangkap ay may reserba, ang buhay ng tagapangasiwa ay lubos na pinasimple. Oo, ang pamamaraang ito ay mayroon ding mga disbentaha, maaaring mukhang isang "saklay", ngunit kung makatipid ito ng pera, ibinaon ang problema at hindi nagiging sanhi ng mga bago - bakit hindi?

PS

Basahin din sa aming blog:

Pinagmulan: www.habr.com

Magdagdag ng komento