Vanliga frågor om arkitektur och arbete med VKontakte

Historien om skapandet av VKontakte finns på Wikipedia; den berättades av Pavel själv. Det verkar som att alla redan känner henne. Om insidan, arkitekturen och strukturen på webbplatsen på HighLoad++ Pavel berättade för mig redan 2010. Många servrar har läckt sedan dess, så vi kommer att uppdatera informationen: vi kommer att dissekera den, ta ut insidan, väga den och titta på VK-enheten ur teknisk synvinkel.

Vanliga frågor om arkitektur och arbete med VKontakte

Alexey Akulovich (AterCattus) backend-utvecklare i VKontakte-teamet. Transkriptionen av denna rapport är ett samlat svar på vanliga frågor om driften av plattformen, infrastruktur, servrar och interaktion mellan dem, men inte om utveckling, nämligen om järn. Separat om databaser och vad VK har istället, om att samla in loggar och övervaka hela projektet som helhet. Detaljer under snittet.



I mer än fyra år har jag sysslat med alla möjliga uppgifter relaterade till backend.

  • Ladda upp, lagra, bearbeta, distribuera media: video, livestreaming, ljud, foton, dokument.
  • Infrastruktur, plattform, utvecklarövervakning, loggar, regionala cachar, CDN, proprietärt RPC-protokoll.
  • Integration med externa tjänster: push-meddelanden, extern länkanalys, RSS-flöde.
  • Att hjälpa kollegor med olika frågor, vars svar kräver att man dyker in i okänd kod.

Under den här tiden hade jag ett finger med i många delar av sajten. Jag vill dela med mig av denna erfarenhet.

Allmän arkitektur

Allt börjar som vanligt med en server eller grupp av servrar som accepterar förfrågningar.

Främre server

Frontservern accepterar förfrågningar via HTTPS, RTMP och WSS.

HTTPS - dessa är förfrågningar om huvud- och mobilwebbversionerna av webbplatsen: vk.com och m.vk.com, och andra officiella och inofficiella klienter av vårt API: mobilklienter, budbärare. Vi har mottagning RTMP-trafik för Live-sändningar med separata frontservrar och WSS- anslutningar för Streaming API.

För HTTPS och WSS på servrar är det värt nginx. För RTMP-sändningar bytte vi nyligen till vår egen lösning hade, men det ligger utanför rapportens ram. För feltolerans annonserar dessa servrar vanliga IP-adresser och agerar i grupper så att om det finns ett problem på en av servrarna, går användarförfrågningar inte förlorade. För HTTPS och WSS krypterar samma servrar trafik för att ta del av CPU-belastningen på sig själva.

Vi kommer inte att prata vidare om WSS och RTMP, utan bara om vanliga HTTPS-förfrågningar, som vanligtvis förknippas med ett webbprojekt.

backend

Bakom fronten finns vanligtvis backend-servrar. De behandlar förfrågningar som frontservern tar emot från klienter.

Den kPHP-servrar, där HTTP-demonen körs, eftersom HTTPS redan är dekrypterad. kPHP är en server som körs på förgaffelmodeller: startar en masterprocess, ett gäng underordnade processer, skickar lyssningsuttag till dem och de behandlar sina förfrågningar. I det här fallet startas inte processer om mellan varje begäran från användaren, utan återställer helt enkelt deras tillstånd till det ursprungliga nollvärdestillståndet - begäran efter begäran, istället för att starta om.

Lastfördelning

Alla våra backends är inte en enorm pool av maskiner som kan behandla vilken begäran som helst. Vi dem indelade i separata grupper: allmänt, mobilt, api, video, iscensättning... Problemet på en separat grupp av maskiner kommer inte att påverka alla andra. Vid problem med video kommer användaren som lyssnar på musik inte ens veta om problemen. Vilken backend förfrågan ska skickas till bestäms av nginx på framsidan enligt konfigurationen.

Metrisk insamling och ombalansering

För att förstå hur många bilar vi behöver ha i varje grupp ska vi lita inte på QPS. Backends är olika, de har olika önskemål, varje begäran har olika komplexitet att beräkna QPS. Det är därför vi vi arbetar med konceptet belastning på servern som helhet - på CPU och perf.

Vi har tusentals sådana servrar. Varje fysisk server kör en kPHP-grupp för att återvinna alla kärnor (eftersom kPHP är entrådad).

Innehållsserver

