Întrebări frecvente despre arhitectura și munca VKontakte

Istoria creării VKontakte este pe Wikipedia; a fost spusă de însuși Pavel. Se pare că toată lumea o cunoaște deja. Despre interiorul, arhitectura și structura site-ului pe HighLoad++ Pavel mi-a spus în 2010. De atunci s-au scurs multe servere, așa că vom actualiza informațiile: le vom diseca, vom scoate interiorul, îl vom cântări și vom privi dispozitivul VK din punct de vedere tehnic.

Întrebări frecvente despre arhitectura și munca VKontakte

Alexei Akulovici (AterCattus) dezvoltator backend în echipa VKontakte. Transcrierea acestui raport este un răspuns colectiv la întrebările frecvente despre funcționarea platformei, infrastructurii, serverelor și interacțiunii dintre acestea, dar nu și despre dezvoltare, și anume despre fier. Separat, despre bazele de date și despre ce are VK în schimb, despre colectarea jurnalelor și monitorizarea întregului proiect în ansamblu. Detalii sub croiala.



De mai bine de patru ani mă ocup de tot felul de sarcini legate de backend.

  • Încărcarea, stocarea, procesarea, distribuirea media: video, live streaming, audio, fotografii, documente.
  • Infrastructură, platformă, monitorizare dezvoltator, jurnale, cache regionale, CDN, protocol RPC proprietar.
  • Integrare cu servicii externe: notificări push, analiza link-urilor externe, flux RSS.
  • Ajutați colegii cu diverse întrebări, ale căror răspunsuri necesită scufundarea în cod necunoscut.

În acest timp, am avut parte de multe componente ale site-ului. Vreau să împărtășesc această experiență.

Arhitectura generala

Totul, ca de obicei, începe cu un server sau un grup de servere care acceptă cereri.

Server frontal

Serverul frontal acceptă cereri prin HTTPS, RTMP și WSS.

HTTPS - acestea sunt solicitări pentru versiunile web principale și mobile ale site-ului: vk.com și m.vk.com, și alți clienți oficiali și neoficiali ai API-ului nostru: clienți mobili, mesageri. Avem recepție RTMP-trafic pentru transmisiuni live cu servere frontale separate și WSS- conexiuni pentru Streaming API.

Pentru HTTPS și WSS pe servere merită Nginx. Pentru transmisiile RTMP, recent am trecut la propria noastră soluție kive, dar depășește domeniul de aplicare al raportului. Pentru toleranță la erori, aceste servere fac publicitate adreselor IP comune și acționează în grupuri, astfel încât, dacă există o problemă pe unul dintre servere, solicitările utilizatorilor să nu se piardă. Pentru HTTPS și WSS, aceleași servere criptează traficul pentru a prelua o parte din încărcarea CPU asupra lor.

Nu vom vorbi mai departe despre WSS și RTMP, ci doar despre solicitările HTTPS standard, care sunt de obicei asociate unui proiect web.

Backend

În spatele frontului sunt de obicei servere backend. Ei procesează cererile pe care serverul frontal le primește de la clienți.

Aceasta servere kPHP, pe care rulează demonul HTTP, deoarece HTTPS este deja decriptat. kPHP este un server care rulează modele de prefurca: pornește un proces principal, o grămadă de procese copil, le transmite socket-uri de ascultare și le procesează cererile. În acest caz, procesele nu sunt repornite între fiecare solicitare din partea utilizatorului, ci pur și simplu își resetează starea la starea originală cu valoare zero - cerere după solicitare, în loc să se repornească.

Distribuția încărcăturii

Toate backend-urile noastre nu sunt un număr mare de mașini care pot procesa orice cerere. Noi ei împărțite în grupuri separate: general, mobil, API, video, montare... Problema pe un grup separat de mașini nu le va afecta pe toate celelalte. În cazul unor probleme cu videoclipul, utilizatorul care ascultă muzică nici nu va ști despre probleme. Ce backend la care să trimită cererea este decis de către nginx pe front, conform configurației.

Colectare și reechilibrare metrice

Pentru a înțelege câte mașini trebuie să avem în fiecare grupă, noi nu vă bazați pe QPS. Backend-urile sunt diferite, au solicitări diferite, fiecare cerere are o complexitate diferită de calculare a QPS. De aceea noi operăm cu conceptul de încărcare pe server ca un întreg - pe CPU și perf.

Avem mii de astfel de servere. Fiecare server fizic rulează un grup kPHP pentru a recicla toate nucleele (deoarece kPHP are un singur thread).

Server de conținut

CS sau Content Server este un spațiu de stocare. CS este un server care stochează fișiere și, de asemenea, procesează fișierele încărcate și tot felul de sarcini sincrone de fundal pe care i le atribuie interfața web principală.

