mcrouterin käyttäminen memcachedin skaalaamiseen vaakasuunnassa

mcrouterin käyttäminen memcachedin skaalaamiseen vaakasuunnassa

Raskaiden projektien kehittäminen millä tahansa kielellä vaatii erityistä lähestymistapaa ja erikoistyökalujen käyttöä, mutta PHP:n sovellusten osalta tilanne voi kärjistyä niin, että joudut kehittämään mm. oma sovelluspalvelin. Tässä muistiinpanossa puhumme hajautetun istunnon tallennuksen ja tiedon välimuistin tutusta tuskasta memcachedissa ja kuinka ratkaisimme nämä ongelmat yhdessä ”osaston” projektissa.

Tilaisuuden sankari on symfony 2.3 -kehykseen perustuva PHP-sovellus, joka ei ole ollenkaan mukana päivitettävässä liiketoimintasuunnitelmassa. Varsin tavallisen istuntotallennustilan lisäksi tämä projekti hyödynsi täysimääräisesti "kaiken välimuisti" -käytäntö memcachedissa: vastaukset tietokannan ja API-palvelimien pyyntöihin, erilaiset liput, lukot koodin suorittamisen synkronointiin ja paljon muuta. Tällaisessa tilanteessa memcachedin hajoamisesta tulee kohtalokas sovelluksen toiminnalle. Lisäksi välimuistin katoaminen johtaa vakaviin seurauksiin: DBMS alkaa räjähtää saumoista, API-palvelut alkavat estää pyyntöjä jne. Tilanteen vakauttaminen voi kestää kymmeniä minuutteja, ja tänä aikana palvelu on kamalan hidasta tai kokonaan poissa käytöstä.

Meidän piti tarjota kyky skaalata sovellus vaakasuunnassa pienellä vaivalla, eli lähdekoodiin on tehty vain vähän muutoksia ja kaikki toiminnot säilyvät. Tee välimuistista ei vain kestänyt vikoja, vaan yritä myös minimoida siitä aiheutuva tietojen menetys.

Mitä vikaa itse memcachedissa on?

Yleensä välimuistissa oleva PHP-laajennus tukee hajautettua dataa ja istuntojen tallennusta heti valmiina. Johdonmukaisen avainten hajautusmekanismin avulla voit sijoittaa tietoja tasaisesti useille palvelimille osoittaen yksilöllisesti jokaisen tietyn avaimen tietylle palvelimelle ryhmästä, ja sisäänrakennetut vikasietotyökalut varmistavat välimuistipalvelun korkean käytettävyyden (mutta valitettavasti ei dataa).

Asiat ovat hieman paremmin istunnon tallennustilan kanssa: voit määrittää memcached.sess_number_of_replicas, jonka seurauksena tiedot tallennetaan useille palvelimille kerralla ja yhden välimuistiin tallennetun ilmentymän epäonnistuessa tiedot siirretään muilta. Jos palvelin kuitenkin palaa online-tilaan ilman tietoja (kuten yleensä tapahtuu uudelleenkäynnistyksen jälkeen), osa avaimista jaetaan uudelleen sen eduksi. Itse asiassa tämä tarkoittaa istuntotietojen katoaminen, koska ei ole mahdollista "mennä" toiseen replikaan, jos se puuttuu.

Tavalliset kirjastotyökalut on tarkoitettu pääasiassa vaakasuoraan skaalaus: niiden avulla voit kasvattaa välimuistin jättimäisiin kokoihin ja tarjota pääsyn siihen eri palvelimilla isännöidyn koodin avulla. Kuitenkin tilanteessamme tallennetun tiedon määrä ei ylitä useita gigatavuja, ja yhden tai kahden solmun suorituskyky riittää. Näin ollen ainoat hyödylliset vakiotyökalut voisivat olla memcachedin käytettävyyden varmistaminen samalla kun vähintään yksi välimuistiinstanssi säilyy toimintakunnossa. Tätäkään tilaisuutta ei kuitenkaan voitu hyödyntää... Tässä kannattaa muistaa projektissa käytetyn kehyksen ikivanha, minkä vuoksi sovellusta oli mahdotonta saada toimimaan palvelinpoolin kanssa. Älkäämme myöskään unohtako istuntotietojen menetystä: asiakkaan silmät nykivät käyttäjien massiivisesta uloskirjautumisesta.

Ihannetapauksessa sitä vaadittiin välimuistissa olevien tietueiden replikointi ja replikoiden ohittaminen virheen tai virheen sattuessa. Auttoi meitä toteuttamaan tämän strategian mcrouter.

mcrouter

Tämä on memcached-reititin, jonka Facebook on kehittänyt ratkaisemaan ongelmansa. Se tukee välimuistissa olevaa tekstiprotokollaa, joka mahdollistaa mittakaavassa välimuistiin tallennetut asennukset hulluihin mittasuhteisiin. Yksityiskohtainen kuvaus mcrouterista löytyy osoitteesta tämä ilmoitus. Muun muassa laaja toiminnallisuus se voi tehdä mitä tarvitsemme:

  • kopioi tietue;
  • palaa muihin ryhmän palvelimiin, jos tapahtuu virhe.

Päästään asiaan!

mcrouterin kokoonpano

Siirryn suoraan asetuksiin:

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