CS eller Content Server är en lagring. CS är en server som lagrar filer och även bearbetar uppladdade filer och alla möjliga synkrona uppgifter i bakgrunden som huvudwebbgränssnittet tilldelar den.

Vi har tiotusentals fysiska servrar som lagrar filer. Användare älskar att ladda upp filer, och vi älskar att lagra och dela dem. Vissa av dessa servrar är stängda av speciella pu/pp-servrar.

pu/pp

Om du öppnade nätverksfliken i VK såg du pu/pp.

Vanliga frågor om arkitektur och arbete med VKontakte

Vad är pu/pp? Om vi ​​stänger en server efter en annan, så finns det två alternativ för att ladda upp och ladda ner en fil till servern som stängdes: direkt genom http://cs100500.userapi.com/path eller via mellanserver - http://pu.vk.com/c100500/path.

Pu är det historiska namnet för fotouppladdning, och pp är fotoproxy. Det vill säga, en server är till för att ladda upp foton och en annan för att ladda upp. Nu laddas inte bara bilder, utan namnet har bevarats.

Dessa servrar avsluta HTTPS-sessionerför att ta bort processorbelastningen från lagringen. Dessutom, eftersom användarfiler bearbetas på dessa servrar, desto mindre känslig information som lagras på dessa maskiner, desto bättre. Till exempel HTTPS-krypteringsnycklar.

Eftersom maskinerna är stängda av våra andra maskiner har vi råd att inte ge dem "vita" externa IP-adresser, och ge "grå". På detta sätt sparade vi på IP-poolen och garanterade att skydda maskinerna från extern åtkomst - det finns helt enkelt ingen IP att komma in i den.

Motståndskraft över delade IP-adresser. När det gäller feltolerans fungerar schemat likadant - flera fysiska servrar har en gemensam fysisk IP, och hårdvaran framför dem väljer var förfrågan ska skickas. Jag ska prata om andra alternativ senare.

Den kontroversiella punkten är att i det här fallet klienten behåller färre anslutningar. Om det finns samma IP för flera maskiner - med samma värd: pu.vk.com eller pp.vk.com, har klientwebbläsaren en gräns för antalet samtidiga förfrågningar till en värd. Men i tiden med allestädes närvarande HTTP/2 tror jag att detta inte längre är så relevant.

Den uppenbara nackdelen med systemet är att det måste pumpa upp all trafik, som går till lagringen, via en annan server. Eftersom vi pumpar trafik genom maskiner kan vi ännu inte pumpa tung trafik, till exempel video, med samma schema. Vi överför det direkt - en separat direktanslutning för separata lagringar specifikt för video. Vi överför lättare innehåll via en proxy.

För inte så länge sedan fick vi en förbättrad version av proxy. Nu ska jag berätta hur de skiljer sig från vanliga och varför detta är nödvändigt.

sol

I september 2017 köpte Oracle, som tidigare hade köpt Sun, sparkade ett stort antal Sun-anställda. Vi kan säga att i detta ögonblick upphörde företaget att existera. När vi valde ett namn för det nya systemet bestämde sig våra administratörer för att hylla detta företags minne och döpte det nya systemet till Sun. Inbördes kallar vi henne helt enkelt "soler".

Vanliga frågor om arkitektur och arbete med VKontakte

pp hade några problem. En IP per grupp - ineffektiv cache. Flera fysiska servrar delar en gemensam IP-adress, och det finns inget sätt att styra vilken server förfrågan ska gå till. Därför, om olika användare kommer för samma fil, då om det finns en cache på dessa servrar, hamnar filen i varje servers cache. Detta är ett mycket ineffektivt system, men ingenting kunde göras.

Följaktligen - vi kan inte skära innehåll, eftersom vi inte kan välja en specifik server för den här gruppen - de har en gemensam IP. Även av några interna skäl har vi det var inte möjligt att installera sådana servrar i regioner. De stod bara i St Petersburg.

Med solarna ändrade vi urvalssystemet. Nu har vi anycast routing: dynamisk routing, anycast, självkontrolldemon. Varje server har sin egen individuella IP, men ett gemensamt subnät. Allt är konfigurerat på ett sådant sätt att om en server misslyckas sprids trafiken automatiskt över de andra servrarna i samma grupp. Nu är det möjligt att välja en specifik server, ingen redundant cachelagring, och tillförlitligheten påverkades inte.

