
Suure koormusega projektide arendamine igasugustes keeltes nõuab erilist lähenemist ja spetsiaalsete tööriistade kasutamist, kuid kui on juttu PHP rakendustest, võib olukord muutuda nii teravaks, et tuleb välja arendada näiteks . Selles märkmes räägime tuttavast murest seoses jaotatud sessioonide salvestamise ja andmete vahemällu salvestamisega memcachedis ning sellest, kuidas me neid probleeme ühes "alustalas" projekti lahendasime.
Jubeldaja on PHP rakendus, mis põhineb frameworkil symfony 2.3, mille uuendamine ei ole äri plaanidesse üldse kaasatud. Lisaks täiesti standardsele sessioonide salvestamisele kasutati sellel projektil samuti "kõike vahemällu salvestamise" põhimõtet memcached: vastuseid DB ja API-serverite päringutele, erinevaid lippusid, koodijooksu sünkroniseerimise blokeeringuid ja palju muud. Sellises olukorras memcached purunemine võib rakenduse töö jaoks olla fataalne. Pealegi viib vahemälu kadumine tõsiste tagajärgedeni: DB hakkab lagunema, API-teenused keelavad päringud jne. Situatsiooni stabiliseerimine võib võtta kümneid minuteid, samal ajal kui teenus töötab aeglaselt või muutub täiesti kättesaamatuks.
Me pidime tagama rakenduse horisontaalse skaleeritavuse minimaalse vaevaga, st minimaalsete muudatustega algkoodis ja täieliku funktsionaalsuse säilitamisega. Teha vahemälu mitte ainult tõrketaluvaks, vaid ka püüda minimeerida sealset andmete kadu.
Mis on valesti memcached-iga?
Esmalt, PHP memcached laiendus „kastist välja” toetab andmete ja sessioonide jaotatud salvestamist. Ühtse võtme hashimise mehanism võimaldab andmeid ühtlaselt jaotada paljudele serveritele, suunates iga konkreetse võtme kindlale serverile grupis, ja sisseehitatud failover-meetodid tagavad kõrge kättesaadavuse vahemälu teenusele (kuid kahjuks, ei andmeid).
Sessioonide salvestamisega on lood natuke paremad: saab seadistada memcached.sess_number_of_replicas, mille tulemusel andmed salvestatakse koheselt mitmesse serverisse ja kui üks memcached eksemplar ebaõnnestub, antakse andmed teistest. Siiski, kui server naaseb tööle ilma andmeteta (nagu tavaliselt pärast taaskäivitamist), jagatakse osa võtmetest tema kasuks. Tegelikult tähendaks see sessioonide andmete kadu, kuna pole võimalik „minna” teise koopia juurde, kui esialgne ei toimi.
Tehnilise teegi standardvahendid on suunatud peamiselt horisontaalsele skaleerimine: need võimaldavad suurendada vahemälu tohutute mõõtmeteni ja tagada sellele juurdepääs erinevates serverites hostitud koodist. Siiski ei ületa meie olukorras salvestatud andmete maht mitut gigabaiti, ja ühe või kahe sõlme jõudlus on täiesti piisav. Seega, kasulikud vaikimisi tööriistad võiksid tagada ainult memcached'i kättesaadavuse, hoides vähemalt üht vahemälu eksemplari töövalmis. Kuid isegi seda võimalust polnud meil võimalik kasutada... Siinkohal tuleb meenutada projekti kasutatud raamistiku vanust, mistõttu ei õnnestunud rakendust serverite kogumiga käivitada. Ärme unusta ka sessioonide andmete kaotusi: tellija kasutajate massiline väljalogimine põhjustas silmade tõmblemist.
Ideaalis oleks olnud vajalik kande rehkendamine memcached'is ja replikate vältimine õnnestumise või vea korral. Selle strateegia rakendamisel aitas meid .
mcrouter
See on memcached'i marsruuter, mille on välja töötanud Facebook oma probleemide lahendamiseks. See toetab memcached'i tekstiprotokolli, mis võimaldab skaleerida memcached'i installatsioone uskumatult suuruseks. Üksikasjalik kirjeldus mcrouterist on saadaval . Lisaks muule saab ta teha seda, mida me vajame:
- salvestuse replikatsioon;
- teha fallback grupi teistele serveritele viga tekkimise korral.
Ees on töö!
mcrouteri konfiguratsioon
Küpsen kohe konfiguratsiooni juurde:
{
"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"
]
}
}
}
}Miks on kolm puuli? Miks serverid korduvad? Uurime, kuidas see töötab.
- Antud konfiguratsioonis valib mcrouter tee, mille kaudu päring saadetakse, lähtudes päringu tüübist. Seda ütleb talle tüüp
OperationSelectorRoute. - GET-päringud suunduvad töötlejasse
RandomRoute, mis valib juhuslikult puuli või marsruudi array objektide hulgastchildren. Iga selle massiivi element on omakorda töötlejaMissFailoverRoute, mis läbib iga serveri sõnumist, kuni saab vastuse koos andmetega, mis tagastatakse kliendile. - Kui me kasutaksime ainult
MissFailoverRoutekolmest serverist koosnevat paaki, siis kõik päringud läheksid esmalt esimesele memcached eksemplarile, samal ajal kui ülejäänud saaksid päringud jääkmeetodiga, kui andmed puuduvad. See lähenemine tooks kaasa liigse koormuse nimekirja esimesel serveril, seetõttu otsustati genereerida kolm paaki erinevate aadressides järjekorras ja valida neid juhuslikult. - Kõik ülejäänud päringud (ja see hõlmab kirjutamist) töötlevad
AllMajorityRoute. See töötleja saadab päringud kõigisse paagi serveritesse ja ootab vastuseid vähemalt N/2 + 1 neist. AllSyncRoutekirjutamistegevuste jaoks pidi loobuma, kuna see meetod nõuab positiivset vastustgrupi serveritelt — vastasel juhul tagastab see keskkondades ja projekti klastrites. Selline põhimõte on õige SERVER_ERROR. Kuigi mcrouter salvestab andmed kättesaadavatesse vahemä-storeidesse, tagastab PHP funktsioonvea ja genereerib märkuande. ei ole nii range ja lubab välja kukutada kuni poole sõlmedest ilma eespool kirjeldatud probleemideta.AllMajorityRoutePeamine miinus
Основной минус Selle skeemi probleem seisneb selles, et kui andmeid vahemälus pole, siis iga kliendi päringu korral tehakse tegelikult N päringut memcachedile — serveritele, kõikidest mis on puulis. Serverite arvu puulis saab vähendada, näiteks kahte, loobudes salvestamise usaldusväärsusest, saades suurema kiirus ja väiksema koormuse puuduvate võtmete päringutest.o: Kasulikud lingid mcrouteri uurimiseks võivad samuti olla
NBdokumentatsioon wiki's ja mcrouteri koostamine ja käivitamine
Rakendus (ja memcached) töötab meil Kuberneteses — vastavalt asub seal ka mcrouter. Containeri koostamiseks
kasutame , mille konfigureerimine võib välja näha järgmiselt: : Artiklis toodud loendid on avaldatud repos flant/mcrouter
NB: Листинги, приведённые в статье, опубликованы в репозитории .
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 viskame Helm-chart. Huvi pakkuda – siin on ainult konfiguraator, olenevalt replikate arvust (kui kellelgi on lühem ja elegantsim variant – jagage kommentaarides):
{{- $count := (pluck .Values.global.env .Values.memcached.replicas | first | default .Values.memcached.replicas._default | int) -}}
{{- $pools := dict -}}
{{- $servers := list -}}
{{- /* Täidame massiivi kahe serveri koopiaga: "0 1 2 0 1 2" */ -}}
{{- range until 2 -}}
{{- range $i, $_ := until $count -}}
{{- $servers = append $servers (printf "mc-%d.mc:11211" $i) -}}
{{- end -}}
{{- end -}}
{{- /* Liikudes massiivis edasi, saame N lõike: "[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|poold" $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 }}
}
}
}
}()
Viime tõlkime testkeskkonda ja kontrollime:
# 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 >Veateate sisu otsimine ei andnud tulemusi, kuid päringu „“ esimeses reas mainiti projekti kõige vanemat lahendamata probleemi — memcachedis.
NB: ASCII-protokoll memcachedis on aeglasem kui binaarne protokoll, ning olemasolevad tööriistad võtme konsistentse hajutamise jaoks töötavad ainult binaarse protokolliga. Kuid see ei tekita konkreetse juhtumi jaoks probleeme.
Asi on selge: tuleb vaid vahetada ASCII-protokollile ja kõik töötab... Kuid antud juhul osutus harjumus otsida vastuseid kurvaks naljaks. Seal õiget vastust ei leia... välja arvatud juhul, kui sa jõuad lõpuni, kus sektsioonis „Kasutaja antud märkused“ on õige ja .
Jah, valikute õige nimi on memcached.sess_binary_protocol. See tuleb välja lülitada, pärast mida seansid hakkavad tööle. Jäänud on vaid panna mcrouter konteiner PHP podi!
Kokkuvõte
Nii suutsime ainult infrastruktuuri muudatustega lahendada püstitatud ülesande: memcachedi talitlushäire küsimus on lahendatud, vahemälu säilitamise usaldusväärsus on suurenenud. Peale silmapaistvate eeliste rakendusele andis see ruumi manööverdusvõimeks platvormi arendamise ajal: kui kõikidel komponentidel on varu, lihtsustab see administraatori elu oluliselt. Jah, see meetod omab ka oma puudusi, see võib tunduda „toetav“, kuid kui see säästab raha, peidab probleemi ja ei tekita uusi - miks mitte?
P.S.
Lugege ka meie blogist:
- „Dappi praktika“ (näiteks symfony-demo): ja ;
- «».
Allikas: habr.com
