Często zadawane pytania dotyczące architektury i pracy VKontakte

Historia powstania VKontakte znajduje się w Wikipedii, opowiedział ją sam Pavel. Wygląda na to, że wszyscy ją już znają. O wnętrzu, architekturze i strukturze witryny na HighLoad++ Pavel powiedział mi w 2010 roku. Od tego czasu wyciekło wiele serwerów, dlatego będziemy aktualizować informacje: rozbiorą je, wyjmiemy wnętrze, zważymy i spojrzymy na urządzenie VK od strony technicznej.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Aleksiej Akulowicz (AterCattus) programista backendu w zespole VKontakte. Zapis tego raportu jest zbiorczą odpowiedzią na najczęściej zadawane pytania dotyczące działania platformy, infrastruktury, serwerów i interakcji pomiędzy nimi, ale nie o rozwój, a mianowicie o żelazie. Osobno o bazach danych i o tym, co zamiast tego ma VK, o zbieraniu logów i monitorowaniu całego projektu jako całości. Szczegóły pod rozcięciem.



Od ponad czterech lat zajmuję się wszelkiego rodzaju zadaniami związanymi z backendem.

  • Przesyłanie, przechowywanie, przetwarzanie, dystrybucja multimediów: wideo, transmisji na żywo, audio, zdjęć, dokumentów.
  • Infrastruktura, platforma, monitorowanie programistów, logi, regionalne pamięci podręczne, CDN, autorski protokół RPC.
  • Integracja z usługami zewnętrznymi: powiadomienia push, parsowanie linków zewnętrznych, kanał RSS.
  • Pomaganie kolegom z różnymi pytaniami, na które odpowiedzi wymagają zagłębienia się w nieznany kod.

W tym czasie miałem swój udział w wielu elementach serwisu. Chcę podzielić się tym doświadczeniem.

Architektura ogólna

Wszystko jak zwykle zaczyna się od serwera lub grupy serwerów, które przyjmują żądania.

Serwer frontowy

Serwer frontowy akceptuje żądania za pośrednictwem protokołu HTTPS, RTMP i WSS.

HTTPS - są to żądania dotyczące głównej i mobilnej wersji internetowej witryny: vk.com i m.vk.com oraz innych oficjalnych i nieoficjalnych klientów naszego API: klientów mobilnych, komunikatorów. Mamy przyjęcie RTMP-ruch dla transmisji na żywo z oddzielnymi serwerami frontowymi i WSS- połączenia dla Streaming API.

Dla HTTPS i WSS na serwerach warto nginx. W przypadku transmisji RTMP niedawno przeszliśmy na własne rozwiązanie kiwa, ale wykracza to poza zakres raportu. Aby zapewnić odporność na awarie, serwery te ogłaszają wspólne adresy IP i działają w grupach, dzięki czemu w przypadku problemu na jednym z serwerów żądania użytkowników nie zostaną utracone. W przypadku HTTPS i WSS te same serwery szyfrują ruch, aby przejąć na siebie część obciążenia procesora.

Nie będziemy dalej mówić o WSS i RTMP, a jedynie o standardowych żądaniach HTTPS, które zwykle kojarzą się z projektem internetowym.

Backend

Za frontem zwykle znajdują się serwery backendowe. Przetwarzają żądania, które serwer frontowy otrzymuje od klientów.

To serwery kPHP, na którym działa demon HTTP, ponieważ protokół HTTPS jest już odszyfrowany. kPHP to serwer, na którym działa modele z preforkiem: uruchamia proces główny, czyli kilka procesów potomnych, przekazuje im gniazda nasłuchujące, a one przetwarzają ich żądania. W tym przypadku procesy nie są ponownie uruchamiane pomiędzy każdym żądaniem użytkownika, ale po prostu resetują swój stan do pierwotnego stanu o wartości zerowej – żądanie po żądaniu, zamiast restartować.

Rozkład obciążenia

Wszystkie nasze backendy nie są ogromną pulą maszyn, które mogą obsłużyć dowolne żądanie. My, oni podzielone na osobne grupy: ogólne, mobilne, api, wideo, staging... Problem na osobnej grupie komputerów nie będzie miał wpływu na wszystkie inne. W przypadku problemów z obrazem użytkownik słuchający muzyki nawet nie będzie o nich wiedział. O tym, do którego backendu wysłać żądanie, decyduje nginx z przodu, zgodnie z konfiguracją.

Gromadzenie i równoważenie metryk

Aby zrozumieć, ile samochodów musimy mieć w każdej grupie, musimy nie polegaj na QPS. Backendy są różne, mają różne żądania, każde żądanie ma inną złożoność obliczania QPS. Dlatego my działamy w oparciu o koncepcję obciążenia serwera jako całości – procesora i wydajności.

Mamy tysiące takich serwerów. Każdy serwer fizyczny uruchamia grupę kPHP w celu recyklingu wszystkich rdzeni (ponieważ kPHP jest jednowątkowy).

Serwer treści