Viktstöd. Nu har vi råd att installera maskiner med olika effekt efter behov, och även, i händelse av tillfälliga problem, ändra vikten på de arbetande "solarna" för att minska belastningen på dem, så att de "vilar" och börjar arbeta igen.

Delning efter innehålls-id. En rolig sak med klippning: vi brukar skärpa innehåll så att olika användare går till samma fil genom samma "sol" så att de har en gemensam cache.

Vi lanserade nyligen applikationen "Clover". Detta är ett online-quiz i en livesändning, där värden ställer frågor och användare svarar i realtid och väljer alternativ. Appen har en chatt där användare kan chatta. Kan samtidigt ansluta till sändningen mer än 100 tusen människor. De skriver alla meddelanden som skickas till alla deltagare, och en avatar följer med meddelandet. Om 100 tusen människor kommer för en avatar i en "sol", så kan den ibland rulla bakom ett moln.

För att motstå skurar av förfrågningar för samma fil är det för en viss typ av innehåll som vi sätter på ett dumt schema som sprider filer över alla tillgängliga "solar" i regionen.

Sol från insidan

Omvänd proxy på nginx, cache antingen i RAM eller på snabba Optane/NVMe-diskar. Exempel: http://sun4-2.userapi.com/c100500/path — en länk till "solen", som finns i den fjärde regionen, den andra servergruppen. Den stänger sökvägsfilen, som fysiskt ligger på server 100500.

Cache

Vi lägger till ytterligare en nod till vårt arkitektoniska schema - cachingmiljön.

Vanliga frågor om arkitektur och arbete med VKontakte

Nedan är layoutdiagrammet regionala cacher, det finns ett 20-tal av dem. Det är de platser där cacher och "solar" finns, som kan cachetrafik genom sig själva.

Vanliga frågor om arkitektur och arbete med VKontakte

Detta är cachning av multimediainnehåll; inga användardata lagras här - bara musik, video, foton.

För att fastställa användarens region, vi vi samlar in BGP-nätverksprefix som annonseras i regionerna. Vid fallback måste vi också analysera geoip-databasen om vi inte kunde hitta IP:n med prefix. Vi bestämmer regionen utifrån användarens IP. I koden kan vi titta på en eller flera regioner hos användaren - de punkter som han är närmast geografiskt.

Hur fungerar det?

Vi räknar filernas popularitet efter region. Det finns ett antal av den regionala cachen där användaren befinner sig, och en filidentifierare - vi tar detta par och ökar betyget med varje nedladdning.

Samtidigt kommer demoner - tjänster i regioner - då och då till API:et och säger: "Jag är en sådan och en sådan cache, ge mig en lista över de mest populära filerna i min region som ännu inte finns på mig. ” API:et levererar ett gäng filer sorterade efter betyg, demonen laddar ner dem, tar dem till regionerna och levererar filerna därifrån. Detta är den grundläggande skillnaden mellan pu/pp och Sun från cacher: de ger filen genom sig själva omedelbart, även om den här filen inte finns i cachen, och cachen laddar först ner filen till sig själv och börjar sedan ge tillbaka den.

I det här fallet får vi innehåll närmare användarna och sprida ut nätverksbelastningen. Till exempel, bara från Moskva-cachen distribuerar vi mer än 1 Tbit/s under rusningstid.

Men det finns problem - cacheservrar är inte av gummi. För superpopulärt innehåll finns det ibland inte tillräckligt med nätverk för en separat server. Våra cacheservrar är på 40-50 Gbit/s, men det finns innehåll som helt täpper till en sådan kanal. Vi går mot att implementera lagring av mer än en kopia av populära filer i regionen. Jag hoppas att vi kommer att genomföra det i slutet av året.

Vi tittade på den allmänna arkitekturen.

  • Frontservrar som accepterar förfrågningar.
  • Backends som processförfrågningar.
  • Förråd som är stängda av två typer av ombud.
  • Regionala cacher.

Vad saknas i detta diagram? Naturligtvis databaserna där vi lagrar data.

Databaser eller motorer

Vi kallar dem inte databaser, utan motorer - motorer, eftersom vi praktiskt taget inte har databaser i allmänt accepterad mening.

Vanliga frågor om arkitektur och arbete med VKontakte