Avem zeci de mii de servere fizice care stochează fișiere. Utilizatorilor le place să încarce fișiere, iar noi le place să le stocăm și să le partajăm. Unele dintre aceste servere sunt închise de servere speciale pu/pp.

pu/pp

Dacă ați deschis fila de rețea în VK, ați văzut pu/pp.

Întrebări frecvente despre arhitectura și munca VKontakte

Ce este pu/pp? Dacă închidem un server după altul, atunci există două opțiuni pentru încărcarea și descărcarea unui fișier pe serverul care a fost închis: direct prin http://cs100500.userapi.com/path sau prin server intermediar - http://pu.vk.com/c100500/path.

Pu este numele istoric pentru încărcarea fotografiilor, iar pp este proxy foto. Adică, un server este pentru încărcarea fotografiilor, iar altul este pentru încărcare. Acum nu sunt încărcate doar fotografiile, dar și numele a fost păstrat.

Aceste servere termina sesiunile HTTPSpentru a elimina încărcarea procesorului din stocare. De asemenea, deoarece fișierele utilizatorului sunt procesate pe aceste servere, cu cât informațiile sunt mai puțin sensibile stocate pe aceste mașini, cu atât mai bine. De exemplu, cheile de criptare HTTPS.

Deoarece mașinile sunt închise de celelalte mașini ale noastre, ne putem permite să nu le dăm IP-uri externe „albe” și da "gri". În acest fel, am economisit în pool-ul de IP și am garantat să protejăm mașinile de accesul din exterior - pur și simplu nu există IP pentru a intra în el.

Reziliență la IP-uri partajate. În ceea ce privește toleranța la erori, schema funcționează la fel - mai multe servere fizice au un IP fizic comun, iar hardware-ul din fața lor alege unde să trimită cererea. Voi vorbi despre alte opțiuni mai târziu.

Punctul controversat este că în acest caz clientul păstrează mai puține conexiuni. Dacă există același IP pentru mai multe mașini - cu aceeași gazdă: pu.vk.com sau pp.vk.com, browser-ul client are o limită a numărului de solicitări simultane către o gazdă. Dar pe vremea ubicuului HTTP/2, cred că acest lucru nu mai este atât de relevant.

Dezavantajul evident al schemei este că trebuie pompa tot traficul, care merge la stocare, prin alt server. Deoarece pompăm traficul prin mașini, nu putem încă pompa trafic greu, de exemplu, video, folosind aceeași schemă. Îl transmitem direct - o conexiune directă separată pentru stocări separate, special pentru video. Transmitem conținut mai ușor printr-un proxy.

Nu cu mult timp în urmă am primit o versiune îmbunătățită de proxy. Acum vă voi spune cum diferă de cele obișnuite și de ce este necesar.

soare

În septembrie 2017, Oracle, care a cumpărat anterior Sun, a concediat un număr mare de angajați ai Sun. Putem spune că în acest moment firma a încetat să mai existe. Atunci când au ales un nume pentru noul sistem, administratorii noștri au decis să aducă un omagiu memoriei acestei companii și au numit noul sistem Sun. Între noi îi numim pur și simplu „sori”.

Întrebări frecvente despre arhitectura și munca VKontakte

pp a avut câteva probleme. Un IP per grup - cache ineficient. Mai multe servere fizice au o adresă IP comună și nu există nicio modalitate de a controla la ce server va ajunge cererea. Prin urmare, dacă diferiți utilizatori vin pentru același fișier, atunci dacă există un cache pe aceste servere, fișierul ajunge în cache-ul fiecărui server. Aceasta este o schemă foarte ineficientă, dar nu s-a putut face nimic.

Prin urmare - nu putem fragmenta conținutul, deoarece nu putem selecta un anumit server pentru acest grup - au un IP comun. Tot din anumite motive interne avem nu a fost posibil să se instaleze astfel de servere în regiuni. Au stat doar la Sankt Petersburg.

Odată cu soarele, am schimbat sistemul de selecție. Acum avem rutare anycast: rutare dinamică, anycast, demon de autoverificare. Fiecare server are propriul IP individual, dar o subrețea comună. Totul este configurat în așa fel încât, dacă un server eșuează, traficul este răspândit automat peste celelalte servere din același grup. Acum este posibil să selectați un anumit server, fără cache redundantă, iar fiabilitatea nu a fost afectată.

Suport greutate. Acum ne putem permite să instalăm mașini de putere diferită după cum este necesar și, de asemenea, în cazul unor probleme temporare, să schimbăm greutățile „soarelor” de lucru pentru a reduce sarcina asupra acestora, astfel încât să „odihnească” și să înceapă să lucreze din nou.

