FAQ om arkitektur og arbejde i VKontakte

Historien om oprettelsen af ​​VKontakte er på Wikipedia; det blev fortalt af Pavel selv. Det lader til, at alle allerede kender hende. Om det indre, arkitektur og struktur på siden på HighLoad++ Pavel fortalte mig tilbage i 2010. Mange servere er lækket siden da, så vi vil opdatere informationen: vi vil dissekere den, tage den indvendige side ud, veje den og se på VK-enheden fra et teknisk synspunkt.

FAQ om arkitektur og arbejde i VKontakte

Alexey Akulovich (AterCattus) backend-udvikler i VKontakte-teamet. Udskriften af ​​denne rapport er et samlet svar på ofte stillede spørgsmål om driften af ​​platformen, infrastruktur, servere og interaktion mellem dem, men ikke om udvikling, nemlig om jern. Separat om databaser og hvad VK har i stedet, om at indsamle logs og overvåge hele projektet som helhed. Detaljer under snittet.



I mere end fire år har jeg beskæftiget mig med alle mulige opgaver relateret til backend.

  • Upload, lagring, behandling, distribution af medier: video, live streaming, lyd, fotos, dokumenter.
  • Infrastruktur, platform, udviklerovervågning, logfiler, regionale caches, CDN, proprietær RPC-protokol.
  • Integration med eksterne tjenester: push-meddelelser, ekstern link-parsing, RSS-feed.
  • At hjælpe kolleger med forskellige spørgsmål, hvis svar kræver at dykke ned i ukendt kode.

I løbet af denne tid havde jeg en finger med i mange komponenter på webstedet. Jeg vil gerne dele denne oplevelse.

Generel arkitektur

Alt starter som sædvanligt med en server eller gruppe af servere, der accepterer anmodninger.

Front server

Frontserveren accepterer anmodninger via HTTPS, RTMP og WSS.

HTTPS - disse er anmodninger om hoved- og mobilwebversionerne af webstedet: vk.com og m.vk.com, og andre officielle og uofficielle klienter af vores API: mobilklienter, messengers. Vi har reception RTMP-trafik til Live-udsendelser med separate frontservere og WSS- forbindelser til Streaming API.

For HTTPS og WSS på servere er det værd Nginx. Til RTMP-udsendelser skiftede vi for nylig til vores egen løsning havde, men det ligger uden for rapportens rammer. For fejltolerance annoncerer disse servere almindelige IP-adresser og fungerer i grupper, så hvis der er et problem på en af ​​serverne, går brugeranmodninger ikke tabt. For HTTPS og WSS krypterer de samme servere trafik for at tage en del af CPU-belastningen på sig selv.

Vi vil ikke tale yderligere om WSS og RTMP, men kun om standard HTTPS-anmodninger, som normalt er forbundet med et webprojekt.

Bagende

Bag fronten er der normalt backend-servere. De behandler anmodninger, som frontserveren modtager fra klienter.

Det kPHP-servere, hvor HTTP-dæmonen kører, fordi HTTPS allerede er dekrypteret. kPHP er en server, der kører på forgaffel modeller: starter en masterproces, en masse underordnede processer, sender lyttestik til dem, og de behandler deres anmodninger. I dette tilfælde genstartes processer ikke mellem hver anmodning fra brugeren, men nulstilles blot deres tilstand til den oprindelige nulværditilstand - anmodning efter anmodning, i stedet for at genstarte.

Belastningsfordeling

Alle vores backends er ikke en enorm pulje af maskiner, der kan behandle enhver anmodning. Vi dem opdelt i separate grupper: generelt, mobil, api, video, iscenesættelse... Problemet på en separat gruppe af maskiner vil ikke påvirke alle andre. I tilfælde af problemer med video, vil brugeren, der lytter til musik, ikke engang vide om problemerne. Hvilken backend, der skal sendes anmodningen til, bestemmes af nginx på forsiden i henhold til konfigurationen.

Metrisk indsamling og rebalancering

For at forstå, hvor mange biler vi skal have i hver gruppe, skal vi stol ikke på QPS. Backends er forskellige, de har forskellige anmodninger, hver anmodning har en forskellig kompleksitet til at beregne QPS. Det er derfor, vi vi opererer med konceptet belastning på serveren som helhed - på CPU og perf.

Vi har tusindvis af sådanne servere. Hver fysisk server kører en kPHP-gruppe for at genbruge alle kernerne (fordi kPHP er enkelttrådet).

Indholdsserver