Detta är en nödvändig åtgärd.. Detta hände för att 2008-2009, när VK hade en explosiv tillväxt i popularitet, fungerade projektet helt på MySQL och Memcache och det uppstod problem. MySQL älskade att krascha och korrumpera filer, varefter det inte återhämtade sig, och Memcache försämrades gradvis i prestanda och var tvungen att startas om.

Det visar sig att det allt populärare projektet hade beständig lagring, som korrumperar data, och en cache, som saktar ner. Under sådana förhållanden är det svårt att utveckla ett växande projekt. Det beslutades att försöka skriva om de kritiska saker som projektet fokuserade på på våra egna cyklar.

Lösningen var framgångsrik. Det fanns en möjlighet att göra detta, liksom en extrem nödvändighet, eftersom andra sätt att skala inte fanns på den tiden. Det fanns inte ett gäng databaser, NoSQL fanns inte ännu, det fanns bara MySQL, Memcache, PostrgreSQL - och det är allt.

Universell drift. Utvecklingen leddes av vårt team av C-utvecklare och allt gjordes konsekvent. Oavsett motor hade de alla ungefär samma filformat skrivet till disk, samma startparametrar, bearbetade signaler på samma sätt och betedde sig ungefär likadant vid kantsituationer och problem. Med tillväxten av motorer är det bekvämt för administratörer att använda systemet - det finns ingen djurpark som behöver underhållas, och de måste lära sig om hur man använder varje ny tredjepartsdatabas, vilket gjorde det möjligt att snabbt och bekvämt öka deras antal.

Typer av motorer

Teamet skrev en hel del motorer. Här är bara några av dem: vän, tips, bild, ipdb, brev, listor, loggar, memcached, meowdb, nyheter, nostradamus, foto, spellistor, pmemcached, sandlåda, sök, lagring, likes, uppgifter, …

För varje uppgift som kräver en specifik datastruktur eller behandlar atypiska förfrågningar, skriver C-teamet en ny motor. Varför inte.

Vi har en separat motor memcached, som liknar en vanlig, men med en massa godsaker, och som inte saktar ner. Inte ClickHouse, men det fungerar också. Finns separat pmemcachad - Är ihållande memcachad, som också kan lagra data på disk, dessutom, än passar in i RAM, för att inte förlora data vid omstart. Det finns olika motorer för individuella uppgifter: köer, listor, set - allt som vårt projekt kräver.

Kluster

Ur ett kodperspektiv finns det inget behov av att tänka på motorer eller databaser som processer, enheter eller instanser. Koden fungerar specifikt med kluster, med grupper av motorer - en typ per kluster. Låt oss säga att det finns ett memcachat kluster - det är bara en grupp maskiner.

Koden behöver inte känna till den fysiska platsen, storleken eller antalet servrar alls. Han går till klustret med hjälp av en viss identifierare.

För att detta ska fungera måste du lägga till ytterligare en enhet som finns mellan koden och motorerna - ombud.

RPC-proxy

Ombud anslutande buss, där nästan hela webbplatsen körs. Samtidigt har vi ingen tjänst upptäckt — istället finns det en konfiguration för denna proxy, som känner till platsen för alla kluster och alla skärvor av detta kluster. Detta är vad administratörer gör.

Programmerare bryr sig inte alls om hur mycket, var och vad det kostar - de går bara till klustret. Detta tillåter oss mycket. När man tar emot en förfrågan omdirigerar proxyn förfrågan, att veta vart - den bestämmer detta själv.

Vanliga frågor om arkitektur och arbete med VKontakte

I det här fallet är proxy en skyddspunkt mot tjänstefel. Om någon motor saktar ner eller kraschar, förstår proxyn detta och svarar i enlighet med klientsidan. Detta gör att du kan ta bort timeout - koden väntar inte på att motorn ska svara, men förstår att den inte fungerar och måste bete sig annorlunda på något sätt. Koden måste vara förberedd för att databaserna inte alltid fungerar.

Specifika implementeringar

Ibland vill vi ändå verkligen ha någon form av icke-standardlösning som motor. Samtidigt beslutades det att inte använda vår färdiga rpc-proxy, skapad specifikt för våra motorer, utan att göra en separat proxy för uppgiften.

För MySQL, som vi fortfarande har här och där, använder vi db-proxy, och för ClickHouse - Kattungehus.

Det fungerar generellt så här. Det finns en viss server, den kör kPHP, Go, Python - i allmänhet vilken kod som helst som kan använda vårt RPC-protokoll. Koden körs lokalt på en RPC-proxy - varje server där koden finns kör sin egen lokala proxy. På begäran förstår proxy vart den ska vända sig.