CS lub Content Server to magazyn. CS to serwer, który przechowuje pliki, a także przetwarza przesłane pliki i wszelkiego rodzaju zadania synchroniczne w tle, które przypisuje mu główny interfejs WWW.

Mamy dziesiątki tysięcy serwerów fizycznych, na których przechowywane są pliki. Użytkownicy uwielbiają przesyłać pliki, a my uwielbiamy je przechowywać i udostępniać. Niektóre z tych serwerów są zamknięte przez specjalne serwery pu/pp.

pu/pp

Jeśli otworzyłeś kartę sieci w VK, zobaczyłeś pu/pp.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Co to jest pu/pp? Jeśli zamykamy jeden serwer po drugim, wówczas istnieją dwie możliwości wgrania i pobrania pliku na serwer, który został zamknięty: bezpośrednio przez http://cs100500.userapi.com/path lub za pośrednictwem serwera pośredniego - http://pu.vk.com/c100500/path.

Pu to historyczna nazwa przesyłania zdjęć, a pp to serwer proxy zdjęć. Oznacza to, że jeden serwer służy do przesyłania zdjęć, a drugi do przesyłania. Teraz ładowane są nie tylko zdjęcia, ale nazwa została zachowana.

Te serwery zakończyć sesje HTTPSaby usunąć obciążenie procesora z pamięci. Ponadto, ponieważ pliki użytkowników są przetwarzane na tych serwerach, im mniej wrażliwych informacji jest przechowywanych na tych komputerach, tym lepiej. Na przykład klucze szyfrujące HTTPS.

Ponieważ maszyny są zamykane przez inne nasze maszyny, możemy sobie pozwolić na to, aby nie nadawać im „białych” zewnętrznych adresów IP i daj „szary”. W ten sposób zaoszczędziliśmy na puli adresów IP i gwarantujemy ochronę maszyn przed dostępem z zewnątrz - po prostu nie ma adresu IP, który mógłby się do nich dostać.

Odporność na współdzielone adresy IP. Jeśli chodzi o odporność na awarie, schemat działa tak samo - kilka serwerów fizycznych ma wspólny fizyczny adres IP, a sprzęt znajdujący się przed nimi wybiera, dokąd wysłać żądanie. O innych opcjach porozmawiam później.

Kontrowersyjne jest to, że w tym przypadku klient utrzymuje mniej połączeń. Jeśli dla kilku maszyn jest ten sam adres IP - z tym samym hostem: pu.vk.com lub pp.vk.com, przeglądarka klienta ma ograniczenie liczby jednoczesnych żądań do jednego hosta. Jednak w dobie wszechobecnego protokołu HTTP/2 uważam, że nie jest to już tak istotne.

Oczywistą wadą tego schematu jest to, że musi pompować cały ruch, który trafia do magazynu, za pośrednictwem innego serwera. Ponieważ pompujemy ruch przez maszyny, nie możemy jeszcze pompować dużego ruchu, na przykład wideo, używając tego samego schematu. Przesyłamy go bezpośrednio - oddzielne bezpośrednie połączenie dla oddzielnych magazynów specjalnie dla wideo. Przesyłamy lżejsze treści za pośrednictwem serwera proxy.

Niedawno otrzymaliśmy ulepszoną wersję proxy. Teraz powiem ci, czym różnią się od zwykłych i dlaczego jest to konieczne.

Niedz

We wrześniu 2017 r. Oracle, który wcześniej kupił firmę Sun, zwolnił ogromną liczbę pracowników firmy Sun. Można powiedzieć, że w tym momencie firma przestała istnieć. Wybierając nazwę dla nowego systemu, nasi administratorzy postanowili oddać hołd pamięci tej firmy i nadali nowemu systemowi nazwę Sun. Między sobą nazywamy ją po prostu „słońcami”.

Często zadawane pytania dotyczące architektury i pracy VKontakte

pp miał kilka problemów. Jeden adres IP na grupę - nieefektywna pamięć podręczna. Kilka serwerów fizycznych ma ten sam adres IP i nie ma możliwości kontrolowania, do którego serwera trafi żądanie. Dlatego jeśli różni użytkownicy przychodzą po ten sam plik, to jeśli na tych serwerach znajduje się pamięć podręczna, plik trafia do pamięci podręcznej każdego serwera. To bardzo nieefektywny schemat, ale nic nie można zrobić.

W konsekwencji - nie możemy dzielić treści, ponieważ nie możemy wybrać konkretnego serwera dla tej grupy - mają one wspólne IP. Również z pewnych powodów wewnętrznych nie było możliwości zainstalowania takich serwerów w regionach. Stali tylko w Petersburgu.

Wraz ze słońcami zmieniliśmy system selekcji. Teraz mamy routing anycast: routing dynamiczny, anycast, demon samokontroli. Każdy serwer ma swój indywidualny adres IP, ale wspólną podsieć. Wszystko jest skonfigurowane w taki sposób, że w przypadku awarii jednego serwera ruch automatycznie rozkłada się na pozostałe serwery tej samej grupy. Teraz istnieje możliwość wyboru konkretnego serwera, bez zbędnego buforowaniai niezawodność nie została naruszona.