Partajarea după id-ul conținutului. Un lucru amuzant despre sharding: de obicei, fragmentăm conținutul, astfel încât diferiți utilizatori să meargă la același fișier prin același „sun”, astfel încât să aibă un cache comun.

Am lansat recent aplicația „Clover”. Acesta este un test online într-o transmisie în direct, în care gazda pune întrebări și utilizatorii răspund în timp real, alegând opțiuni. Aplicația are un chat în care utilizatorii pot conversa. Se poate conecta simultan la transmisie peste 100 de mii de oameni. Toți scriu mesaje care sunt trimise tuturor participanților și un avatar vine împreună cu mesajul. Dacă 100 de mii de oameni vin pentru un avatar într-un „soare”, atunci se poate rostogoli uneori în spatele unui nor.

Pentru a rezista la exploziile de solicitări pentru același fișier, pentru un anumit tip de conținut activăm o schemă stupidă care răspândește fișierele în toate „soarele” disponibile din regiune.

Soarele din interior

Proxy invers pe nginx, cache fie în RAM, fie pe discuri rapide Optane/NVMe. Exemplu: http://sun4-2.userapi.com/c100500/path — o legătură către „soare”, care se află în a patra regiune, al doilea grup de servere. Închide fișierul cale, care se află fizic pe serverul 100500.

Cache

Adăugăm încă un nod la schema noastră arhitecturală - mediul de cache.

Întrebări frecvente despre arhitectura și munca VKontakte

Mai jos este diagrama aspectului cache-urile regionale, sunt aproximativ 20 dintre ele. Acestea sunt locurile în care se află cache-urile și „sorii”, care pot stoca traficul prin ele însele.

Întrebări frecvente despre arhitectura și munca VKontakte

Aceasta este stocarea în cache a conținutului multimedia; nu sunt stocate date de utilizator aici - doar muzică, video, fotografii.

Pentru a determina regiunea utilizatorului, noi colectăm prefixele de rețea BGP anunțate în regiuni. În cazul fallback-ului, trebuie să analizăm și baza de date geoip dacă nu am putut găsi IP-ul după prefixe. Determinăm regiunea după IP-ul utilizatorului. În cod, putem privi una sau mai multe regiuni ale utilizatorului - acele puncte de care este cel mai aproape geografic.

Cum funcționează?

Numărăm popularitatea fișierelor în funcție de regiune. Există numărul cache-ului regional în care se află utilizatorul și identificatorul fișierului - luăm această pereche și creștem ratingul cu fiecare descărcare.

În același timp, demonii - servicii în regiuni - vin din când în când la API și spun: „Sunt așa sau așa cache, dă-mi o listă cu cele mai populare fișiere din regiunea mea care nu sunt încă pe mine. ” API-ul furnizează o grămadă de fișiere sortate după evaluare, demonul le descarcă, le duce în regiuni și livrează fișierele de acolo. Aceasta este diferența fundamentală dintre pu/pp și Sun din cache: ei dau fișierul prin ei înșiși imediat, chiar dacă acest fișier nu este în cache, iar cache-ul descarcă mai întâi fișierul în sine, apoi începe să-l dea înapoi.

În acest caz obținem conținut mai aproape de utilizatori și răspândirea încărcării rețelei. De exemplu, doar din memoria cache din Moscova distribuim mai mult de 1 Tbit/s în orele de vârf.

Dar sunt probleme - serverele cache nu sunt de cauciuc. Pentru conținut super popular, uneori nu există suficientă rețea pentru un server separat. Serverele noastre cache sunt de 40-50 Gbit/s, dar există conținut care blochează complet un astfel de canal. Ne îndreptăm către implementarea stocării a mai mult de o copie a fișierelor populare din regiune. Sper că îl vom implementa până la sfârșitul anului.

Ne-am uitat la arhitectura generală.

  • Servere frontale care acceptă cereri.
  • Backend-uri care procesează cereri.
  • Stocările care sunt închise de două tipuri de proxy.
  • Cache-uri regionale.

Ce lipsește din această diagramă? Desigur, bazele de date în care stocăm datele.

Baze de date sau motoare

Le numim nu baze de date, ci motoare - Motoare, pentru că practic nu avem baze de date în sensul general acceptat.

Întrebări frecvente despre arhitectura și munca VKontakte

Aceasta este o măsură necesară.. Acest lucru s-a întâmplat pentru că în 2008-2009, când VK a avut o creștere explozivă a popularității, proiectul a funcționat în întregime pe MySQL și Memcache și au existat probleme. MySQL îi plăcea să prăbușească și să corupă fișierele, după care nu se mai recupera, iar Memcache s-a degradat treptat în performanță și a trebuit să fie repornit.