Vanliga frågor om arkitektur och arbete med VKontakte

Om en motor vill gå till en annan, även om det är en granne, går den via en proxy, eftersom grannen kan vara i ett annat datacenter. Motorn ska inte förlita sig på att veta var något annat än sig själv befinner sig - det här är vår standardlösning. Men det finns givetvis undantag :)

Ett exempel på ett TL-schema enligt vilket alla motorer fungerar.

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;

Detta är ett binärt protokoll, vars närmaste analog är protobuf. Schemat beskriver valfria fält, komplexa typer - tillägg av inbyggda skalärer och frågor. Allt fungerar enligt detta protokoll.

RPC över TL över TCP/UDP... UDP?

Vi har ett RPC-protokoll för exekvering av motorförfrågningar som körs ovanpå TL-schemat. Allt detta fungerar över en TCP/UDP-anslutning. TCP är förståeligt, men varför behöver vi UDP ofta?

UDP hjälper undvika problemet med ett stort antal anslutningar mellan servrar. Om varje server har en RPC-proxy och i allmänhet kan den gå till vilken motor som helst, så finns det tiotusentals TCP-anslutningar per server. Det finns en belastning, men den är värdelös. I fallet med UDP existerar inte detta problem.

Inget redundant TCP-handslag. Detta är ett typiskt problem: när en ny motor eller en ny server startas upprättas många TCP-anslutningar på en gång. För små lätta förfrågningar, till exempel UDP-nyttolast, är all kommunikation mellan koden och motorn två UDP-paket: den ena flyger åt ena hållet, den andra åt den andra. En tur och retur - och koden fick svar från motorn utan handslag.

Ja, allt bara fungerar med en mycket liten andel paketförlust. Protokollet har stöd för återsändningar och timeouts, men tappar vi mycket får vi nästan TCP, vilket inte är lönsamt. Vi kör inte UDP över hav.

Vi har tusentals sådana servrar, och schemat är detsamma: ett paket med motorer är installerat på varje fysisk server. De är för det mesta enkelgängade för att köras så snabbt som möjligt utan att blockeras, och delas som entrådiga lösningar. Samtidigt har vi inget mer tillförlitligt än dessa motorer, och mycket uppmärksamhet ägnas åt beständig datalagring.

Beständig datalagring

Motorer skriver binlogs. En binlog är en fil i slutet av vilken en händelse för en ändring av tillstånd eller data läggs till. I olika lösningar kallas det på olika sätt: binär logg, WAL, AOF, men principen är densamma.

För att hindra motorn från att läsa om hela binloggen under många år vid omstart, skriver motorerna ögonblicksbilder - aktuellt tillstånd. Om det behövs läser de först från den och avslutar sedan läsningen från binloggen. Alla binloggar är skrivna i samma binära format - enligt TL-schemat, så att administratörer kan administrera dem lika med sina verktyg. Det finns inget sådant behov av ögonblicksbilder. Det finns en allmän rubrik som anger vems ögonblicksbild som är int, motorns magi och vilken kropp som inte är viktig för någon. Detta är ett problem med motorn som registrerade ögonblicksbilden.

Jag kommer snabbt att beskriva funktionsprincipen. Det finns en server som motorn körs på. Han öppnar en ny tom binlogg för att skriva och skriver en händelse för ändring av den.

Vanliga frågor om arkitektur och arbete med VKontakte

Vid något tillfälle bestämmer han sig antingen för att själv ta en ögonblicksbild, eller så får han en signal. Servern skapar en ny fil, skriver in hela dess tillstånd i den, lägger till den aktuella binlogstorleken - offset - i slutet av filen och fortsätter att skriva. En ny binlogg skapas inte.

Vanliga frågor om arkitektur och arbete med VKontakte

Vid något tillfälle, när motorn startat om, kommer det att finnas både en binlog och en ögonblicksbild på skivan. Motorn läser av hela ögonblicksbilden och höjer sitt tillstånd vid en viss punkt.

Vanliga frågor om arkitektur och arbete med VKontakte

Läser positionen som var när ögonblicksbilden skapades och storleken på binloggen.

Vanliga frågor om arkitektur och arbete med VKontakte