CS eller Content Server er et lager. CS er en server, der gemmer filer og også behandler uploadede filer og alle mulige synkrone opgaver i baggrunden, som hovedwebfrontenden tildeler den.

Vi har titusindvis af fysiske servere, der gemmer filer. Brugere elsker at uploade filer, og vi elsker at gemme og dele dem. Nogle af disse servere er lukket af specielle pu/pp-servere.

pu/pp

Hvis du åbnede netværksfanen i VK, så du pu/pp.

FAQ om arkitektur og arbejde i VKontakte

Hvad er pu/pp? Hvis vi lukker den ene server efter den anden, så er der to muligheder for at uploade og downloade en fil til den server, der blev lukket: direkte gennem http://cs100500.userapi.com/path eller via mellemserverhttp://pu.vk.com/c100500/path.

Pu er det historiske navn for fotoupload, og pp er fotoproxy. Det vil sige, at en server er til at uploade billeder, og en anden er til at uploade. Nu er ikke kun billeder indlæst, men navnet er bevaret.

Disse servere afslutte HTTPS-sessionerfor at fjerne processorbelastningen fra lageret. Da brugerfiler behandles på disse servere, jo mindre følsomme oplysninger, der er gemt på disse maskiner, jo bedre. For eksempel HTTPS-krypteringsnøgler.

Da maskinerne er lukket af vores andre maskiner, har vi råd til ikke at give dem "hvide" eksterne IP'er, og giv "grå". På denne måde har vi sparet på IP-puljen og garanteret at beskytte maskinerne mod adgang udefra – der er simpelthen ingen IP at komme ind i den.

Modstandsdygtighed over delte IP'er. Med hensyn til fejltolerance fungerer ordningen ens – flere fysiske servere har en fælles fysisk IP, og hardwaren foran vælger, hvor anmodningen skal sendes. Jeg vil tale om andre muligheder senere.

Den kontroversielle pointe er, at i dette tilfælde klienten bevarer færre forbindelser. Hvis der er samme IP for flere maskiner - med samme vært: pu.vk.com eller pp.vk.com, har klientbrowseren en grænse for antallet af samtidige anmodninger til én vært. Men i tiden med allestedsnærværende HTTP/2, tror jeg, at dette ikke længere er så relevant.

Den åbenlyse ulempe ved ordningen er, at den skal pumpe al trafik, som går til lageret, gennem en anden server. Da vi pumper trafik gennem maskiner, kan vi endnu ikke pumpe tung trafik, for eksempel video, ved hjælp af samme ordning. Vi transmitterer det direkte - en separat direkte forbindelse til separate lagre specifikt til video. Vi transmitterer lettere indhold gennem en proxy.

For ikke længe siden fik vi en forbedret version af proxy. Nu vil jeg fortælle dig, hvordan de adskiller sig fra almindelige, og hvorfor det er nødvendigt.

Sol

I september 2017 købte Oracle, som tidligere havde købt Sun, fyrede et stort antal Sun-ansatte. Vi kan sige, at virksomheden i dette øjeblik ophørte med at eksistere. Da de valgte et navn til det nye system, besluttede vores administratorer at hylde dette firmas minde og kaldte det nye system Sun. Indbyrdes kalder vi hende simpelthen "soler".

FAQ om arkitektur og arbejde i VKontakte

pp havde et par problemer. Én IP pr. gruppe - ineffektiv cache. Flere fysiske servere deler en fælles IP-adresse, og der er ingen måde at kontrollere, hvilken server anmodningen går til. Derfor, hvis forskellige brugere kommer for den samme fil, så hvis der er en cache på disse servere, ender filen i hver servers cache. Dette er en meget ineffektiv ordning, men intet kunne gøres.

Følgelig - vi kan ikke sønderdele indhold, fordi vi ikke kan vælge en specifik server til denne gruppe - de har en fælles IP. Også af nogle interne årsager har vi det var ikke muligt at installere sådanne servere i regioner. De stod kun i St. Petersborg.

Med solerne ændrede vi udvælgelsessystemet. Nu har vi anycast routing: dynamisk routing, anycast, selv-tjek daemon. Hver server har sin egen individuelle IP, men et fælles undernet. Alt er konfigureret på en sådan måde, at hvis en server fejler, spredes trafikken automatisk over de andre servere i samme gruppe. Nu er det muligt at vælge en bestemt server, ingen redundant caching, og pålideligheden blev ikke påvirket.

