
Utveckling av högbelastade projekt pÄ vilket sprÄk som helst krÀver en speciell metodik och anvÀndning av speciella verktyg, men nÀr det handlar om applikationer i PHP kan situationen förvÀrras sÄ mycket att man exempelvis mÄste utveckla . I denna anteckning kommer vi att prata om en bekant smÀrta med distribuerad lagring av sessioner och cachning av data i memcached samt hur vi löste dessa problem i ett "projet" av vÄrd.
Huvudpersonen Àr en PHP-applikation baserad pÄ ramverket Symfony 2.3, vilket inte ingÄr i företagsplanerna att uppdatera. Förutom den helt standardmÀssiga sessionlagringen anvÀndes i detta projekt politiken "cachning av allt" i memcached: svar pÄ frÄgor till databasen och API-servrar, olika flaggor, lÄs för synkronisering av kodens utförande och mycket mer. I en sÄdan situation blir ett fel i memcached fatalt för applikationens funktion. Dessutom leder förlust av cache till allvarliga konsekvenser: databasen börjar braka, API-tjÀnsterna blockerar förfrÄgningar, etc. Stabilisering av situationen kan ta mÄnga minuter, och under denna tid kommer tjÀnsten att vara extremt seg eller till och med bli otillgÀnglig.
Vi behövde sÀkerstÀlla möjligheten till horisontell skalning av applikationen med minimala insatser, dvs. med minimiÀndringar i kÀllkoden och fullstÀndig bevarande av funktionaliteten. Att göra cachen inte bara felfri, utan ocksÄ att minimera förluster av data frÄn den.
Vad Àr fel med memcached sjÀlv?
Generellt sett stödjer memcached-tillÀgget för PHP "ur lÄdan" distribuerad lagring av data och sessioner. Mekanismen för konsekvent hashning av nycklar gör det möjligt att jÀmnt fördela data över mÄnga servrar genom att entydigt adressera varje specifik nyckel till en viss server i gruppen, och de inbyggda failover-mekanismerna sÀkerstÀller hög tillgÀnglighet för cache-tjÀnsten (men tyvÀrr, inte för datan).
Sessionlagring fungerar nÄgot bÀttre: det gÄr att stÀlla in memcached.sess_number_of_replicas, vilket innebÀr att data kommer att sparas pÄ flera servrar, och i hÀndelse av att en instans av memcached gÄr ner kommer datan att hÀmtas frÄn andra. Men om servern kommer tillbaka utan data (som ofta hÀnder efter en omstart), kommer en del nycklar att omfördelas till dess fördel. Faktum Àr att detta skulle innebÀra förlust av sessionsdata, eftersom det inte finns nÄgon möjlighet att "gÄ" till en annan replik vid en miss.
De standardverktyg som biblioteket erbjuder Ă€r frĂ€mst inriktade pĂ„ horisontell skalerbarhet: de tillĂ„ter att öka cacheminnet till gigantiska storlekar och ge Ă„tkomst till det frĂ„n kod som körs pĂ„ olika servrar. Men i vĂ„r situation överstiger den mĂ€ngd data som lagras inte nĂ„gra fĂ„ gigabyte, och prestandan hos en eller tvĂ„ noder Ă€r mer Ă€n tillrĂ€cklig. DĂ€rmed skulle den enda anvĂ€ndbara funktionen vara att sĂ€kerstĂ€lla tillgĂ„ng till memcached sĂ„ lĂ€nge minst en instans av cacheminnet Ă€r i drift. Ăven denna möjlighet kunde man dock inte utnyttja⊠HĂ€r bör man pĂ„minna om ramverkets Ă„lder som anvĂ€ndes i projektet, vilket gjorde det omöjligt att fĂ„ applikationen att fungera med en serverpool. Vi fĂ„r heller inte glömma förlusten av sessionsdata: den massiva utloggningen av anvĂ€ndare fick kunden att bli nervös.
I idealfallet krÀvdes replikering av poster i memcached och att kringgÄ repliker vid en miss eller ett fel. För att genomföra denna strategi hjÀlpte oss .
mcrouter
Detta Àr en router för memcached, utvecklad av Facebook för att lösa deras problem. Den stöder det textbaserade protokollet för memcached, som möjliggör att skala memcached-installationer till orimliga storlekar. En detaljerad beskrivning av mcrouter kan hittas i . Förutom andra kan den göra det vi behöver:
- replikera poster;
- falla tillbaka pÄ andra servrar i gruppen vid uppkomst av fel.
LÄt oss sÀtta igÄng!
Konfiguration av mcrouter
Jag gÄr direkt till konfigurationen:
{
"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"
]
}
}
}
}Varför tre pooler? Varför upprepas servrar? LÄt oss titta pÄ hur det fungerar.
- I denna konfiguration vÀljer mcrouter vÀgen för att skicka begÀran baserat pÄ begÀrningens typ. Det informeras av typen
OperationSelectorRoute. - GET-begÀrningar gÄr till hanteraren
RandomRoute, som slumpmÀssigt vÀljer en pool eller vÀg bland objekten i arrayenchildren. Varje element i denna array Àr i sin tur en hanterareMissFailoverRoute, som skannar var och en av servrarna i poolen tills den fÄr ett svar med data, vilket kommer att returneras till klienten. - Om vi hade anvÀnt enbart
MissFailoverRouteen pool med tre servrar skulle alla begÀrningar först gÄ till den första instansen av memcached, medan de andra skulle mottaga begÀrningar endast om datan saknas. Denna metod skulle ha lett till en överbelastning av den första servern i listan, vilket Àr anledningen till att vi beslutade att skapa tre pooler med adresser i olika sekvenser och vÀlja dem slumpmÀssigt. - Alla andra begÀrningar (det vill sÀga skrivningar) hanteras med hjÀlp av
AllMajorityRoute. Denna hanterare skickar begĂ€rningar till alla servrar i poolen och vĂ€ntar pĂ„ svar frĂ„n minst N/2 + 1 av dem. Vi var tvungna att avstĂ„ frĂ„n att anvĂ€ndaAllSyncRouteför skrivoperationer eftersom denna metod krĂ€ver ett positivt svar frĂ„n alla servergrupperna â annars returnerar denSERVER_ERROR. Ăven om mcrouter dĂ„ lĂ€gger datan i tillgĂ€ngliga cacheminnen, kommer den anropande PHP-funktionen att returnera ett fel och generera ett meddelande.AllMajorityRouteĂ€r inte lika strikt och tillĂ„ter att upp till hĂ€lften av noderna sĂ€tts ur drift utan ovanstĂ„ende problem.
Den största nackdelen med detta system Ă€r att om det verkligen inte finns nĂ„gon data i cachen, kommer varje begĂ€ran frĂ„n klienten praktiskt taget att utföra N begĂ€rningar till memcached â till med allt servrarna i poolen. Vi kan minska antalet servrar i poolerna, till exempel till tvĂ„: genom att offra lagringspĂ„litlighet, fĂ„r vi en...bĂ€sta resultatet (subjektiv Ă„sikt).högre hastighet och mindre belastning frĂ„n förfrĂ„gningar till saknade nycklar.
NB: Nyttiga lÀnkar för att studera mcrouter kan ocksÄ vara och (inklusive stÀngda), som representerar en verklig guldgruva av olika konfigurationer.
Bygga och köra mcrouter
Applikationen (och memcached sjĂ€lv) körs i Kubernetes â dĂ€rför Ă€r mcrouter ocksĂ„ dĂ€r. För att bygga containern anvĂ€nder vi , konfigurationen kommer att se ut som följer:
NB: Listorna som ges i artikeln Àr publicerade i repositoryt .
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: Installera förutsÀttningar
apt:
name: [ 'apt-transport-https', 'tzdata', 'locales' ]
update_cache: yes
- name: LĂ€gg till mcrouter APT-nyckel
apt_key:
url: https://facebook.github.io/mcrouter/debrepo/xenial/PUBLIC.KEY
- name: LĂ€gg till mcrouter Repo
apt_repository:
repo: deb https://facebook.github.io/mcrouter/debrepo/xenial xenial contrib
filename: mcrouter
update_cache: yes
- name: StÀll in tidszon
timezone:
name: "Europe/Moscow"
- name: Se till att en lokalversion finns
locale_gen:
name: en_US.UTF-8
state: present
install:
- name: Installera mcrouter
apt:
name: [ 'mcrouter' ]()
⊠och skriver ner Helm-chart. Intressant â hĂ€r Ă€r endast konfiggeneratorn baserat pĂ„ antal repliker (om nĂ„gon har ett mer kortfattat och elegant alternativ â dela med er i kommentarerna):
{{- $count := (pluck .Values.global.env .Values.memcached.replicas | first | default .Values.memcached.replicas._default | int) -}}
{{- $pools := dict -}}
{{- $servers := list -}}
{{- /* Fyller matrisen med tvÄ kopior av servrar: "0 1 2 0 1 2" */ -}}
{{- range until 2 -}}
{{- range $i, $_ := until $count -}}
{{- $servers = append $servers (printf "mc-%d.mc:11211" $i) -}}
{{- end -}}
{{- end -}}
{{- /* Genom att vrida pÄ matrisen fÄr vi N skivor: "[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 }}
}
}
}
}()
Rullas ut i testmiljö och kontrolleras:
# 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 >Sökning pĂ„ felmeddelandet gav inget resultat, men med förfrĂ„gan «» fanns den Ă€ldsta öppna frĂ„gan in kĂ€rnan av projektet â memcached:s binĂ€ra protokoll.
NB: ASCII-protokol i memcached Àr lÄngsammare Àn det binÀra, och de inbyggda verktygen för konsekvent hashning av nycklar fungerar endast med det binÀra protokollet. Men det skapar inga problem för det specifika fallet.
Saken Àr biff: det Àr bara att vÀxla till ASCII-protokollet sÄ fungerar allt igen.... Men i detta fall spelade vanan att söka svar i oss ett elakt spratt. Du kommer inte att hitta det rÀtta svaret dÀr... om du inte blÀddrar till slutet, dÀr i sektionen «User contributed notes» finns det ett korrekt och .
Ja, det rÀtta namnet pÄ alternativet Àr memcached.sess_binary_protocol. Det mÄste stÀngas av, efter vilket sessionerna börjar fungera. Det ÄterstÄr bara att placera containern med mcrouter i podden med PHP!
Slutsats
SÄledes lyckades vi, genom enbart infrastrukturella förÀndringar, lösa den stÀllda uppgiften: frÄgan om failover i memcached Àr löst, pÄlitligheten av cachelagring har ökat. Förutom de uppenbara fördelarna för applikationen har det gett utrymme för manövrering vid arbetet med plattformen: nÀr alla komponenter har reserv gör det livet lÀttare för administratören. Ja, denna metod har sina nackdelar, kan se ut som ett "krÄngligt" tillvÀgagÄngssÀtt, men om det sparar pengar, döljer problemet och inte orsakar nya - varför inte?
P.S.
LÀs Àven i vÄr blogg:
- «Praktik med dapp» (exempelvis symfony-demo): och ;
- «».
KĂ€lla: habr.com