Läser slutet av binloggen för att få aktuell status och fortsätter att skriva ytterligare händelser. Detta är ett enkelt schema; alla våra motorer fungerar enligt det.

Data replikering

Som ett resultat, datareplikering i vår uttalande baserad — vi skriver i binloggen inga sidändringar, utan nämligen ändringsförfrågningar. Mycket likt det som kommer över nätverket, bara något modifierat.

Samma schema används inte bara för replikering, utan också för att skapa säkerhetskopior. Vi har en motor - en skrivmästare som skriver till binloggen. På alla andra ställen där administratörerna ställer in det, kopieras den här binloggen, och det är allt - vi har en säkerhetskopia.

Vanliga frågor om arkitektur och arbete med VKontakte

Om det behövs läsreplikaFör att minska CPU-läsbelastningen startas helt enkelt läsmotorn, som läser slutet av binloggen och exekverar dessa kommandon lokalt.

Fördröjningen här är mycket liten, och det är möjligt att ta reda på hur mycket repliken släpar efter mastern.

Datadelning i RPC-proxy

Hur fungerar skärning? Hur förstår proxyn vilken klusterbit som ska skickas till? Koden säger inte: "Skicka efter 15 skärvor!" - nej, detta görs av ombudet.

Det enklaste schemat är firstint — den första siffran i begäran.

get(photo100_500) => 100 % N.

Detta är ett exempel på ett enkelt memcachat textprotokoll, men naturligtvis kan frågor vara komplexa och strukturerade. Exemplet tar det första talet i frågan och resten när de divideras med klusterstorleken.

Detta är användbart när vi vill ha datalokalitet för en enda enhet. Låt oss säga att 100 är ett användar- eller grupp-ID, och vi vill att all data för en enhet ska finnas på en shard för komplexa frågor.

Om vi ​​inte bryr oss om hur förfrågningar sprids över klustret finns det ett annat alternativ - hasha hela skärvan.

hash(photo100_500) => 3539886280 % N

Vi får också hashen, resten av divisionen och skärvans nummer.

Båda dessa alternativ fungerar bara om vi är förberedda på det faktum att när vi ökar storleken på klustret kommer vi att dela upp det eller öka det med flera gånger. Till exempel hade vi 16 skärvor, vi har inte tillräckligt, vi vill ha fler - vi kan säkert få 32 utan stillestånd. Om vi ​​vill öka inte multiplar, kommer det att finnas driftstopp, eftersom vi inte kommer att kunna dela upp allt korrekt utan förluster. Dessa alternativ är användbara, men inte alltid.

Om vi ​​behöver lägga till eller ta bort ett godtyckligt antal servrar använder vi Konsekvent hash på ringen a la Ketama. Men samtidigt tappar vi helt lokaliteten för data, vi måste slå samman begäran till klustret så att varje del returnerar sitt eget lilla svar och sedan slå samman svaren till proxyn.

Det finns superspecifika önskemål. Det ser ut så här: RPC-proxy tar emot begäran, bestämmer vilket kluster som ska gå till och bestämmer fragmentet. Sedan finns det antingen skrivmaster, eller, om klustret har replikstöd, skickar det till en replik på begäran. Proxyn gör allt detta.

Vanliga frågor om arkitektur och arbete med VKontakte

Loggar

Vi skriver loggar på flera sätt. Den mest uppenbara och enkla är skriv loggar till memcache.

ring-buffer: prefix.idx = line

Det finns ett nyckelprefix - namnet på loggen, en rad, och det finns storleken på denna logg - antalet rader. Vi tar ett slumptal från 0 till antalet rader minus 1. Nyckeln i memcache är ett prefix sammanlänkade med detta slumptal. Vi sparar loggraden och aktuell tid till värdet.

När det är nödvändigt att läsa loggar utför vi Multi Get alla nycklar, sorterade efter tid, och får på så sätt en produktionslogg i realtid. Schemat används när du behöver felsöka något i produktionen i realtid, utan att gå sönder något, utan att stoppa eller tillåta trafik till andra maskiner, men den här loggen varar inte länge.

För pålitlig lagring av stockar har vi en motor stockar-motor. Det är just därför det skapades och används i stor utsträckning i ett stort antal kluster. Det största klustret jag känner till lagrar 600 TB packade stockar.

Motorn är väldigt gammal, det finns kluster som redan är 6-7 år gamla. Det finns problem med det som vi försöker lösa, till exempel började vi aktivt använda ClickHouse för att lagra loggar.