Vægtstøtte. Nu har vi råd til at installere maskiner med forskellig effekt efter behov, og også, i tilfælde af midlertidige problemer, ændre vægten af ​​de arbejdende "soler" for at reducere belastningen på dem, så de "hviler" og begynder at arbejde igen.

Deling efter indholds-id. En sjov ting ved sharding: Vi sønderdeler normalt indhold, så forskellige brugere går til den samme fil gennem den samme "sol", så de har en fælles cache.

Vi lancerede for nylig applikationen "Clover". Dette er en online quiz i en live-udsendelse, hvor værten stiller spørgsmål og brugerne svarer i realtid og vælger muligheder. Appen har en chat, hvor brugere kan chatte. Kan samtidig oprette forbindelse til udsendelsen mere end 100 tusinde mennesker. De skriver alle beskeder, der sendes til alle deltagere, og en avatar følger med beskeden. Hvis 100 tusinde mennesker kommer efter en avatar i én "sol", så kan den nogle gange rulle bag en sky.

For at modstå udbrud af anmodninger om den samme fil, er det for en bestemt type indhold, at vi tænder for et dumt skema, der spreder filer på tværs af alle tilgængelige "soler" i regionen.

Sol indefra

Omvendt proxy på nginx, cache enten i RAM eller på hurtige Optane/NVMe-diske. Eksempel: http://sun4-2.userapi.com/c100500/path — et link til "solen", som er placeret i den fjerde region, den anden servergruppe. Den lukker stifilen, som fysisk ligger på server 100500.

Cache

Vi tilføjer endnu en node til vores arkitektoniske skema - caching-miljøet.

FAQ om arkitektur og arbejde i VKontakte

Nedenfor er layoutdiagrammet regionale cacher, der er omkring 20 af dem. Det er de steder, hvor der findes cacher og "soler", som kan cache trafik gennem sig selv.

FAQ om arkitektur og arbejde i VKontakte

Dette er caching af multimedieindhold; ingen brugerdata gemmes her - kun musik, video, fotos.

For at bestemme brugerens region skal vi vi indsamler BGP-netværkspræfikser annonceret i regionerne. I tilfælde af fallback skal vi også analysere geoip-databasen, hvis vi ikke kunne finde IP'en med præfikser. Vi bestemmer regionen ud fra brugerens IP. I koden kan vi se på en eller flere regioner af brugeren - de punkter, som han er tættest på geografisk.

Hvordan fungerer det?

Vi tæller populariteten af ​​filer efter region. Der er nummeret på den regionale cache, hvor brugeren er placeret, og fil-id'en - vi tager dette par og øger vurderingen med hver download.

Samtidig kommer dæmoner - tjenester i regioner - fra tid til anden til API'en og siger: "Jeg er sådan og sådan en cache, giv mig en liste over de mest populære filer i min region, der endnu ikke er på mig. ” API'en leverer en masse filer sorteret efter vurdering, dæmonen downloader dem, tager dem til regionerne og leverer filerne derfra. Dette er den grundlæggende forskel mellem pu/pp og Sun fra caches: de giver filen gennem sig selv med det samme, selvom denne fil ikke er i cachen, og cachen downloader først filen til sig selv og begynder derefter at give den tilbage.

I dette tilfælde får vi indhold tættere på brugerne og sprede netværksbelastningen. For eksempel distribuerer vi kun fra Moskva-cachen mere end 1 Tbit/s i myldretiden.

Men der er problemer - cache-servere er ikke gummi. For super populært indhold er der nogle gange ikke nok netværk til en separat server. Vores cache-servere er på 40-50 Gbit/s, men der er indhold, der fuldstændig tilstopper sådan en kanal. Vi bevæger os i retning af at implementere lagring af mere end én kopi af populære filer i regionen. Jeg håber, at vi vil implementere det inden årets udgang.

Vi kiggede på den generelle arkitektur.

  • Frontservere, der accepterer anmodninger.
  • Backends, der behandler anmodninger.
  • Lagre, der er lukket af to typer fuldmagter.
  • Regionale cacher.

Hvad mangler der i dette diagram? Selvfølgelig de databaser, som vi gemmer data i.

Databaser eller motorer

Vi kalder dem ikke databaser, men motorer - Motorer, fordi vi praktisk talt ikke har databaser i almindeligt accepteret forstand.

FAQ om arkitektur og arbejde i VKontakte

Dette er en nødvendig foranstaltning. Dette skete, fordi i 2008-2009, hvor VK havde en eksplosiv vækst i popularitet, arbejdede projektet udelukkende på MySQL og Memcache, og der var problemer. MySQL elskede at gå ned og korrupte filer, hvorefter det ikke ville genoprettes, og Memcache forringedes gradvist i ydeevne og måtte genstartes.