Wsparcie wagi. Teraz możemy sobie pozwolić na montaż maszyn o różnej mocy w zależności od potrzeb, a także w przypadku przejściowych problemów na zmianę ciężarów pracujących „słońc”, aby zmniejszyć ich obciążenie, aby „odpoczęły” i ponownie zabrały się do pracy.

Fragmentowanie według identyfikatora treści. Zabawna rzecz dotycząca fragmentowania: zwykle dzielimy zawartość na fragmenty, dzięki czemu różni użytkownicy korzystają z tego samego pliku przez to samo „słońce”, dzięki czemu mają wspólną pamięć podręczną.

Niedawno uruchomiliśmy aplikację „Koniczyna”. Jest to quiz online w transmisji na żywo, w którym prowadzący zadaje pytania, a użytkownicy odpowiadają w czasie rzeczywistym, wybierając opcje. Aplikacja posiada czat, na którym użytkownicy mogą rozmawiać. Można jednocześnie połączyć się z transmisją ponad 100 tysięcy osób. Wszyscy piszą wiadomości, które są wysyłane do wszystkich uczestników, a do wiadomości dołączany jest awatar. Jeśli w jednym „słońcu” przyjdzie po jednego awatara 100 tysięcy ludzi, to czasami może ono przetoczyć się za chmurę.

Aby wytrzymać serię żądań tego samego pliku, dla określonego typu treści włączamy głupi schemat, który rozprowadza pliki po wszystkich dostępnych „słońcach” w regionie.

Słońce od środka

Odwrotne proxy na Nginx, pamięć podręczna w pamięci RAM lub na szybkich dyskach Optane/NVMe. Przykład: http://sun4-2.userapi.com/c100500/path — łącze do „słońca”, które znajduje się w czwartym regionie, drugiej grupie serwerów. Zamyka plik ścieżki, który fizycznie znajduje się na serwerze 100500.

Cache

Do naszego schematu architektonicznego dodajemy jeszcze jeden węzeł – środowisko buforujące.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Poniżej znajduje się schemat układu regionalne skrytki, jest ich około 20. Są to miejsca, w których znajdują się skrzynki i „słońca”, które same mogą buforować ruch.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Jest to buforowanie treści multimedialnych; nie są tu przechowywane żadne dane użytkownika – tylko muzyka, wideo i zdjęcia.

Aby określić region użytkownika, my zbieramy prefiksy sieci BGP ogłaszane w regionach. W przypadku powrotu musimy również przeanalizować bazę danych geoip, jeśli nie możemy znaleźć adresu IP według prefiksów. Region określamy na podstawie adresu IP użytkownika. W kodzie możemy przyjrzeć się jednemu lub większej liczbie regionów użytkownika – czyli punktów, do których jest on najbliżej geograficznie.

Jak to działa?

Liczymy popularność plików według regionu. Istnieje numer regionalnej pamięci podręcznej, w której znajduje się użytkownik, oraz identyfikator pliku - bierzemy tę parę i zwiększamy ocenę przy każdym pobraniu.

Jednocześnie demony - usługi w regionach - od czasu do czasu przychodzą do API i mówią: „Jestem taką a taką skrytką, dajcie mi listę najpopularniejszych plików w moim regionie, których jeszcze u mnie nie ma. ” API dostarcza kilka plików posortowanych według ocen, demon je pobiera, przenosi do regionów i stamtąd dostarcza pliki. Na tym polega podstawowa różnica między pu/pp i Sun z pamięci podręcznych: natychmiast przekazują plik przez siebie, nawet jeśli tego pliku nie ma w pamięci podręcznej, a pamięć podręczna najpierw pobiera plik do siebie, a następnie zaczyna go oddawać.

W tym przypadku otrzymujemy treści bliżej użytkowników i rozkładanie obciążenia sieci. Przykładowo, tylko z pamięci podręcznej Moskwy dystrybuujemy w godzinach szczytu ponad 1 Tbit/s.

Ale są problemy - serwery pamięci podręcznej nie są gumowe. W przypadku bardzo popularnych treści czasami nie ma wystarczającej ilości sieci na oddzielny serwer. Nasze serwery pamięci podręcznej mają przepustowość 40-50 Gbit/s, ale istnieją treści, które całkowicie zatykają taki kanał. Zmierzamy w stronę wdrożenia przechowywania więcej niż jednej kopii popularnych plików w regionie. Mam nadzieję, że uda nam się go wdrożyć do końca roku.

Przyjrzeliśmy się ogólnej architekturze.

  • Serwery frontowe, które akceptują żądania.
  • Backendy przetwarzające żądania.
  • Magazyny zamykane przez dwa typy serwerów proxy.
  • Skrytki regionalne.

Czego brakuje na tym schemacie? Oczywiście bazy danych, w których przechowujemy dane.

Bazy danych lub silniki

