
Suure koormusega projektide arendamine mis tahes keeles nõuab erilist lähenemist ja spetsiaalsete vahendite rakendamist, kuid kui jutt käib PHP rakendustest, võib olukord muutuda nii tõsiseks, et tuleb arendada näiteks . Käesolevas märkuses käsitletakse kõigile tuttavat muret, mis on seotud sessioonide ja andmete vahemällu salvestamise probleemidega memcachedis, ning seda, kuidas me neid probleeme õppisime lahendama ühes "katse-aluses" projektis.
Tähtsaim asi on PHP rakendus, mis põhineb Symfony 2.3 raamistikul, mille uuendamine ei kuulu äriplaanidesse. Lisaks täiesti standardselt sessioonide salvestamisele kasutas see projekt ulatuslikult kõike ülesse kühveldamise poliitikat memcachedis: vastuseid andmebaasi ja API serverite päringutele, erinevaid lippe, koodide täitmise sünkroniseerimise lukke ja palju muud. Sellises olukorras memcachedi rike muutub rakenduse toimimise jaoks fataalseks. Lisaks kaotatud vahemälu võib põhjustada tõsiseid tagajärgi: andmebaas hakkab murduma, API teenused - keelavad päringud jne. Olukorra stabiliseerimine võib võtta kümneid minuteid, ja selle aja jooksul aeglustub teenus kohutavalt või muutub üldse kättesaamatuks.
Me pidime tagama rakenduse horisontaalse skaleeritavuse minimaalse kuluga, st minimaalsete muudatustega algkoodis ja täieliku funktsionaalsuse säilitamisega. Tee vahemälu mitte ainult rikkenodele vastupidavaks, vaid üritada ka kaotusi minimeerida.
Mis on valesti ise memcachediga?
Tegelikult toetab memcachedi laiendus PHP jaoks „karbist väljas“ andmete ja sessioonide jaotatud salvestamist. Võtme järjepideva hajutamise mehhanism võimaldab andmeid ühtlaselt paigutada mitmele serverile, suunates iga konkreetse võtme kindlale serverile rühmas, samas kui integreeritud varu lahendamiseks mõeldud vahendid tagavad vahemälu teenuse kõrge kättesaadavuse (kuid paraku mitte andmete).
Sessioonide salvestamine on pisut parem: saate seadistada memcached.sess_number_of_replicas, mille tõttu salvestatakse andmed samal ajal mitmele serverile ja kui üks memcached eksemplar ebaõnnestub, antakse andmed teiste kaudu. Kuid kui server naaseb tööle ilma andmeteta (nagu tavaliselt pärast taaskäivitamist), jagatakse osa võtmeid tema kasuks ümber. Praktikas tähendab see seanssiandmete kadu, sest teise koopia juurde „külastamine” ei ole võimalik, kui juhtub tõrge.
Raamatukogu tavalised vahendid on suunatud peamiselt horisontaalsele skaleerimisele: need võimaldavad suurendada vahemälu hiiglaslikesse mõõtmetesse ja tagada sellele juurdepääs erinevates serverites asuvalt koodilt. Kuid meie olukorras ei ületa salvestatud andmete maht mõnda gigabaiti ja kahe sõlme jõudlus on täiesti piisav. Seetõttu võiksid kasulikud standardvahendid vaid tagada memcached'i kättesaadavuse, hoidudes vähemalt ühe aktiivse vahemälukopeerimise säilitamisest. Kuid isegi sellest võimalusest ei õnnestunud kasu lõigata… Siinkohal tuleks meenutada projekti jaoks kasutatud raamistikku seda vanadust, mistõttu ei õnnestunud rakendust serverite rikka meditsiini juhtkonnaga käivitada. Samuti ei tohiks unustada seanssiandmete kadu: tellija massilisest väljalogimise tõttu raputas silm.
Ideaalis oli vajalik kirje replikatsioon memcached'is ja repliikide kasutamine tõrke või vea korral. Selle strateegia elluviimisel aitas meid .
mcrouter
See on memcached'i marsruuter, mille on välja töötanud Facebook oma probleemide lahendamiseks. See toetab tekstiprotokolli memcached, mis võimaldab skaleerida memcached'i paigaldusi ilma haripunktideta. Üksikasjaliku kirjelduse mcrouterist leiate . Lisaks muule suudab see, mida me vajame:
- kirje replitseerimine;
- teistesse serveritesse tagasipöördumine veateateid esineva rühma läbimisel.
Alustame!
mcrouter'i konfiguratsioon
Lähen kohe config'ile:
{
"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, mida mööda saadetakse päring, lähtudes päringu käsust. Selle ütleb talle tüüp
OperationSelectorRoute. - GET-päringud jõuavad töötlejasse
RandomRoute, mis valib juhuslikult puuli või marsruudi massiivi objektide seastchildren. Iga element sellest massiivist on omakorda töötlejaMissFailoverRoute, mis läbib iga serveri puulis, kuni saab vastuse andmetega, mis tagastatakse kliendile. - Kui me peaksime kasutama ainult
MissFailoverRoutekolmest serverist koosnevat puuli, siis kõik päringud jõuaksid esmalt esimese memcached eksemplari juurde ning teised saaksid päringud järgi, kui andmed puuduvad. Selline lähenemine tooks kaasa ülemäärase koormuse nimekirja esimesel serveril, mistõttu otsustati genereerida kolm puuli aadressidega erinevas järjestuses ja valida need juhuslikult. - Kõik muud päringud (see tähendab, kirjutamine) töödeldakse
AllMajorityRoute. Antud töötleja saadab päringud kõikidele puuli serveritele ja ootab vastuseid vähemalt N/2 + 1 neilt. AllSyncRoute kasutamisestkirjutamisoperatsioonide jaoks tuli loobuda, kuna see meetod nõuab grupi serveritelt positiivset vastust — vastasel juhul tagastab seeSERVER_ERROR kõigi . Kuigi sel juhul salvestab mcrouter andmed kergesti kätte saadavatesse vahemäludesse, tagastab kutse PHPveateateja genereerib teadete. ei ole nii ranged ja võimaldab vältida kuni poole sõlmede eemaldamist ilma eespool nimetatud probleemideta. Selle skeemi peamine miinus on see, et kui vahemälus tõesti andmeid ei ole, täidetakse igale kliendi päringule tegelikult N päringut memcached'ile —AllMajorityRouteserveritele puulis. Saame vähendada serverite arvu puulides, näiteks kahele: ohverdades säilitamise usaldusväärsuse, saame b
Peamine puudus selle skeemi puhul on see, et kui vahemikus pole tõeliselt andmeid, siis iga kliendipäringu puhul tehakse tegelikult N päringut memcachedi — serveritele puulis. kõik Pulede serverite arvu saab vähendada, näiteks kaheni: ohverdades andmete salvestamise usaldusväärsuse, saame me bilotkasuurem kiirus ja väiksem koormus kadunud võtmete päringutest.
NB: Kasulikud lingid mcrouteri uurimiseks võivad samuti olla ja (sealhulgas suletud), mis esindavad kogu erinevaid konfiguratsioonide varasalve.
mcrouteri kogumine ja käivitamine
Rakendus (ja ise memcached) töötab meil Kuberneteses - vastavalt seal on ka mcrouter. Kogu konteineri kogumise kasutame , mille konfiguratsioon näeb välja järgmiselt:
NB: Artiklis toodud nimekirjad on avaldatud registris .
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'i chart. Huvi pärast - siin on ainult konfiguratsioonigeneraator replikate arvust (kui kellelgi on lühem ja elegantsem 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, 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 }}
}
}
}
}()
Käivitame testkeskkonnas 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 >Tekstipäringu otsing ei andnud tulemusi, kuid päringu „» seas oli esimeses reas vanim avatud probleem projektis - binaarse memcached protokoll.
NB: ASCII-protokoll memcachedis on aeglasem kui binaarprotokoll ning ka vaikimisi võtme konsistentsuse häsherdamise vahendid töötavad ainult binaarprotokolliga. Kuid see ei tekita konkreetse juhtumi jaoks probleeme.
Asi on kindel: tuleb vaid üle minna ASCII-protokollile ja kõik töötab…. Siiski, sel juhul mängis harjumus otsida vastuseid meile halba nalja. Õiget vastust sealt ei leia… kui, muidugi, sa ei kerida lõpuni, kus sektsioonis "Kasutaja antud märkmed" on õige ja .
Jah, õige valiku nimetuseks on memcached.sess_binary_protocol. Selle peab välja lülitama, pärast mida hakkavad sessioonid töötama. Järgmine samm on panna mcrouter konteiner PHP podi sisse!
Kokkuvõte
Nii suutsime ainult infrastruktuuriliste muudatustega lahendada seatud ülesande: memcachedi tõrketaluvuse probleem on lahendatud, vahemälu säilitamise usaldusväärsus on tõusnud. Peale ilmselgete eeliste rakendusele andis see manööverdusruumi platvormi kallal töötamiseks: kui kõik komponendid on varustatud, lihtsustab see administraatori elu oluliselt. Jah, see meetod omab oma puudusi, võib tunduda "toetav", kuid kui see säästab raha, matab probleemi ja ei tekita uusi — siis miks mitte?
P.S.
Lugege ka meie blogist:
- "Praktika dappiga" (näite põhjal symfony-demo): ja ;
- «».
Allikas: habr.com