Det viser sig, at det stadig mere populære projekt havde vedvarende lagring, som korrumperer data, og en cache, som bremser. Under sådanne forhold er det svært at udvikle et projekt i vækst. Det blev besluttet at forsøge at omskrive de kritiske ting, som projektet havde fokus på, på vores egne cykler.

Løsningen lykkedes. Der var mulighed for at gøre dette, såvel som en ekstrem nødvendighed, fordi andre måder at skalere på ikke eksisterede på det tidspunkt. Der var ikke en masse databaser, NoSQL eksisterede ikke endnu, der var kun MySQL, Memcache, PostrgreSQL - og det er det.

Universel drift. Udviklingen blev ledet af vores team af C-udviklere, og alt blev udført på en ensartet måde. Uanset motoren havde de alle omtrent det samme filformat skrevet til disken, de samme startparametre, behandlede signaler på samme måde og opførte sig nogenlunde ens i tilfælde af kantsituationer og problemer. Med væksten af ​​motorer er det bekvemt for administratorer at betjene systemet - der er ingen zoologisk have, der skal vedligeholdes, og de skal genlære, hvordan man betjener hver ny tredjepartsdatabase, hvilket gjorde det muligt hurtigt og bekvemt at øge deres nummer.

Typer af motorer

Holdet skrev en del motorer. Her er blot nogle af dem: ven, tip, billede, ipdb, breve, lister, logfiler, memcached, meowdb, nyheder, nostradamus, foto, afspilningslister, pmemcached, sandkasse, søg, lagring, likes, opgaver, …

For hver opgave, der kræver en specifik datastruktur eller behandler atypiske anmodninger, skriver C-teamet en ny motor. Hvorfor ikke.

Vi har en separat motor memcached, som ligner en almindelig, men med en masse lækkerier, og som ikke bremser. Ikke ClickHouse, men det virker også. Fås separat pmemcached - Er vedvarende memcached, som desuden kan gemme data på disk, end det passer ind i RAM, for ikke at miste data ved genstart. Der er forskellige motorer til individuelle opgaver: køer, lister, sæt - alt hvad vores projekt kræver.

Klynger

Fra et kodeperspektiv er der ingen grund til at tænke på motorer eller databaser som processer, entiteter eller instanser. Koden fungerer specifikt med klynger, med grupper af motorer - én type pr. klynge. Lad os sige, at der er en memcached klynge - det er bare en gruppe af maskiner.

Koden behøver slet ikke at kende den fysiske placering, størrelse eller antallet af servere. Han går til klyngen ved hjælp af en bestemt identifikator.

For at dette skal virke, skal du tilføje endnu en enhed, der er placeret mellem koden og motorerne - proxy.

RPC proxy

Fuldmagt forbindelsesbus, hvorpå næsten hele siden kører. Samtidig har vi ingen service opdagelse - i stedet er der en konfiguration for denne proxy, som kender placeringen af ​​alle klynger og alle shards af denne klynge. Dette er, hvad administratorer gør.

Programmører er overhovedet ligeglade med hvor meget, hvor og hvad det koster - de går bare til klyngen. Dette giver os meget. Når man modtager en anmodning, omdirigerer fuldmægtigen anmodningen, vel vidende hvor - den bestemmer selv dette.

FAQ om arkitektur og arbejde i VKontakte

I dette tilfælde er proxy et beskyttelsespunkt mod tjenestefejl. Hvis en motor sænker farten eller går ned, forstår proxyen dette og reagerer i overensstemmelse hermed på klientsiden. Dette giver dig mulighed for at fjerne timeout - koden venter ikke på, at motoren reagerer, men forstår, at den ikke fungerer og skal opføre sig anderledes. Koden skal være forberedt på, at databaserne ikke altid virker.

Specifikke implementeringer

Nogle gange vil vi stadig virkelig gerne have en form for ikke-standardløsning som motor. Samtidig blev det besluttet ikke at bruge vores færdiglavede rpc-proxy, skabt specielt til vores motorer, men at lave en separat proxy til opgaven.

Til MySQL, som vi stadig har hist og her, bruger vi db-proxy, og til ClickHouse - Killingehus.

Det fungerer generelt sådan. Der er en bestemt server, den kører kPHP, Go, Python - generelt enhver kode, der kan bruge vores RPC-protokol. Koden kører lokalt på en RPC proxy - hver server, hvor koden er placeret, kører sin egen lokale proxy. Efter anmodning forstår fuldmægtigen, hvor han skal henvende sig.