Nazywamy je nie bazami danych, ale silnikami - Silnikami, ponieważ praktycznie nie mamy baz danych w ogólnie przyjętym znaczeniu.

Często zadawane pytania dotyczące architektury i pracy VKontakte

To jest konieczny środek.. Stało się tak, ponieważ w latach 2008-2009, kiedy popularność VK gwałtownie rosła, projekt działał wyłącznie na MySQL i Memcache i pojawiały się problemy. MySQL uwielbiał zawieszać się i uszkadzać pliki, po czym nie mógł się odzyskać, a wydajność Memcache stopniowo spadała i trzeba było go uruchamiać ponownie.

Okazuje się, że coraz popularniejszy projekt posiadał pamięć trwałą, która uszkadza dane, oraz pamięć podręczną, która spowalnia. W takich warunkach trudno rozwijać rozwijający się projekt. Postanowiono spróbować przepisać najważniejsze rzeczy, na których skupiał się projekt, w odniesieniu do naszych własnych rowerów.

Rozwiązanie okazało się skuteczne. Była ku temu okazja, ale i skrajna konieczność, bo innych sposobów skalowania wówczas nie było. Nie było zbyt wielu baz danych, NoSQL jeszcze nie istniał, był tylko MySQL, Memcache, PostrgreSQL – i tyle.

Uniwersalne działanie. Rozwój był prowadzony przez nasz zespół programistów C i wszystko zostało wykonane w spójny sposób. Niezależnie od silnika, wszystkie miały w przybliżeniu ten sam format pliku zapisywane na dysku, te same parametry uruchamiania, przetwarzały sygnały w ten sam sposób i zachowywały się mniej więcej tak samo w przypadku sytuacji brzegowych i problemów. Wraz z rozwojem silników obsługa systemu staje się wygodna dla administratorów – nie ma zoo, które trzeba utrzymywać, a oni muszą na nowo uczyć się obsługi każdej nowej, obcej bazy danych, co umożliwiło szybkie i wygodnie zwiększyć ich liczbę.

Rodzaje silników

Zespół napisał sporo silników. Oto tylko niektóre z nich: przyjaciel, wskazówki, obraz, ipdb, listy, listy, logi, memcached, meowdb, aktualności, nostradamus, zdjęcia, listy odtwarzania, pmemcached, sandbox, wyszukiwanie, przechowywanie, polubienia, zadania,…

Dla każdego zadania, które wymaga określonej struktury danych lub przetwarza nietypowe żądania, zespół C pisze nowy silnik. Dlaczego nie.

Mamy osobny silnik memcached, który jest podobny do zwykłego, ale z mnóstwem gadżetów i który nie zwalnia. Nie ClickHouse, ale też działa. Dostępne osobno pmemcached - jest trwałe memcached, który może również przechowywać dane na dysku, ponadto mieści się w pamięci RAM, aby nie utracić danych przy ponownym uruchomieniu. Istnieją różne silniki do poszczególnych zadań: kolejki, listy, zestawy - wszystko, czego wymaga nasz projekt.

Klastry

Z perspektywy kodu nie ma potrzeby myśleć o silnikach czy bazach danych jak o procesach, jednostkach czy instancjach. Kod działa specjalnie z klastrami, z grupami silników - jeden typ na klaster. Załóżmy, że istnieje klaster z pamięcią podręczną memcached — to tylko grupa komputerów.

Kod w ogóle nie musi znać fizycznej lokalizacji, rozmiaru ani liczby serwerów. Do klastra trafia posługując się określonym identyfikatorem.

Aby to zadziałało, musisz dodać jeszcze jedną encję, która znajduje się pomiędzy kodem a silnikami - pełnomocnik.

Serwer proxy RPC

Pełnomocnik autobus łączący, na którym działa prawie cała witryna. Jednocześnie mamy brak wykrywania usług — zamiast tego istnieje konfiguracja tego proxy, która zna lokalizację wszystkich klastrów i wszystkich fragmentów tego klastra. To właśnie robią administratorzy.

Programistom jest zupełnie obojętne, ile, gdzie i ile to kosztuje – po prostu idą do klastra. To nam na wiele pozwala. Otrzymując żądanie, proxy przekierowuje żądanie, wiedząc gdzie – sam to określa.

Często zadawane pytania dotyczące architektury i pracy VKontakte

W tym przypadku proxy jest punktem zabezpieczającym przed awarią usługi. Jeśli jakiś silnik zwalnia lub ulega awarii, serwer proxy to rozumie i odpowiednio reaguje po stronie klienta. Pozwala to na usunięcie timeoutu - kod nie czeka na reakcję silnika, ale rozumie, że nie działa i musi się jakoś inaczej zachować. Kod musi być przygotowany na to, że bazy danych nie zawsze działają.

Konkretne wdrożenia

Czasami nadal bardzo chcemy mieć jakieś niestandardowe rozwiązanie jako silnik. Jednocześnie zdecydowano się nie używać naszego gotowego proxy rpc, stworzonego specjalnie dla naszych silników, ale stworzyć osobne proxy dla tego zadania.