Se pare că proiectul din ce în ce mai popular avea stocare persistentă, care corupe datele și un cache, care încetinește. În astfel de condiții, este dificil să dezvolți un proiect în creștere. S-a decis să încercăm să rescriem lucrurile critice asupra cărora proiectul s-a concentrat asupra propriilor biciclete.

Soluția a avut succes. A existat o oportunitate de a face acest lucru, precum și o necesitate extremă, deoarece alte modalități de scalare nu existau la acel moment. Nu existau o grămadă de baze de date, NoSQL nu exista încă, existau doar MySQL, Memcache, PostrgreSQL - și atât.

Funcționare universală. Dezvoltarea a fost condusă de echipa noastră de dezvoltatori C și totul a fost realizat într-o manieră consecventă. Indiferent de motor, toți aveau aproximativ același format de fișier scris pe disc, aceiași parametri de lansare, procesau semnalele în același mod și se comportau aproximativ la fel în cazul situațiilor marginale și problemelor. Odată cu creșterea motoarelor, administratorilor este convenabil să opereze sistemul - nu există nicio grădină zoologică care trebuie întreținută și trebuie să reînvețe cum să opereze fiecare nouă bază de date terță parte, ceea ce a făcut posibilă rapid și crește în mod convenabil numărul acestora.

Tipuri de motoare

Echipa a scris destul de multe motoare. Iată doar câteva dintre ele: prieten, indicii, imagine, ipdb, scrisori, liste, jurnale, memcached, meowdb, știri, nostradamus, fotografie, liste de redare, pmemcached, sandbox, căutare, stocare, aprecieri, sarcini, …

Pentru fiecare sarcină care necesită o structură de date specifică sau procesează solicitări atipice, echipa C scrie un nou motor. De ce nu.

Avem un motor separat memcached, care seamănă cu una obișnuită, dar cu o grămadă de bunătăți, și care nu încetinește. Nu ClickHouse, dar funcționează și. Disponibil separat pmemcached - E persistent memcached, care poate stoca și date pe disc, în plus, decât încape în RAM, pentru a nu pierde date la repornire. Există diverse motoare pentru sarcini individuale: cozi, liste, seturi - tot ceea ce necesită proiectul nostru.

Clustere

Din perspectiva codului, nu este nevoie să ne gândim la motoarele sau bazele de date ca procese, entități sau instanțe. Codul funcționează în mod specific cu clustere, cu grupuri de motoare - un tip pe cluster. Să presupunem că există un cluster memcache - este doar un grup de mașini.

Codul nu trebuie să știe deloc locația fizică, dimensiunea sau numărul de servere. El merge la cluster folosind un anumit identificator.

Pentru ca acest lucru să funcționeze, trebuie să adăugați încă o entitate care se află între cod și motoare - împuternicit.

proxy RPC

Proxy autobuz de legătură, pe care rulează aproape întregul site. În același timp avem nicio descoperire a serviciului — în schimb, există o configurație pentru acest proxy, care cunoaște locația tuturor clusterelor și a tuturor fragmentelor acestui cluster. Asta fac administratorii.

Programatorilor nu le pasă deloc cât de mult, unde și cât costă - doar merg la cluster. Acest lucru ne permite multe. Când primește o solicitare, proxy-ul redirecționează solicitarea, știind unde - determină acest lucru însuși.

Întrebări frecvente despre arhitectura și munca VKontakte

În acest caz, proxy-ul este un punct de protecție împotriva eșecului serviciului. Dacă un motor încetinește sau se blochează, atunci proxy-ul înțelege acest lucru și răspunde în consecință la partea clientului. Acest lucru vă permite să eliminați timeout - codul nu așteaptă ca motorul să răspundă, dar înțelege că nu funcționează și trebuie să se comporte într-un fel diferit. Codul trebuie pregătit pentru faptul că bazele de date nu funcționează întotdeauna.

Implementări specifice

Uneori încă vrem să avem un fel de soluție non-standard ca motor. În același timp, s-a decis să nu folosim rpc-proxy-ul nostru gata făcut, creat special pentru motoarele noastre, ci să facem un proxy separat pentru sarcină.

Pentru MySQL, pe care îl mai avem aici și acolo, folosim db-proxy, iar pentru ClickHouse - Casa de pisici.

În general funcționează așa. Există un anumit server, rulează kPHP, Go, Python - în general, orice cod care poate folosi protocolul nostru RPC. Codul rulează local pe un proxy RPC - fiecare server pe care se află codul rulează propriul proxy local. La cerere, proxy-ul înțelege unde să meargă.

Întrebări frecvente despre arhitectura și munca VKontakte