FAQ om arkitektur og arbejde i VKontakte

Hvis en motor vil gå til en anden, selvom det er en nabo, går den gennem en proxy, fordi naboen kan være i et andet datacenter. Motoren skal ikke være afhængig af at kende placeringen af ​​andet end sig selv - dette er vores standardløsning. Men der er selvfølgelig undtagelser :)

Et eksempel på en TL-ordning, som alle motorer fungerer efter.

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;

Dette er en binær protokol, hvoraf den nærmeste analog er protobuf. Skemaet forudbeskriver valgfrie felter, komplekse typer - udvidelser af indbyggede skalarer og forespørgsler. Alt fungerer efter denne protokol.

RPC over TL over TCP/UDP... UDP?

Vi har en RPC-protokol til at udføre motoranmodninger, der kører oven på TL-skemaet. Alt dette fungerer over en TCP/UDP-forbindelse. TCP er forståeligt, men hvorfor har vi brug for UDP ofte?

UDP hjælper undgå problemet med et stort antal forbindelser mellem servere. Hvis hver server har en RPC-proxy, og den generelt kan gå til en hvilken som helst motor, så er der titusindvis af TCP-forbindelser pr. server. Der er en belastning, men den er ubrugelig. I tilfælde af UDP eksisterer dette problem ikke.

Intet overflødigt TCP-håndtryk. Dette er et typisk problem: Når en ny motor eller en ny server startes, etableres mange TCP-forbindelser på én gang. For små letvægtsanmodninger, for eksempel UDP-nyttelast, er al kommunikation mellem koden og motoren to UDP-pakker: den ene flyver i den ene retning, den anden i den anden. En rundtur - og koden fik svar fra motoren uden håndtryk.

Ja, det hele virker bare med en meget lille procentdel af pakketab. Protokollen har understøttelse af retransmissioner og timeouts, men hvis vi taber meget, får vi næsten TCP, hvilket ikke er rentabelt. Vi kører ikke UDP over oceaner.

Vi har tusindvis af sådanne servere, og ordningen er den samme: en pakke motorer er installeret på hver fysisk server. De er for det meste enkelttrådede for at køre så hurtigt som muligt uden at blokere, og er sønderdelt som enkelttrådede løsninger. Samtidig har vi ikke noget mere pålideligt end disse motorer, og der lægges stor vægt på vedvarende datalagring.

Vedvarende datalagring

Motorer skriver binlogs. En binlog er en fil, i slutningen af ​​hvilken en hændelse for en ændring i tilstand eller data tilføjes. I forskellige løsninger kaldes det forskelligt: ​​binær log, WAL, AOF, men princippet er det samme.

For at forhindre motoren i at genlæse hele binloggen i mange år ved genstart, skriver motorerne snapshots - nuværende tilstand. Hvis det er nødvendigt, læser de først fra den, og læser derefter færdig fra binloggen. Alle binlogs er skrevet i det samme binære format - ifølge TL-skemaet, så administratorer kan administrere dem ligeligt ved hjælp af deres værktøjer. Der er ikke et sådant behov for snapshots. Der er en generel header, der angiver, hvis øjebliksbillede er int, motorens magi, og hvilken krop der ikke er vigtig for nogen. Dette er et problem med den motor, der optog øjebliksbilledet.

Jeg vil hurtigt beskrive princippet om drift. Der er en server, som motoren kører på. Han åbner en ny tom binlog til skrivning og skriver en begivenhed til ændring af den.

FAQ om arkitektur og arbejde i VKontakte

På et tidspunkt beslutter han enten selv at tage et snapshot, eller også modtager han et signal. Serveren opretter en ny fil, skriver hele sin tilstand ind i den, tilføjer den aktuelle binlogstørrelse - offset - til slutningen af ​​filen og fortsætter med at skrive yderligere. Der oprettes ikke en ny binlog.

FAQ om arkitektur og arbejde i VKontakte

På et tidspunkt, når motoren genstartede, vil der være både en binlog og et snapshot på disken. Motoren læser hele øjebliksbilledet og hæver sin tilstand på et bestemt tidspunkt.

FAQ om arkitektur og arbejde i VKontakte

Læser den position, der var på det tidspunkt, hvor snapshot blev oprettet, og størrelsen på binlogen.

FAQ om arkitektur og arbejde i VKontakte