W przypadku MySQL, który wciąż tu i ówdzie mamy, używamy db-proxy, a dla ClickHouse - Kociak.

Działa to ogólnie w ten sposób. Jest pewien serwer, na którym działa kPHP, Go, Python - ogólnie każdy kod, który może korzystać z naszego protokołu RPC. Kod działa lokalnie na serwerze proxy RPC — każdy serwer, na którym znajduje się kod, uruchamia własny lokalny serwer proxy. Na żądanie pełnomocnik rozumie, dokąd się udać.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Jeśli jeden silnik chce przejść do innego, nawet jeśli jest to sąsiad, przechodzi przez serwer proxy, ponieważ sąsiad może znajdować się w innym centrum danych. Silnik nie powinien polegać na znajomości położenia czegokolwiek innego niż on sam – to nasze standardowe rozwiązanie. Ale oczywiście są wyjątki :)

Przykład schematu TL, zgodnie z którym działają wszystkie silniki.

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;

Jest to protokół binarny, którego najbliższym analogiem jest protobuf. Schemat predefiniuje pola opcjonalne, typy złożone – rozszerzenia wbudowanych skalarów i zapytania. Wszystko działa zgodnie z tym protokołem.

RPC przez TL przez TCP/UDP… UDP?

Mamy protokół RPC do wykonywania żądań silnika, który działa na podstawie schematu TL. Wszystko to działa poprzez połączenie TCP/UDP. TCP jest zrozumiałe, ale dlaczego często potrzebujemy UDP?

UDP pomaga unikaj problemu ogromnej liczby połączeń pomiędzy serwerami. Jeśli każdy serwer ma serwer proxy RPC i ogólnie może przejść do dowolnego silnika, wówczas na serwer przypada dziesiątki tysięcy połączeń TCP. Jest ładunek, ale jest bezużyteczny. W przypadku UDP problem ten nie występuje.

Brak zbędnego uzgadniania protokołu TCP. Jest to typowy problem: kiedy uruchamiany jest nowy silnik lub nowy serwer, jednocześnie nawiązywanych jest wiele połączeń TCP. W przypadku małych, lekkich żądań, na przykład ładunku UDP, cała komunikacja między kodem a silnikiem odbywa się dwa pakiety UDP: jeden leci w jedną stronę, drugi w drugą. Jedna podróż w obie strony - i kod otrzymał odpowiedź od silnika bez uścisku dłoni.

Tak, to wszystko po prostu działa z bardzo małym procentem utraty pakietów. Protokół obsługuje retransmisje i timeouty, ale jeśli stracimy dużo, to otrzymamy prawie TCP, co jest nieopłacalne. Nie przewozimy UDP przez oceany.

Mamy tysiące takich serwerów, a schemat jest ten sam: na każdym serwerze fizycznym instalowany jest pakiet silników. Są one przeważnie jednowątkowe, aby działać tak szybko, jak to możliwe bez blokowania, i są podzielone na fragmenty jako rozwiązania jednowątkowe. Jednocześnie nie mamy nic bardziej niezawodnego niż te silniki, a wiele uwagi poświęca się trwałemu przechowywaniu danych.

Trwałe przechowywanie danych

Silniki zapisują binlogi. Binlog to plik, na końcu którego dodawane jest zdarzenie zmiany stanu lub danych. W różnych rozwiązaniach nazywa się to inaczej: log binarny, WAL, AOF, ale zasada jest ta sama.

Aby zapobiec ponownemu odczytywaniu przez silnik całego binlogu przez wiele lat podczas ponownego uruchamiania, silniki zapisują migawki - stan aktualny. Jeśli to konieczne, najpierw czytają z niego, a następnie kończą czytanie z binlogu. Wszystkie binlogi zapisywane są w tym samym formacie binarnym - zgodnie ze schematem TL, dzięki czemu administratorzy mogą nimi na równi zarządzać swoimi narzędziami. Nie ma takiej potrzeby robienia migawek. Istnieje ogólny nagłówek, który wskazuje, czyja migawka to int, magia silnika i która treść nie jest dla nikogo ważna. Jest to problem z silnikiem, który zarejestrował migawkę.

Zaraz opiszę zasadę działania. Istnieje serwer, na którym działa silnik. Otwiera nowy, pusty binlog do zapisu i zapisuje wydarzenie w celu wprowadzenia w nim zmian.

Często zadawane pytania dotyczące architektury i pracy VKontakte

W pewnym momencie albo sam zdecyduje się zrobić migawkę, albo otrzyma sygnał. Serwer tworzy nowy plik, zapisuje do niego cały swój stan, dołącza bieżący rozmiar binloga – offset – na końcu pliku i kontynuuje zapisywanie. Nowy binlog nie jest tworzony.

Często zadawane pytania dotyczące architektury i pracy VKontakte