Miksi kolme allasta? Miksi palvelimet toistuvat? Selvitetään, miten se toimii.

  • Tässä kokoonpanossa mcrouter valitsee pyyntökomennon perusteella polun, johon pyyntö lähetetään. Mies kertoo tämän OperationSelectorRoute.
  • GET-pyynnöt menevät käsittelijälle RandomRoutejoka valitsee satunnaisesti poolin tai reitin taulukkoobjektien joukosta children. Jokainen tämän taulukon elementti on puolestaan ​​käsittelijä MissFailoverRoute, joka kulkee jokaisen poolin palvelimen läpi, kunnes se saa vastauksen datalla, joka palautetaan asiakkaalle.
  • Jos käytämme yksinomaan MissFailoverRoute kolmen palvelimen poolissa kaikki pyynnöt tulevat ensin ensimmäiseen välimuistiin tallennettuun esiintymään ja loput vastaanottaisivat pyynnöt jäännöspohjalta, kun tietoja ei ole. Tällainen lähestymistapa johtaisi siihen luettelon ensimmäisen palvelimen liiallinen kuormitus, joten päätettiin luoda kolme poolia, joiden osoitteet ovat eri järjestyksessä, ja valita ne satunnaisesti.
  • Kaikki muut pyynnöt (ja tämä on tietue) käsitellään käyttämällä AllMajorityRoute. Tämä käsittelijä lähettää pyynnöt kaikille poolin palvelimille ja odottaa vastauksia vähintään N/2 + 1:ltä. Käytöstä AllSyncRoute kirjoitustoiminnoista jouduttiin luopumaan, koska tämä menetelmä vaatii positiivisen vastauksen Kaikki palvelimia ryhmässä - muuten se palaa SERVER_ERROR. Vaikka mcrouter lisää tiedot käytettävissä oleviin välimuistiin, kutsuva PHP-toiminto palauttaa virheilmoituksen ja tuottaa ilmoituksen. AllMajorityRoute ei ole niin tiukka ja mahdollistaa jopa puolet yksiköistä poistamisen ilman yllä kuvattuja ongelmia.

Tärkein haitta Tämä kaava on se, että jos välimuistissa ei todellakaan ole dataa, jokaisen asiakkaan pyynnöstä suoritetaan N pyyntöä memcachediin - kaikille palvelimia altaassa. Voimme vähentää palvelinten määrää poolissa esimerkiksi kahteen: uhraamalla tallennusluotettavuuden saammeоsuurempi nopeus ja vähemmän kuormitusta pyynnöistä puuttuviin avaimiin.

NB: Saatat myös löytää hyödyllisiä linkkejä mcrouterin oppimiseen dokumentaatio wikissä и projektikysymyksiä (myös suljetut), jotka edustavat kokonaista varastoa eri kokoonpanoilla.

mcrouterin rakentaminen ja käyttäminen

Sovelluksemme (ja memcached itse) toimii Kubernetesissa - vastaavasti mcrouter sijaitsee myös siellä. varten kontin kokoonpano käytämme werf, jonka kokoonpano näyttää tältä:

NB: Artikkelissa annetut luettelot on julkaistu arkistossa flat/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)

...ja piirrä se Ruorikaavio. Mielenkiintoista on, että on olemassa vain konfiguraatiogeneraattori, joka perustuu replikoiden määrään (jos jollain on lakoninen ja tyylikkäämpi vaihtoehto, jaa se kommenteissa):

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

Julkaisemme sen testiympäristöön ja tarkistamme:

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

Virheen tekstin haku ei tuottanut tulosta, mutta kyselylle "mcrouter php"Etusijalla oli projektin vanhin ratkaisematon ongelma - tuen puute välimuistiin tallennettu binääriprotokolla.

NB: Memcachedin ASCII-protokolla on hitaampi kuin binääri, ja vakiomenetelmät johdonmukaiseen avainten hajautusjärjestelmään toimivat vain binääriprotokollan kanssa. Mutta tämä ei aiheuta ongelmia yksittäisessä tapauksessa.

Temppu on pussissa: sinun tarvitsee vain vaihtaa ASCII-protokollaan ja kaikki toimii.... Kuitenkin tässä tapauksessa tapana etsiä vastauksia dokumentaatio osoitteessa php.net pelasi julman vitsin. Et löydä sieltä oikeaa vastausta... ellet tietenkään vieritä osion loppuun "Käyttäjien tekemät muistiinpanot" tulee olemaan uskollinen ja epäoikeudenmukaisesti miinusäänestetty vastaus.

Kyllä, oikean vaihtoehdon nimi on memcached.sess_binary_protocol. Se on poistettava käytöstä, minkä jälkeen istunnot alkavat toimia. Jäljelle jää vain laittaa kontti mcrouterilla PHP-koteloon!

Johtopäätös

Näin ollen pystyimme ratkaisemaan ongelman pelkillä infrastruktuurimuutoksilla: välimuistiin tallennetun vikasietokyvyn ongelma on ratkaistu ja välimuistin tallennuksen luotettavuutta on lisätty. Sovelluksen ilmeisten etujen lisäksi tämä antoi liikkumavaraa alustalla työskennellessä: kun kaikilla komponenteilla on varaus, järjestelmänvalvojan elämä yksinkertaistuu huomattavasti. Kyllä, tällä menetelmällä on myös haittapuolensa, se voi näyttää "sauvolta", mutta jos se säästää rahaa, hautaa ongelman eikä aiheuta uusia - miksi ei?

PS.

Lue myös blogistamme:

Lähde: will.com

Lisää kommentti