Læser slutningen af ​​binloggen for at få den aktuelle tilstand og fortsætter med at skrive yderligere begivenheder. Dette er et simpelt skema; ​​alle vores motorer fungerer efter det.

Data replikering

Som et resultat heraf datareplikering i vores erklæringsbaseret — vi skriver i binlog ikke nogen sideændringer, men nemlig ændringsanmodninger. Meget lig det, der kommer over netværket, kun lidt modificeret.

Det samme skema bruges ikke kun til replikering, men også at lave sikkerhedskopier. Vi har en motor - en skrivemester, der skriver til binloggen. Et hvilket som helst andet sted, hvor administratorerne har sat det op, kopieres denne binlog, og det er det - vi har en backup.

FAQ om arkitektur og arbejde i VKontakte

Hvis det er nødvendigt læse replikaFor at reducere CPU-læsebelastningen startes læsemotoren simpelthen, som læser slutningen af ​​binloggen og udfører disse kommandoer lokalt.

Lagringen her er meget lille, og det er muligt at finde ud af, hvor meget replikaen halter efter mesteren.

Datadeling i RPC-proxy

Hvordan fungerer sønderdeling? Hvordan forstår proxyen, hvilken klynge shard der skal sendes til? Koden siger ikke: "Send efter 15 skår!" - nej, dette gøres af fuldmægtigen.

Den enkleste ordning er firstint — det første tal i anmodningen.

get(photo100_500) => 100 % N.

Dette er et eksempel på en simpel memcached tekstprotokol, men selvfølgelig kan forespørgsler være komplekse og strukturerede. Eksemplet tager det første tal i forespørgslen og resten, når det divideres med klyngestørrelsen.

Dette er nyttigt, når vi ønsker at have datalokalitet for en enkelt enhed. Lad os sige, at 100 er et bruger- eller gruppe-id, og vi ønsker, at alle data fra én enhed skal være på ét shard til komplekse forespørgsler.

Hvis vi er ligeglade med, hvordan anmodninger er spredt over hele klyngen, er der en anden mulighed - hash hele skåret.

hash(photo100_500) => 3539886280 % N

Vi får også hashen, resten af ​​divisionen og skårnummeret.

Begge disse muligheder virker kun, hvis vi er forberedte på, at når vi øger klyngens størrelse, vil vi opdele den eller øge den flere gange. For eksempel havde vi 16 skår, vi har ikke nok, vi vil have flere - vi kan roligt få 32 uden nedetid. Hvis vi ønsker at øge ikke multipla, vil der være nedetid, fordi vi ikke vil være i stand til nøjagtigt at dele alt op uden tab. Disse muligheder er nyttige, men ikke altid.

Hvis vi skal tilføje eller fjerne et vilkårligt antal servere, bruger vi Konsekvent hashing på ringen a la Ketama. Men samtidig mister vi fuldstændig lokaliteten af ​​dataene; vi er nødt til at flette anmodningen til klyngen, så hver brik returnerer sit eget lille svar, og derefter flette svarene til proxyen.

Der er superspecifikke ønsker. Det ser sådan ud: RPC-proxy modtager anmodningen, bestemmer hvilken klynge der skal gå til og bestemmer shard. Så er der enten skrivemastere, eller hvis klyngen har replika-understøttelse, sender den til en replika efter behov. Fuldmægtigen gør alt dette.

FAQ om arkitektur og arbejde i VKontakte

Logs

Vi skriver logs på flere måder. Den mest oplagte og enkle er skrive logs til memcache.

ring-buffer: prefix.idx = line

Der er et nøglepræfiks - navnet på loggen, en linje, og der er størrelsen på denne log - antallet af linjer. Vi tager et tilfældigt tal fra 0 til antallet af linjer minus 1. Nøglen i memcache er et præfiks sammenkædet med dette tilfældige tal. Vi gemmer loglinjen og den aktuelle tid til værdien.

Når det er nødvendigt at læse logs, udfører vi Multi Get alle nøgler, sorteret efter tid, og får dermed en produktionslog i realtid. Skemaet bruges, når du skal debugge noget i produktionen i realtid, uden at gå i stykker, uden at stoppe eller tillade trafik til andre maskiner, men denne log varer ikke længe.

Til pålidelig opbevaring af træstammer har vi en motor logs-motor. Det er netop derfor, det blev skabt og er meget udbredt i et stort antal klynger. Den største klynge jeg kender af gemmer 600 TB pakkede logfiler.

Motoren er meget gammel, der er klynger, der allerede er 6-7 år gamle. Der er problemer med det, som vi forsøger at løse, for eksempel begyndte vi aktivt at bruge ClickHouse til at gemme logfiler.

