
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. . 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
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 . Muun muassa 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 joukostachildren. 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
MissFailoverRoutekolmen 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äAllSyncRoutekirjoitustoiminnoista jouduttiin luopumaan, koska tämä menetelmä vaatii positiivisen vastauksen Kaikki palvelimia ryhmässä - muuten se palaaSERVER_ERROR. Vaikka mcrouter lisää tiedot käytettävissä oleviin välimuistiin, kutsuva PHP-toiminto palauttaa virheilmoituksen ja tuottaa ilmoituksen.AllMajorityRouteei 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 и (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 , jonka kokoonpano näyttää tältä:
NB: Artikkelissa annetut luettelot on julkaistu arkistossa .
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' ]()
...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 }}
}
}
}
}()
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 ""Etusijalla oli projektin vanhin ratkaisematon ongelma - 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 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 .
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:
- "Harjoittele dappilla" (käytetään esimerkkinä symfony-demoa): и ;
- «'.
Lähde: will.com