Samla loggar i ClickHouse

Detta diagram visar hur vi går in i våra motorer.

Vanliga frågor om arkitektur och arbete med VKontakte

Det finns kod som går lokalt via RPC till RPC-proxyn, och den förstår var den ska gå till motorn. Om vi ​​vill skriva loggar i ClickHouse måste vi ändra två delar i detta schema:

  • byt ut någon motor med ClickHouse;
  • ersätt RPC-proxyn, som inte kan komma åt ClickHouse, med någon lösning som kan, och via RPC.

Motorn är enkel - vi ersätter den med en server eller ett kluster av servrar med ClickHouse.

Och för att gå till ClickHouse gjorde vi det KittenHouse. Om vi ​​går direkt från KittenHouse till ClickHouse kommer det inte att klara det. Även utan förfrågningar, adderas det från HTTP-anslutningar för ett stort antal maskiner. För att schemat ska fungera, på en server med ClickHouse lokal omvänd proxy höjs, som är skriven på ett sådant sätt att den tål de erforderliga anslutningsvolymerna. Den kan också buffra data i sig själv relativt tillförlitligt.

Vanliga frågor om arkitektur och arbete med VKontakte

Ibland vill vi inte implementera RPC-schemat i icke-standardiserade lösningar, till exempel i nginx. Därför har KittenHouse möjlighet att ta emot loggar via UDP.

Vanliga frågor om arkitektur och arbete med VKontakte

Om avsändaren och mottagaren av loggarna fungerar på samma maskin, är sannolikheten att förlora ett UDP-paket inom den lokala värden ganska låg. Som en kompromiss mellan behovet av att implementera RPC i en tredjepartslösning och tillförlitlighet använder vi helt enkelt UDP-sändning. Vi återkommer till detta upplägg senare.

övervakning

Vi har två typer av loggar: de som samlas in av administratörer på deras servrar och de som skrivs av utvecklare från kod. De motsvarar två typer av mätvärden: system och produkt.

Systemmått

Det fungerar på alla våra servrar nätdata, som samlar in statistik och skickar den till Grafit kol. Därför används ClickHouse som lagringssystem, och inte till exempel Whisper. Vid behov kan du läsa direkt från ClickHouse, eller använda grafana för mätvärden, grafer och rapporter. Som utvecklare har vi tillräckligt med tillgång till Netdata och Grafana.

Produktmått

För enkelhetens skull har vi skrivit en massa saker. Till exempel finns det en uppsättning vanliga funktioner som låter dig skriva Counts, UniqueCounts-värden till statistik, som skickas någonstans längre.

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

$stats = statlogsStatData($params)

Därefter kan vi använda sorterings- och grupperingsfilter och göra allt vi vill av statistik - bygga grafer, konfigurera Watchdogs.

Vi skriver väldigt många mått antalet händelser är från 600 miljarder till 1 biljon per dag. Vi vill dock behålla dem åtminstone ett par årför att förstå trender i mått. Att få ihop allt är ett stort problem som vi inte har löst ännu. Jag ska berätta hur det har fungerat de senaste åren.

Vi har funktioner som skriver dessa mått till lokal memcacheför att minska antalet anmälningar. En gång på kort tid lokalt lanserad statistik-daemon samlar alla register. Därefter slår demonen samman mätvärdena till två lager av servrar stockar-samlare, som samlar statistik från ett gäng av våra maskiner så att lagret bakom dem inte dör.

Vanliga frågor om arkitektur och arbete med VKontakte

Vid behov kan vi skriva direkt till stocksamlare.

Vanliga frågor om arkitektur och arbete med VKontakte

Men att skriva från kod direkt till samlare, förbi stas-daemom, är en dåligt skalbar lösning eftersom det ökar belastningen på samlaren. Lösningen är endast lämplig om vi av någon anledning inte kan höja memcache stats-daemon på maskinen, eller om den kraschade och vi gick direkt.

Därefter slår loggsamlare samman statistik till mjauDB - det här är vår databas, som också kan lagra mätvärden.

Vanliga frågor om arkitektur och arbete med VKontakte

Sedan kan vi göra binära "nära-SQL"-val från koden.

Vanliga frågor om arkitektur och arbete med VKontakte

Experiment