Indsamling af logfiler i ClickHouse

Dette diagram viser, hvordan vi går ind i vores motorer.

FAQ om arkitektur og arbejde i VKontakte

Der er kode, der går lokalt via RPC til RPC-proxyen, og den forstår, hvor den skal hen til motoren. Hvis vi vil skrive logs i ClickHouse, skal vi ændre to dele i denne ordning:

  • udskift en eller anden motor med ClickHouse;
  • erstatte RPC-proxyen, som ikke kan få adgang til ClickHouse, med en løsning, der kan, og via RPC.

Motoren er enkel - vi erstatter den med en server eller en klynge af servere med ClickHouse.

Og det gjorde vi for at gå til ClickHouse Killingehus. Hvis vi går direkte fra KittenHouse til ClickHouse, vil det ikke klare sig. Selv uden anmodninger, tilføjes det fra HTTP-forbindelser på et stort antal maskiner. For at ordningen skal fungere, på en server med ClickHouse lokal omvendt proxy hæves, som er skrevet på en sådan måde, at den kan modstå de nødvendige mængder af forbindelser. Det kan også bufre data i sig selv relativt pålideligt.

FAQ om arkitektur og arbejde i VKontakte

Nogle gange ønsker vi ikke at implementere RPC-ordningen i ikke-standardiserede løsninger, for eksempel i nginx. Derfor har KittenHouse mulighed for at modtage logs via UDP.

FAQ om arkitektur og arbejde i VKontakte

Hvis afsenderen og modtageren af ​​logfilerne arbejder på den samme maskine, så er sandsynligheden for at miste en UDP-pakke inden for den lokale vært ret lav. Som et kompromis mellem behovet for at implementere RPC i en tredjepartsløsning og pålidelighed, bruger vi blot UDP-afsendelse. Vi vender tilbage til denne ordning senere.

overvågning

Vi har to typer logfiler: dem, der indsamles af administratorer på deres servere, og dem, der er skrevet af udviklere fra kode. De svarer til to typer målinger: system og produkt.

Systemmålinger

Det virker på alle vores servere netdata, som indsamler statistik og sender dem til Grafit kulstof. Derfor bruges ClickHouse som opbevaringssystem, og ikke Whisper f.eks. Om nødvendigt kan du direkte læse fra ClickHouse, eller bruge grafana til målinger, grafer og rapporter. Som udviklere har vi nok adgang til Netdata og Grafana.

Produktmålinger

For nemheds skyld har vi skrevet en masse ting. For eksempel er der et sæt almindelige funktioner, der giver dig mulighed for at skrive Counts, UniqueCounts-værdier ind i statistik, som sendes et andet sted videre.

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

$stats = statlogsStatData($params)

Efterfølgende kan vi bruge sorterings- og grupperingsfiltre og gøre alt, hvad vi vil af statistik - bygge grafer, konfigurere Watchdogs.

Vi skriver meget mange målinger antallet af begivenheder er fra 600 milliarder til 1 billioner pr. dag. Vi vil dog gerne beholde dem mindst et par årat forstå tendenser i metrics. At sætte det hele sammen er et stort problem, som vi ikke har løst endnu. Jeg vil fortælle dig, hvordan det har fungeret de sidste par år.

Vi har funktioner, der skriver disse metrics til lokal memcachefor at reducere antallet af tilmeldinger. En gang i en kort periode lokalt lanceret statistik-dæmon samler alle optegnelser. Dernæst fusionerer dæmonen metrikken til to lag af servere log-samlere, som samler statistik fra en flok af vores maskiner, så laget bag dem ikke dør.

FAQ om arkitektur og arbejde i VKontakte

Om nødvendigt kan vi skrive direkte til logs-samlere.

FAQ om arkitektur og arbejde i VKontakte

Men at skrive fra kode direkte til samlere, uden at stas-daemom, er en dårligt skalerbar løsning, fordi det øger belastningen på samleren. Løsningen er kun egnet, hvis vi af en eller anden grund ikke kan hæve memcache stats-daemonen på maskinen, eller den styrtede ned, og vi gik direkte.

Dernæst slår log-samlere statistik ind i meowDB - dette er vores database, som også kan gemme metrics.

FAQ om arkitektur og arbejde i VKontakte

Så kan vi foretage binære "nær-SQL" valg fra koden.

FAQ om arkitektur og arbejde i VKontakte

Eksperiment