Dacă un motor vrea să meargă la altul, chiar dacă este un vecin, trece printr-un proxy, pentru că vecinul poate fi într-un alt centru de date. Motorul nu ar trebui să se bazeze pe cunoașterea locației altceva decât însuși - aceasta este soluția noastră standard. Dar, desigur, există și excepții :)

Un exemplu de schemă TL conform căreia funcționează toate motoarele.

memcache.not_found                                = memcache.Value;
memcache.strvalue	value:string flags:int = memcache.Value;
memcache.addOrIncr key:string flags:int delay:int value:long = memcache.Value;

tasks.task
    fields_mask:#
    flags:int
    tag:%(Vector int)
    data:string
    id:fields_mask.0?long
    retries:fields_mask.1?int
    scheduled_time:fields_mask.2?int
    deadline:fields_mask.3?int
    = tasks.Task;
 
tasks.addTask type_name:string queue_id:%(Vector int) task:%tasks.Task = Long;

Acesta este un protocol binar, cel mai apropiat analog al căruia este protobuf. Schema descrie câmpuri opționale, tipuri complexe - extensii ale scalarilor încorporați și interogări. Totul funcționează conform acestui protocol.

RPC peste TL peste TCP/UDP... UDP?

Avem un protocol RPC pentru executarea solicitărilor motorului care rulează peste schema TL. Toate acestea funcționează printr-o conexiune TCP/UDP. TCP este de înțeles, dar de ce avem nevoie de UDP des?

UDP ajută evitați problema unui număr mare de conexiuni între servere. Dacă fiecare server are un proxy RPC și, în general, poate merge la orice motor, atunci există zeci de mii de conexiuni TCP per server. Există o încărcătură, dar este inutilă. În cazul UDP această problemă nu există.

Fără strângere de mână TCP redundantă. Aceasta este o problemă tipică: atunci când este lansat un nou motor sau un nou server, multe conexiuni TCP sunt stabilite simultan. Pentru cererile mici, ușoare, de exemplu, sarcina utilă UDP, toată comunicarea dintre cod și motor este două pachete UDP: unul zboară într-o direcție, al doilea în cealaltă. O călătorie dus-întors - și codul a primit un răspuns de la motor fără o strângere de mână.

Da, totul funcționează cu un procent foarte mic de pierderi de pachete. Protocolul are suport pentru retransmiteri și timeout-uri, dar dacă pierdem mult, vom obține aproape TCP, ceea ce nu este benefic. Nu conducem UDP peste oceane.

Avem mii de astfel de servere, iar schema este aceeași: pe fiecare server fizic este instalat un pachet de motoare. Acestea sunt în mare parte cu un singur thread pentru a rula cât mai repede posibil, fără a fi blocate, și sunt fragmentate ca soluții cu un singur thread. În același timp, nu avem nimic mai fiabil decât aceste motoare și se acordă multă atenție stocării persistente a datelor.

Stocare persistentă a datelor

Motoarele scriu binlog-uri. Un binlog este un fișier la sfârșitul căruia se adaugă un eveniment pentru o schimbare a stării sau a datelor. În diferite soluții se numește diferit: log binar, WAL, AOF, dar principiul este același.

Pentru a preveni ca motorul să recitească întregul binlog timp de mulți ani la repornire, motoarele scriu instantanee - starea curentă. Dacă este necesar, ei citesc mai întâi din el și apoi termină citirea din binlog. Toate binlogurile sunt scrise în același format binar - conform schemei TL, astfel încât administratorii să le poată administra în mod egal folosind instrumentele lor. Nu este nevoie de asemenea de instantanee. Există un antet general care indică al cui instantaneu este int, magia motorului și care corp nu este important pentru nimeni. Aceasta este o problemă cu motorul care a înregistrat instantaneul.

Voi descrie rapid principiul de funcționare. Există un server pe care rulează motorul. El deschide un nou binlog gol pentru scriere și scrie un eveniment pentru modificarea acestuia.

Întrebări frecvente despre arhitectura și munca VKontakte

La un moment dat, fie decide să facă el însuși un instantaneu, fie primește un semnal. Serverul creează un fișier nou, își scrie întreaga stare în el, adaugă dimensiunea curentă a fișierului binlog - offset - la sfârșitul fișierului și continuă să scrie în continuare. Nu este creat un nou binlog.

Întrebări frecvente despre arhitectura și munca VKontakte

La un moment dat, când motorul a repornit, vor exista atât un binlog, cât și un instantaneu pe disc. Motorul citește întregul instantaneu și își ridică starea la un anumit punct.

Întrebări frecvente despre arhitectura și munca VKontakte

Citește poziția care era în momentul în care a fost creat instantaneul și dimensiunea binlog-ului.

Întrebări frecvente despre arhitectura și munca VKontakte