W pewnym momencie, po ponownym uruchomieniu silnika, na dysku pojawi się zarówno dziennik binarny, jak i migawka. Silnik odczytuje całą migawkę i podnosi jej stan w pewnym momencie.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Odczytuje pozycję w momencie utworzenia migawki i rozmiar dziennika binarnego.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Odczytuje koniec binlogu, aby uzyskać bieżący stan i kontynuuje zapisywanie dalszych zdarzeń. Jest to prosty schemat, zgodnie z nim działają wszystkie nasze silniki.

Replikacja danych

W rezultacie replikacja danych w naszym oparte na oświadczeniach — w binlogu nie zapisujemy żadnych zmian stron, ale mianowicie zmiany żądań. Bardzo podobny do tego, co przychodzi przez sieć, tylko nieznacznie zmodyfikowany.

Ten sam schemat służy nie tylko do replikacji, ale także do tworzenia kopii zapasowych. Mamy silnik - mistrza pisania, który zapisuje do binloga. W każdym innym miejscu, gdzie administratorzy to skonfigurowali, ten binlog jest kopiowany i to wszystko - mamy kopię zapasową.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Jeśli potrzebne czytająca replikaAby zmniejszyć obciążenie procesora podczas odczytu, po prostu uruchamia się silnik odczytu, który odczytuje koniec dziennika binarnego i wykonuje te polecenia lokalnie.

Opóźnienie tutaj jest bardzo małe i można dowiedzieć się, jak bardzo replika pozostaje w tyle za wzorcem.

Dzielenie danych w proxy RPC

Jak działa sharding? W jaki sposób serwer proxy rozumie, do którego fragmentu klastra należy wysłać? W kodzie nie jest napisane: „Wyślij po 15 odłamków!” - nie, robi to pełnomocnik.

Najprostszy schemat to Firstint — pierwsza cyfra we wniosku.

get(photo100_500) => 100 % N.

To jest przykład prostego protokołu tekstowego memcached, ale oczywiście zapytania mogą być złożone i ustrukturyzowane. Przykład uwzględnia pierwszą liczbę w zapytaniu i resztę podzieloną przez rozmiar klastra.

Jest to przydatne, gdy chcemy mieć lokalizację danych pojedynczej jednostki. Załóżmy, że 100 to identyfikator użytkownika lub grupy i chcemy, aby w przypadku złożonych zapytań wszystkie dane jednej jednostki znajdowały się w jednym fragmencie.

Jeśli nie obchodzi nas, w jaki sposób żądania są rozkładane w klastrze, istnieje inna opcja - hashowanie całego fragmentu.

hash(photo100_500) => 3539886280 % N

Otrzymujemy również skrót, resztę dzielenia i numer fragmentu.

Obie te opcje sprawdzają się tylko wtedy, gdy jesteśmy przygotowani na to, że zwiększając rozmiar klastra, będziemy go dzielić lub zwiększać wielokrotnie. Przykładowo mieliśmy 16 shardów, nie wystarczy nam, chcemy więcej – spokojnie możemy zdobyć 32 bez przestojów. Jeśli będziemy chcieli zwiększać, a nie wielokrotności, nastąpi przestój, ponieważ nie będziemy w stanie dokładnie rozdzielić wszystkiego bez strat. Opcje te są przydatne, ale nie zawsze.

Jeśli musimy dodać lub usunąć dowolną liczbę serwerów, używamy Konsekwentne mieszanie na ringu a la Ketama. Ale jednocześnie całkowicie tracimy lokalizację danych; musimy scalić żądanie z klastrem, tak aby każdy element zwrócił swoją własną małą odpowiedź, a następnie scalić odpowiedzi z serwerem proxy.

Istnieją bardzo szczegółowe żądania. Wygląda to tak: serwer proxy RPC odbiera żądanie, określa, do którego klastra się udać i określa fragment. Następnie istnieją albo wzorce zapisu, albo, jeśli klaster obsługuje replikę, wysyła do repliki na żądanie. Wszystko to robi proxy.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Dzienniki

Logi piszemy na kilka sposobów. Najbardziej oczywisty i prosty jest zapisz logi do memcache.

ring-buffer: prefix.idx = line

Jest kluczowy przedrostek - nazwa dziennika, linia i jest rozmiar tego dziennika - liczba wierszy. Bierzemy losową liczbę od 0 do liczby wierszy minus 1. Klucz w memcache to przedrostek połączony z tą losową liczbą. Zapisujemy linię logu i aktualny czas do wartości.

Gdy zaistnieje konieczność odczytania logów, wykonujemy je Wielu Get wszystkie klucze, posortowane według czasu, dzięki czemu uzyskasz dziennik produkcji w czasie rzeczywistym. Schemat jest używany, gdy trzeba debugować coś w środowisku produkcyjnym w czasie rzeczywistym, bez psucia czegokolwiek, bez zatrzymywania lub zezwalania na ruch do innych maszyn, ale ten dziennik nie trwa długo.

Do niezawodnego przechowywania kłód posiadamy silnik silnik logów. Właśnie po to powstał i jest szeroko stosowany w ogromnej liczbie klastrów. Największy klaster jaki znam przechowuje 600 TB spakowanych kłód.