I sommeren 2018 havde vi et internt hackathon, og ideen opstod om at forsøge at erstatte den røde del af diagrammet med noget, der kunne gemme metrics i ClickHouse. Vi har logs på ClickHouse - hvorfor ikke prøve det?

FAQ om arkitektur og arbejde i VKontakte

Vi havde en ordning, der skrev logs gennem KittenHouse.

FAQ om arkitektur og arbejde i VKontakte

Vi besluttede tilføje endnu et "*Hus" til diagrammet, som vil modtage nøjagtigt metrics i det format, som vores kode skriver dem via UDP. Så forvandler dette *Hus dem til indlæg, som logs, som KittenHouse forstår. Han kan perfekt levere disse logfiler til ClickHouse, som burde kunne læse dem.

FAQ om arkitektur og arbejde i VKontakte

Ordningen med memcache, stats-daemon og logs-collectors database er erstattet med denne.

FAQ om arkitektur og arbejde i VKontakte

Ordningen med memcache, stats-daemon og logs-collectors database er erstattet med denne.

  • Der er en afsendelse fra kode her, som er skrevet lokalt i StatsHouse.
  • StatsHouse skriver UDP-metrics, der allerede er konverteret til SQL-indsæt, til KittenHouse i batches.
  • KittenHouse sender dem til ClickHouse.
  • Hvis vi vil læse dem, så læser vi dem uden om StatsHouse - direkte fra ClickHouse ved hjælp af almindelig SQL.

Er det stadig eksperiment, men vi kan godt lide, hvordan det bliver. Løser vi problemerne med ordningen, så går vi måske helt over til den. Personligt håber jeg det.

Ordningen sparer ikke jern. Færre servere er nødvendige, lokale stats-dæmoner og logs-samlere er ikke nødvendige, men ClickHouse kræver en større server end dem i den nuværende ordning. Færre servere er nødvendige, men de skal være dyrere og mere kraftfulde.

Indsætte

Lad os først se på PHP-implementeringen. Vi udvikler os i git: brug GitLab и TeamCity til indsættelse. Udviklingsgrene flettes ind i mastergrenen, fra masteren til test flettes de til staging og fra staging til produktion.

Før implementering tages den nuværende produktionsgren og den forrige, og diff-filer tages i betragtning i dem - ændringer: oprettet, slettet, ændret. Denne ændring registreres i binlogen til en speciel copyfast-motor, som hurtigt kan replikere ændringer til hele vores serverflåde. Det, der bruges her, er ikke kopiering direkte, men replikation af sladder, når en server sender ændringer til sine nærmeste naboer, dem til deres naboer og så videre. Dette giver dig mulighed for at opdatere koden på tiere og enheder af sekunder på tværs af hele flåden. Når ændringen når den lokale replika, anvender den disse patches på dens lokalt filsystem. Tilbageføring udføres også efter samme skema.

Vi implementerer også kPHP meget, og det har også sin egen udvikling på git ifølge diagrammet ovenfor. Siden dette HTTP-server binær, så kan vi ikke producere diff - den binære udgivelse vejer hundredvis af MB. Derfor er der en anden mulighed her - versionen er skrevet til binlog copyfast. Med hver build øges den, og under tilbagerulning øges den også. Version replikeres til servere. Lokale copyfasts ser, at en ny version er kommet ind i binloggen, og ved den samme sladderreplikering tager de den nyeste version af binæren for sig selv, uden at trætte vores masterserver, men omhyggeligt at sprede belastningen over netværket. Hvad følger yndefuld relancering for den nye version.

For vores motorer, som også i det væsentlige er binære, er ordningen meget ens:

  • git master branch;
  • binær i deb;
  • versionen er skrevet til binlog copyfast;
  • replikeres til servere;
  • serveren trækker en frisk .dep;
  • dpkg -i;
  • yndefuld relancering til ny version.

Forskellen er, at vores binære er pakket i arkiver deb, og når de pumper ud dpkg -i er placeret på systemet. Hvorfor er kPHP implementeret som en binær, og motorer er implementeret som dpkg? Det skete på den måde. Det virker – rør det ikke.

Nyttige links:

Alexey Akulovich er en af ​​dem, der som en del af programudvalget hjælper PHP Rusland den 17. maj bliver den største begivenhed for PHP-udviklere i nyere tid. Se hvilken fed pc vi har, hvad højttalere (to af dem udvikler PHP-kerne!) - virker som noget, du ikke kan gå glip af, hvis du skriver PHP.

Kilde: www.habr.com

Tilføj en kommentar