Citește sfârșitul binlog-ului pentru a obține starea curentă și continuă să scrie alte evenimente. Aceasta este o schemă simplă; toate motoarele noastre funcționează conform acesteia.

Replicarea datelor

Ca rezultat, replicarea datelor în sistemul nostru bazată pe declarații — scriem în binlog nu orice modificare de pagină, ci și anume cereri de modificare. Foarte asemănător cu ceea ce vine prin rețea, doar puțin modificat.

Aceeași schemă este folosită nu numai pentru replicare, ci și pentru a crea copii de rezervă. Avem un motor - un maestru de scriere care scrie în binlog. În orice alt loc în care administratorii l-au configurat, acest binlog este copiat și gata - avem o copie de rezervă.

Întrebări frecvente despre arhitectura și munca VKontakte

Daca este nevoie replică de citirePentru a reduce sarcina de citire a procesorului, motorul de citire este pur și simplu lansat, care citește sfârșitul binlog-ului și execută aceste comenzi local.

Întârzierea aici este foarte mică și este posibil să aflați cât de mult rămâne replica în urma maestrului.

Partajarea datelor în proxy RPC

Cum funcționează sharding-ul? Cum înțelege proxy-ul către ce fragment de cluster să trimită? Codul nu spune: „Trimite pentru 15 cioburi!” - nu, acest lucru este făcut de proxy.

Cea mai simplă schemă este firstint — primul număr din cerere.

get(photo100_500) => 100 % N.

Acesta este un exemplu pentru un protocol text simplu memcache, dar, desigur, interogările pot fi complexe și structurate. Exemplul ia primul număr din interogare și restul când este împărțit la dimensiunea clusterului.

Acest lucru este util atunci când dorim să avem localitatea de date a unei singure entități. Să presupunem că 100 este un ID de utilizator sau de grup și dorim ca toate datele unei entități să fie într-un singur fragment pentru interogări complexe.

Dacă nu ne pasă cum sunt răspândite cererile în cluster, există o altă opțiune - hashing întregul ciob.

hash(photo100_500) => 3539886280 % N

De asemenea, obținem hash-ul, restul diviziunii și numărul fragmentului.

Ambele opțiuni funcționează doar dacă suntem pregătiți pentru faptul că atunci când creștem dimensiunea clusterului, îl vom împărți sau îl vom crește de mai multe ori. De exemplu, am avut 16 cioburi, nu avem suficiente, vrem mai multe - putem obține în siguranță 32 fără timp de nefuncționare. Dacă vrem să creștem nu multipli, vor exista timpi de nefuncționare, deoarece nu vom putea împărți totul cu exactitate, fără pierderi. Aceste opțiuni sunt utile, dar nu întotdeauna.

Dacă trebuie să adăugăm sau să eliminăm un număr arbitrar de servere, folosim Hashing constant pe ring la Ketama. Dar, în același timp, pierdem complet localitatea datelor; trebuie să îmbinam cererea la cluster, astfel încât fiecare piesă să returneze propriul răspuns mic și apoi să îmbinam răspunsurile la proxy.

Sunt cereri super-specifice. Arată astfel: proxy-ul RPC primește cererea, determină la ce cluster să meargă și determină fragmentul. Apoi există fie master de scriere, fie, dacă clusterul are suport pentru replică, trimite la o replică la cerere. Proxy-ul face toate acestea.

Întrebări frecvente despre arhitectura și munca VKontakte

Bușteni

Scriem jurnalele în mai multe moduri. Cel mai evident și simplu este scrieți jurnalele în memcache.

ring-buffer: prefix.idx = line

Există un prefix cheie - numele jurnalului, o linie, iar dimensiunea acestui jurnal - numărul de linii. Luăm un număr aleatoriu de la 0 la numărul de linii minus 1. Cheia din memcache este un prefix concatenat cu acest număr aleator. Salvăm linia de jurnal și ora curentă în valoare.

Când este necesar să citim jurnalele, efectuăm Multi Get toate cheile, sortate după timp, și astfel obțineți un jurnal de producție în timp real. Schema este folosită atunci când trebuie să depanați ceva în producție în timp real, fără a sparge nimic, fără a opri sau a permite traficul către alte mașini, dar acest jurnal nu durează mult.

Pentru depozitarea sigură a buștenilor avem un motor busteni-motor. Tocmai de aceea a fost creat și este utilizat pe scară largă într-un număr mare de clustere. Cel mai mare cluster pe care îl cunosc stochează 600 TB de jurnale împachetate.

Motorul este foarte vechi, sunt clustere care au deja 6-7 ani. Există probleme cu acesta pe care încercăm să le rezolvăm, de exemplu, am început să folosim în mod activ ClickHouse pentru a stoca jurnalele.

Colectarea jurnalelor în ClickHouse