Silnik jest bardzo stary, zdarzają się klastry mające już 6-7 lat. Są z tym problemy, które staramy się rozwiązać, np. zaczęliśmy aktywnie wykorzystywać ClickHouse do przechowywania logów.

Zbieranie logów w ClickHouse

Ten diagram pokazuje, jak wchodzimy do naszych silników.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Istnieje kod, który jest przesyłany lokalnie przez RPC do serwera proxy RPC i wie, gdzie przejść do silnika. Jeśli chcemy pisać logi w ClickHouse musimy zmienić dwie części w tym schemacie:

  • zastąpić jakiś silnik ClickHouse;
  • zastąp serwer proxy RPC, który nie może uzyskać dostępu do ClickHouse, rozwiązaniem, które to umożliwia, i za pośrednictwem RPC.

Silnik jest prosty – zastępujemy go serwerem lub klastrem serwerów za pomocą ClickHouse.

I pojechaliśmy do ClickHouse, tak zrobiliśmy Dom Kociaka. Jeśli przejdziemy bezpośrednio z KittenHouse do ClickHouse, to nie poradzi sobie. Nawet bez żądań sumuje się z połączeń HTTP ogromnej liczby komputerów. Aby schemat działał, na serwerze z ClickHouse Zostaje wywołane lokalne odwrotne proxy, który jest napisany w taki sposób, aby wytrzymać wymagane ilości połączeń. Może również stosunkowo niezawodnie buforować dane w sobie.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Czasami nie chcemy wdrażać schematu RPC w niestandardowych rozwiązaniach, np. w nginx. Dlatego KittenHouse ma możliwość odbierania logów poprzez UDP.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Jeśli nadawca i odbiorca dzienników pracują na tym samym komputerze, prawdopodobieństwo utraty pakietu UDP na hoście lokalnym jest dość niskie. Jako kompromis pomiędzy koniecznością implementacji RPC w rozwiązaniu firm trzecich a niezawodnością, stosujemy po prostu wysyłkę UDP. Do tego schematu wrócimy później.

Monitorowanie

Mamy dwa rodzaje logów: te gromadzone przez administratorów na ich serwerach oraz te pisane przez programistów z kodu. Odpowiadają one dwóm typom metryk: systemu i produktu.

Metryki systemu

Działa na wszystkich naszych serwerach Dane netto, która zbiera statystyki i przesyła je do Grafit Węgiel. Dlatego też ClickHouse służy jako system przechowywania, a nie na przykład Whisper. W razie potrzeby możesz przeczytać bezpośrednio z ClickHouse lub użyć grafana dla metryk, wykresów i raportów. Jako programiści mamy wystarczający dostęp do Netdata i Grafany.

Metryki produktu

Dla wygody napisaliśmy wiele rzeczy. Na przykład istnieje zestaw zwykłych funkcji, które pozwalają zapisywać wartości Counts, UniqueCounts w statystykach, które są wysyłane gdzieś dalej.

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

$stats = statlogsStatData($params)

Następnie możemy użyć filtrów sortujących i grupujących oraz zrobić wszystko, co chcemy ze statystykami - budować wykresy, konfigurować Watchdogi.

Piszemy bardzo wiele metryk liczba zdarzeń wynosi od 600 miliardów do 1 biliona dziennie. Chcemy je jednak zachować przynajmniej kilka lataby zrozumieć trendy w metrykach. Poskładanie tego wszystkiego w całość to duży problem, którego jeszcze nie rozwiązaliśmy. Opowiem jak to wyglądało przez ostatnie kilka lat.

Mamy funkcje, które zapisują te metryki do lokalnego memcacheaby zmniejszyć liczbę wpisów. Raz na jakiś czas uruchamiany lokalnie demon statystyk zbiera wszystkie zapisy. Następnie demon łączy metryki w dwie warstwy serwerów zbieracze kłód, który agreguje statystyki z kilku naszych maszyn, aby warstwa za nimi nie umarła.

Często zadawane pytania dotyczące architektury i pracy VKontakte

W razie potrzeby możemy napisać bezpośrednio do logów-kolekcjonerów.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Ale pisanie kodu bezpośrednio do kolektorów, z pominięciem stas-daemom, jest słabo skalowalnym rozwiązaniem, ponieważ zwiększa obciążenie kolektora. Rozwiązanie jest odpowiednie tylko wtedy, gdy z jakiegoś powodu nie możemy podnieść demona statystyk memcache na komputerze lub uległ on awarii i poszliśmy bezpośrednio.

Następnie moduły zbierające logi łączą statystyki w miauDB - to jest nasza baza danych, w której można również przechowywać metryki.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Następnie możemy dokonać binarnych wyborów „prawie SQL” z kodu.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Eksperyment

Latem 2018 zorganizowaliśmy wewnętrzny hackaton i zrodził się pomysł, aby spróbować zastąpić czerwoną część diagramu czymś, co mogłoby przechowywać metryki w ClickHouse. Mamy logi w ClickHouse - dlaczego by nie spróbować?