Sommaren 2018 hade vi ett internt hackathon, och idén kom upp att försöka ersätta den röda delen av diagrammet med något som kunde lagra mätvärden i ClickHouse. Vi har loggar på ClickHouse - varför inte prova det?

Vanliga frågor om arkitektur och arbete med VKontakte

Vi hade ett schema som skrev loggar genom KittenHouse.

Vanliga frågor om arkitektur och arbete med VKontakte

Vi bestämde lägg till ytterligare ett "*hus" till diagrammet, som kommer att ta emot exakt mätvärdena i formatet som vår kod skriver dem via UDP. Sedan förvandlar detta *Hus dem till inlägg, som stockar, vilket KittenHouse förstår. Han kan perfekt leverera dessa loggar till ClickHouse, som borde kunna läsa dem.

Vanliga frågor om arkitektur och arbete med VKontakte

Schemat med memcache, stats-daemon och logs-collectors databas ersätts med denna.

Vanliga frågor om arkitektur och arbete med VKontakte

Schemat med memcache, stats-daemon och logs-collectors databas ersätts med denna.

  • Det finns en utskick från kod här, som är skriven lokalt i StatsHouse.
  • StatsHouse skriver UDP-mått, som redan konverterats till SQL-inlägg, till KittenHouse i omgångar.
  • KittenHouse skickar dem till ClickHouse.
  • Om vi ​​vill läsa dem, så läser vi dem förbi StatsHouse - direkt från ClickHouse med vanlig SQL.

Är det fortfarande experiment, men vi gillar hur det blir. Om vi ​​åtgärdar problemen med schemat kanske vi byter till det helt. Personligen hoppas jag det.

Schemat sparar inte järn. Färre servrar behövs, lokala statistik-demoner och loggar-samlare behövs inte, men ClickHouse kräver en större server än de i det nuvarande schemat. Färre servrar behövs, men de måste vara dyrare och kraftfullare.

Distribuera

Låt oss först titta på PHP-distributionen. Vi utvecklas i : använda sig av GitLab и TeamCity för utplacering. Utvecklingsgrenar slås samman till mastergrenen, från mastern för testning slås de samman till staging och från staging till produktion.

Före distribution tas den nuvarande produktionsgrenen och den föregående, och diff-filer beaktas i dem - ändringar: skapad, raderad, ändrad. Denna förändring registreras i binloggen till en speciell copyfast-motor, som snabbt kan replikera ändringar till hela vår serverflotta. Det som används här är inte kopiering direkt, utan replikering av skvaller, när en server skickar ändringar till sina närmaste grannar, de till sina grannar och så vidare. Detta gör att du kan uppdatera koden på tiotals och enheter av sekunder över hela flottan. När ändringen når den lokala repliken applicerar den dessa patchar på sin lokalt filsystem. Återställning utförs också enligt samma schema.

Vi distribuerar också kPHP mycket och det har också sin egen utveckling på enligt diagrammet ovan. Sedan detta HTTP-server binär, då kan vi inte producera diff - utgivningsbinären väger hundratals MB. Därför finns det ett annat alternativ här - versionen är skriven till binlog copyfast. För varje byggnad ökar den, och under återställning ökar den också. Version replikeras till servrar. Lokala copyfasts ser att en ny version har kommit in i binloggen, och genom samma skvallerreplikering tar de den senaste versionen av binären för sig själva, utan att trötta ut vår masterserver, utan att försiktigt sprida belastningen över nätverket. Vad som följer graciös nylansering för den nya versionen.

För våra motorer, som också i huvudsak är binära, är schemat väldigt likt:

  • git master branch;
  • binär i . Deb;
  • versionen är skriven till binlog copyfast;
  • replikeras till servrar;
  • servern drar ut en ny .dep;
  • dpkg -i;
  • graciös nylansering till ny version.

Skillnaden är att vår binär är förpackad i arkiv . Deb, och när de pumpar ut de dpkg -i placeras på systemet. Varför distribueras kPHP som en binär och motorer distribueras som dpkg? Det blev så. Det fungerar – rör det inte.

Användbara länkar:

Alexey Akulovich är en av dem som, som en del av programkommittén, hjälper till PHP Ryssland den 17 maj kommer att bli det största evenemanget för PHP-utvecklare på senare tid. Titta vilken cool PC vi har, vadå högtalare (två av dem utvecklar PHP-kärna!) - verkar vara något du inte får missa om du skriver PHP.

Källa: will.com

Lägg en kommentar