Această diagramă arată cum intrăm în motoarele noastre.

Întrebări frecvente despre arhitectura și munca VKontakte

Există un cod care merge local prin RPC la proxy-ul RPC și înțelege unde să meargă la motor. Dacă vrem să scriem jurnalele în ClickHouse, trebuie să schimbăm două părți în această schemă:

  • înlocuiți un motor cu ClickHouse;
  • înlocuiți proxy-ul RPC, care nu poate accesa ClickHouse, cu o soluție care poate și prin RPC.

Motorul este simplu - îl înlocuim cu un server sau un cluster de servere cu ClickHouse.

Și pentru a merge la ClickHouse, am făcut-o KittenHouse. Dacă mergem direct de la KittenHouse la ClickHouse, nu va face față. Chiar și fără solicitări, se adună din conexiunile HTTP ale unui număr mare de mașini. Pentru ca schema să funcționeze, pe un server cu ClickHouse este ridicat proxy inversă local, care este scris în așa fel încât să reziste la volumele necesare de conexiuni. De asemenea, poate stoca datele în sine în mod relativ fiabil.

Întrebări frecvente despre arhitectura și munca VKontakte

Uneori nu dorim să implementăm schema RPC în soluții non-standard, de exemplu, în nginx. Prin urmare, KittenHouse are capacitatea de a primi jurnale prin UDP.

Întrebări frecvente despre arhitectura și munca VKontakte

Dacă expeditorul și destinatarul jurnalelor lucrează pe aceeași mașină, atunci probabilitatea de a pierde un pachet UDP în gazda locală este destul de mică. Ca un compromis între necesitatea implementării RPC într-o soluție terță parte și fiabilitate, folosim pur și simplu trimiterea UDP. Vom reveni la această schemă mai târziu.

monitorizarea

Avem două tipuri de jurnale: cele colectate de administratori pe serverele lor și cele scrise de dezvoltatori din cod. Acestea corespund două tipuri de metrici: sistem și produs.

Valorile sistemului

Funcționează pe toate serverele noastre netdata, care colectează statistici și le trimite către Carbon grafit. Prin urmare, ClickHouse este folosit ca sistem de stocare, și nu Whisper, de exemplu. Dacă este necesar, puteți citi direct din ClickHouse sau utilizați grafana pentru metrici, grafice și rapoarte. În calitate de dezvoltatori, avem suficient acces la Netdata și Grafana.

Valori de produs

Pentru comoditate, am scris o mulțime de lucruri. De exemplu, există un set de funcții obișnuite care vă permit să scrieți valorile Counts, UniqueCounts în statistici, care sunt trimise undeva mai departe.

statlogsCountEvent   ( ‘stat_name’,            $key1, $key2, …)
statlogsUniqueCount ( ‘stat_name’, $uid,    $key1, $key2, …)
statlogsValuetEvent  ( ‘stat_name’, $value, $key1, $key2, …)

$stats = statlogsStatData($params)

Ulterior, putem folosi filtre de sortare și grupare și facem tot ce ne dorim din statistici - construim grafice, configuram Watchdogs.

Scriem foarte mult multe metrici numărul de evenimente este de la 600 de miliarde la 1 trilion pe zi. Cu toate acestea, vrem să le păstrăm cel puțin câțiva anipentru a înțelege tendințele în metrici. Adunarea tuturor este o mare problemă pe care încă nu am rezolvat-o. Vă spun cum a funcționat în ultimii ani.

Avem funcții care scriu aceste metrici la memcache-ul localpentru a reduce numărul de intrări. O dată într-o perioadă scurtă de timp lansat local statistici-daemon colectează toate înregistrările. Apoi, demonul îmbină valorile în două straturi de servere culegători de buşteni, care adună statistici de la o grămadă de mașini de la noi, astfel încât stratul din spatele lor să nu moară.

Întrebări frecvente despre arhitectura și munca VKontakte

Dacă este necesar, putem scrie direct la colectori de jurnal.

Întrebări frecvente despre arhitectura și munca VKontakte

Dar scrierea din cod direct către colectori, ocolind stas-daemom, este o soluție slab scalabilă, deoarece crește sarcina asupra colectorului. Soluția este potrivită numai dacă dintr-un motiv oarecare nu putem ridica demonul de statistici memcache pe mașină sau s-a prăbușit și am mers direct.

Apoi, colectorii de jurnale îmbină statisticile în meowDB - aceasta este baza noastră de date, care poate stoca și valori.

Întrebări frecvente despre arhitectura și munca VKontakte

Apoi putem face selecții binare „aproape-SQL” din cod.

Întrebări frecvente despre arhitectura și munca VKontakte

Experiment