Często zadawane pytania dotyczące architektury i pracy VKontakte

Mieliśmy schemat, który zapisywał logi przez KittenHouse.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Zdecydowaliśmy dodaj kolejny „*Dom” do diagramu, który otrzyma dokładnie metryki w formacie, w jakim nasz kod zapisuje je poprzez UDP. Następnie ten *House zamienia je we wkładki, takie jak kłody, co KittenHouse rozumie. Potrafi doskonale dostarczyć te logi do ClickHouse, który powinien móc je odczytać.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Schemat z bazą danych memcache, stats-daemon i logs-collectors został zastąpiony tym.

Często zadawane pytania dotyczące architektury i pracy VKontakte

Schemat z bazą danych memcache, stats-daemon i logs-collectors został zastąpiony tym.

  • Tutaj znajduje się komunikat z kodu, który jest napisany lokalnie w StatsHouse.
  • StatsHouse zapisuje metryki UDP, już przekonwertowane na wstawki SQL, do KittenHouse partiami.
  • KittenHouse wysyła je do ClickHouse.
  • Jeśli chcemy je przeczytać, to czytamy je z pominięciem StatsHouse – bezpośrednio z ClickHouse przy użyciu zwykłego SQL.

Czy to nadal? eksperyment, ale podoba nam się, jak to się okazuje. Jeśli naprawimy problemy ze schematem, być może przejdziemy na niego całkowicie. Osobiście mam taką nadzieję.

Schemat nie oszczędza żelaza. Potrzebnych jest mniej serwerów, lokalne demony statystyk i moduły zbierające logi nie są potrzebne, ale ClickHouse wymaga większego serwera niż te w bieżącym schemacie. Potrzebnych jest mniej serwerów, ale muszą być droższe i wydajniejsze.

Wdrożyć

Najpierw przyjrzyjmy się wdrożeniu PHP. Rozwijamy się w odrzutowiec: używać GitLab и TeamCity do wdrożenia. Gałęzie rozwojowe są łączone w gałąź główną, z gałęzi głównej do testowania są łączone w staging, a z inscenizacji w produkcję.

Przed wdrożeniem pobierana jest bieżąca gałąź produkcyjna oraz poprzednia i uwzględniane są w nich pliki różnicowe - zmiany: utworzone, usunięte, zmienione. Zmiana ta jest rejestrowana w binlogu specjalnego mechanizmu copyfast, który może szybko replikować zmiany w całej naszej flocie serwerów. Tutaj nie używa się bezpośredniego kopiowania, ale powielanie plotek, gdy jeden serwer wysyła zmiany do swoich najbliższych sąsiadów, zmiany do sąsiadów itd. Pozwala to na aktualizację kodu w dziesiątkach i jednostkach sekund w całej flocie. Kiedy zmiana dotrze do repliki lokalnej, zastosuje do niej te poprawki lokalny system plików. Wycofywanie odbywa się również według tego samego schematu.

Często wdrażamy kPHP i ma on również swój własny rozwój odrzutowiec zgodnie z powyższym schematem. Od tego Plik binarny serwera HTTP, wtedy nie możemy wyprodukować pliku różnicowego - plik binarny wydania waży setki MB. Dlatego istnieje tutaj inna opcja - wersja jest zapisana kopiowanie binloga. Z każdą kompilacją wzrasta, a podczas wycofywania również wzrasta. Wersja replikowane na serwery. Lokalni copyfastowcy widzą, że nowa wersja weszła do binloga i poprzez tę samą plotkową replikację biorą dla siebie najnowszą wersję binarną, nie męcząc naszego głównego serwera, ale ostrożnie rozkładając obciążenie w całej sieci. Co za tym idzie pełne wdzięku wznowienie dla nowej wersji.

W przypadku naszych silników, które również są zasadniczo plikami binarnymi, schemat jest bardzo podobny:

  • git gałąź główna;
  • binarny w deb;
  • wersja jest zapisywana w binlog copyfast;
  • replikowane na serwery;
  • serwer pobiera świeży plik .dep;
  • dpkg -tj;
  • pełne wdzięku wznowienie do nowej wersji.

Różnica polega na tym, że nasz plik binarny jest spakowany w archiwach debi podczas wypompowywania ich dpkg -tj są umieszczone w systemie. Dlaczego kPHP jest wdrażane jako plik binarny, a silniki są wdrażane jako dpkg? Stało się to w ten sposób. Działa – nie dotykaj tego.

Przydatne linki:

Alexey Akulovich jest jednym z tych, którzy w ramach Komitetu Programowego pomagają PHP Rosja 17 maja stanie się największym wydarzeniem dla programistów PHP w ostatnim czasie. Zobacz, jaki mamy fajny komputer, co głośniki (dwóch z nich pracuje nad rdzeniem PHP!) - wydaje się, że jest to coś, czego nie możesz przegapić, pisząc PHP.

Źródło: www.habr.com

Dodaj komentarz