În vara lui 2018, am avut un hackathon intern și a venit ideea de a încerca să înlocuim partea roșie a diagramei cu ceva care ar putea stoca valori în ClickHouse. Avem jurnale pe ClickHouse - de ce nu încercați?

Întrebări frecvente despre arhitectura și munca VKontakte

Am avut o schemă care a scris jurnalele prin KittenHouse.

Întrebări frecvente despre arhitectura și munca VKontakte

Ne-am hotărât adăugați un alt „*House” la diagramă, care va primi exact valorile în formatul în care codul nostru le scrie prin UDP. Apoi această *House le transformă în inserții, ca niște bușteni, pe care KittenHouse le înțelege. El poate livra perfect aceste jurnale către ClickHouse, care ar trebui să le poată citi.

Întrebări frecvente despre arhitectura și munca VKontakte

Schema cu baza de date memcache, stats-daemon și logs-collectors este înlocuită cu aceasta.

Întrebări frecvente despre arhitectura și munca VKontakte

Schema cu baza de date memcache, stats-daemon și logs-collectors este înlocuită cu aceasta.

  • Există o trimitere din cod aici, care este scrisă local în StatsHouse.
  • StatsHouse scrie în KittenHouse valorile UDP, deja convertite în inserări SQL.
  • KittenHouse le trimite la ClickHouse.
  • Dacă vrem să le citim, atunci le citim ocolind StatsHouse - direct din ClickHouse folosind SQL obișnuit.

Este încă experiment, dar ne place cum iese. Dacă rezolvăm problemele cu schema, atunci poate că vom trece la ea complet. Personal, sper.

Schema nu economisește fier. Sunt necesare mai puține servere, nu sunt necesare demonii de statistici și colectoare de jurnale locale, dar ClickHouse necesită un server mai mare decât cele din schema actuală. Sunt necesare mai puține servere, dar acestea trebuie să fie mai scumpe și mai puternice.

Implementează

Mai întâi, să ne uităm la implementarea PHP. Ne dezvoltăm în merge: utilizare GitLab и TeamCity pentru desfășurare. Ramurile de dezvoltare sunt îmbinate în ramura master, din master pentru testare sunt îmbinate în staging, iar din staging în producție.

Înainte de implementare, ramura actuală de producție și cea anterioară sunt luate, iar fișierele dif sunt luate în considerare în ele - modificări: create, șterse, schimbate. Această modificare este înregistrată în binlog-ul unui motor special copyfast, care poate replica rapid modificările întregii noastre flote de servere. Ceea ce se folosește aici nu este copierea directă, ci replicarea bârfei, când un server trimite modificări către cei mai apropiați vecini ai săi, cele către vecinii săi și așa mai departe. Acest lucru vă permite să actualizați codul în zeci și unități de secunde pe întreaga flotă. Când modificarea ajunge la replica locală, aceasta îi aplică aceste patch-uri sistem de fișiere local. Rollback se efectuează, de asemenea, conform aceleiași scheme.

De asemenea, implementăm mult kPHP și are și propria dezvoltare merge conform diagramei de mai sus. De la aceasta Server HTTP binar, atunci nu putem produce dif - binarul de lansare cântărește sute de MB. Prin urmare, există o altă opțiune aici - versiunea este scrisă binlog copyfast. Cu fiecare build crește, iar în timpul rollback-ului crește, de asemenea. Versiune replicat pe servere. Copyfasts locali văd că o nouă versiune a intrat în binlog și, prin aceeași replicare bârfă, iau pentru ei înșiși cea mai recentă versiune a binarului, fără a obosi serverul nostru principal, dar împrăștiind cu grijă sarcina în rețea. Ce urmează relansare grațioasă pentru noua versiune.

Pentru motoarele noastre, care sunt, de asemenea, în esență binare, schema este foarte similară:

  • git master branch;
  • binar în .deb;
  • versiunea este scrisă în binlog copyfast;
  • replicat pe servere;
  • serverul scoate un nou .dep;
  • dpkg -i;
  • relansare grațioasă la noua versiune.

Diferența este că binarul nostru este ambalat în arhive .deb, iar la pomparea lor dpkg -i sunt plasate pe sistem. De ce este kPHP implementat ca un binar, iar motoarele sunt implementate ca dpkg? S-a întâmplat așa. Funcționează - nu-l atingeți.

Link-uri utile:

Alexey Akulovich este unul dintre cei care, ca parte a Comitetului de program, ajută PHP Rusia pe 17 mai va deveni cel mai mare eveniment din ultima vreme pentru dezvoltatorii PHP. Uite ce PC tare avem, ce difuzoare (două dintre ele dezvoltă nucleul PHP!) - pare ceva ce nu poți rata dacă scrii PHP.

Sursa: www.habr.com

Adauga un comentariu