Bioyino - rozproszony, skalowalny agregator metryk

Zbierasz więc metryki. Tak jak my. Zbieramy także metryki. Oczywiście niezbędne w interesach. Dzisiaj porozmawiamy o pierwszym ogniwie naszego systemu monitorowania - serwerze agregującym kompatybilnym ze statystyką bioyino, dlaczego to napisaliśmy i dlaczego porzuciliśmy Brubeck.

Bioyino - rozproszony, skalowalny agregator metryk

Z naszych poprzednich artykułów (1, 2) można się dowiedzieć, że do pewnego czasu zbieraliśmy oceny za pomocą Brubecka. Jest napisany w języku C. Z punktu widzenia kodu jest tak prosty jak wtyczka (jest to ważne, gdy chcesz wnieść swój wkład) i, co najważniejsze, obsługuje wolumeny wynoszące 2 miliony metrów na sekundę (MPS) w szczycie bez żadnych problemów. Dokumentacja wskazuje obsługę 4 milionów MPS z gwiazdką. Oznacza to, że uzyskasz podaną liczbę, jeśli poprawnie skonfigurujesz sieć w systemie Linux. (Nie wiemy, ile MPS możesz uzyskać, jeśli opuścisz sieć bez zmian). Pomimo tych zalet mieliśmy kilka poważnych skarg na brubeck.

Zastrzeżenie 1. Github, twórca projektu, przestał go wspierać: publikować łatki i poprawki, akceptować nasz i (nie tylko nasz) PR. W ciągu ostatnich kilku miesięcy (gdzieś od lutego-marca 2018 r.) działalność została wznowiona, ale wcześniej panował prawie 2 lata całkowitego spokoju. Ponadto projekt jest w trakcie opracowywania na wewnętrzne potrzeby Gihuba, co może stać się poważną przeszkodą we wprowadzaniu nowych funkcji.

Zastrzeżenie 2. Dokładność obliczeń. Brubeck zbiera w sumie 65536 wartości do agregacji. W naszym przypadku dla niektórych metryk w okresie agregacji (30 sekund) może pojawić się znacznie więcej wartości (w szczycie 1 527 392). W wyniku tego próbkowania wartości maksymalne i minimalne wydają się bezużyteczne. Na przykład tak:

Bioyino - rozproszony, skalowalny agregator metryk
Tak jak było

Bioyino - rozproszony, skalowalny agregator metryk
Jak to powinno wyglądać

Z tego samego powodu kwoty są na ogół obliczane nieprawidłowo. Dodaj tutaj błąd z 32-bitowym przepełnieniem float, który zazwyczaj wysyła serwer do segfault po otrzymaniu pozornie niewinnej metryki i wszystko staje się świetne. Swoją drogą błąd nie został naprawiony.

Oraz, w końcu, Roszczenie X. W chwili pisania tego tekstu jesteśmy gotowi zaprezentować go wszystkim 14 mniej więcej działającym wdrożeniom statystyk, jakie udało nam się znaleźć. Wyobraźmy sobie, że jakaś pojedyncza infrastruktura rozrosła się tak bardzo, że akceptacja 4 milionów MPS już nie wystarczy. Albo nawet jeśli jeszcze nie urósł, ale wskaźniki są już dla Ciebie na tyle ważne, że nawet krótkie, 2-3-minutowe spadki na wykresach mogą już stać się krytyczne i spowodować napady nieprzezwyciężonej depresji wśród menedżerów. Ponieważ leczenie depresji jest niewdzięcznym zadaniem, potrzebne są rozwiązania techniczne.

Po pierwsze, odporność na awarie, aby nagły problem na serwerze nie spowodował psychiatrycznej apokalipsy zombie w biurze. Po drugie skalowanie, aby móc przyjąć ponad 4 miliony MPS, bez wdzierania się głęboko w stos sieciowy Linuksa i spokojnego powiększania „wszerz” do wymaganego rozmiaru.

Ponieważ mieliśmy miejsce na skalowanie, postanowiliśmy zacząć od odporności na błędy. "O! Tolerancja na błędy! To proste, damy radę” – pomyśleliśmy i uruchomiliśmy 2 serwery, na każdym umieszczając kopię brubecka. Aby to zrobić, musieliśmy skopiować ruch z metrykami na oba serwery, a nawet napisać do tego mała użyteczność. Rozwiązaliśmy w ten sposób problem tolerancji błędów, ale... niezbyt dobrze. Na początku wszystko wydawało się świetne: każdy brubeck zbiera własną wersję agregacji, zapisuje dane do Graphite raz na 30 sekund, nadpisując stary interwał (odbywa się to po stronie Graphite). Jeśli jeden serwer nagle ulegnie awarii, zawsze mamy drugi z własną kopią zagregowanych danych. Ale tutaj jest problem: jeśli serwer ulegnie awarii, na wykresach pojawia się „piła”. Dzieje się tak dlatego, że 30-sekundowe interwały Brubecka nie są zsynchronizowane i w momencie awarii jeden z nich nie zostaje nadpisany. Po uruchomieniu drugiego serwera dzieje się to samo. Całkiem znośne, ale chcę lepiej! Nie zniknął także problem skalowalności. Wszystkie metryki w dalszym ciągu „lecą” na jeden serwer, dlatego jesteśmy ograniczeni do tych samych 2-4 milionów MPS, w zależności od poziomu sieci.

Jeśli pomyślisz trochę o problemie i jednocześnie odkopiesz śnieg łopatą, może przyjść Ci do głowy następujący oczywisty pomysł: potrzebujesz statystyki, która może działać w trybie rozproszonym. Oznacza to, że implementuje synchronizację między węzłami pod względem czasu i metryk. „Oczywiście, takie rozwiązanie prawdopodobnie już istnieje” – powiedzieliśmy i udaliśmy się do Google…. I nic nie znaleźli. Po przejrzeniu dokumentacji dla różnych statystyk (https://github.com/etsy/statsd/wiki#server-implementations na dzień 11.12.2017 grudnia XNUMX r.) nie znaleźliśmy absolutnie nic. Najwyraźniej ani twórcy, ani użytkownicy tych rozwiązań nie spotkali się jeszcze z TAK wieloma miernikami, w przeciwnym razie na pewno by coś wymyślili.

I wtedy przypomnieliśmy sobie o „zabawkowych” statystykach – bioyino, które powstały podczas hackatonu Just for Fun (nazwa projektu została wygenerowana przez skrypt przed rozpoczęciem hackatonu) i zdaliśmy sobie sprawę, że pilnie potrzebujemy własnych statystyk. Po co?

  • ponieważ na świecie jest zbyt mało klonów statystyk,
  • ponieważ możliwe jest zapewnienie pożądanej lub zbliżonej do pożądanej odporności na awarie i skalowalności (w tym synchronizacja zagregowanych metryk pomiędzy serwerami i rozwiązywanie problemu konfliktów przesyłania),
  • ponieważ możliwe jest obliczanie wskaźników dokładniej niż robi to Brubeck,
  • ponieważ możesz samodzielnie zbierać bardziej szczegółowe statystyki, których Brubeck praktycznie nam nie udostępniał,
  • ponieważ miałem okazję zaprogramować własną hiperwydajną aplikację w skali rozproszonej, która nie będzie całkowicie powtarzać architektury innego podobnego hiperbo... cóż, to wszystko.

Na czym pisać? Oczywiście w Rust. Dlaczego?

  • ponieważ istniało już rozwiązanie prototypowe,
  • ponieważ autor artykułu znał już wtedy Rusta i bardzo chciał napisać w nim coś na produkcję z możliwością umieszczenia go w open-source,
  • ponieważ języki z GC nie są dla nas odpowiednie ze względu na charakter odbieranego ruchu (prawie w czasie rzeczywistym), a przerwy w GC są praktycznie nie do przyjęcia,
  • ponieważ potrzebujesz maksymalnej wydajności porównywalnej z C
  • ponieważ Rust zapewnia nam nieustraszoną współbieżność i gdybyśmy zaczęli pisać go w C/C++, zgarnęlibyśmy jeszcze więcej luk w zabezpieczeniach, przepełnienia bufora, warunki wyścigowe i inne przerażające słowa niż brubeck.

Był też argument przeciwko Rustowi. Firma nie miała doświadczenia w tworzeniu projektów w Rust, a teraz też nie planujemy używać go w głównym projekcie. Dlatego były poważne obawy, że nic nie wyjdzie, ale postanowiliśmy zaryzykować i spróbować.

Czas minął...

Wreszcie, po kilku nieudanych próbach, pierwsza działająca wersja była gotowa. Co się stało? To jest to, co się stało.

Bioyino - rozproszony, skalowalny agregator metryk

Każdy węzeł otrzymuje swój własny zestaw metryk i gromadzi je, ale nie agreguje metryk dla tych typów, w przypadku których do ostatecznej agregacji wymagany jest ich pełny zestaw. Węzły są połączone ze sobą za pomocą pewnego rodzaju protokołu blokady rozproszonej, który pozwala wybrać spośród nich jedyny (tutaj płakaliśmy), który jest godny przesłania metryk do Wielkiego. Problem ten jest obecnie rozwiązywany przez Konsul, ale w przyszłości ambicje autora sięgają własny realizacja Tratwa, gdzie najbardziej godny będzie oczywiście węzeł lidera konsensusu. Oprócz konsensusu węzły dość często (domyślnie raz na sekundę) wysyłają do swoich sąsiadów te części wstępnie zagregowanych metryk, które udało im się zebrać w ciągu tej sekundy. Okazuje się, że zachowane zostało skalowanie i tolerancja błędów - każdy węzeł nadal przechowuje pełny zestaw metryk, ale metryki są wysyłane już zagregowane, przez TCP i zakodowane w protokole binarnym, dzięki czemu koszty duplikacji są znacznie obniżone w porównaniu z UDP. Pomimo dość dużej liczby przychodzących metryk, akumulacja wymaga bardzo mało pamięci i jeszcze mniej procesora. W przypadku naszych wysoce ściśliwych danych jest to zaledwie kilkadziesiąt megabajtów danych. Jako dodatkowy bonus nie otrzymujemy niepotrzebnego przepisywania danych w Graphite, jak miało to miejsce w przypadku Burbecka.

Pakiety UDP z metrykami są niezrównoważone pomiędzy węzłami sprzętu sieciowego poprzez proste działanie okrężne. Oczywiście sprzęt sieciowy nie analizuje zawartości pakietów i dlatego może pobrać znacznie więcej niż 4M pakietów na sekundę, nie mówiąc już o wskaźnikach, o których w ogóle nic nie wie. Jeśli weźmiemy pod uwagę, że metryki nie przychodzą pojedynczo w każdym pakiecie, to nie przewidujemy w tym miejscu żadnych problemów z wydajnością. W przypadku awarii serwera urządzenie sieciowe szybko (w ciągu 1-2 sekund) wykrywa ten fakt i usuwa uszkodzony serwer z rotacji. Dzięki temu węzły pasywne (czyli nie wiodące) można włączać i wyłączać praktycznie bez zauważania spadków na wykresach. Maksymalna strata jest częścią wskaźników, które pojawiły się w ostatniej sekundzie. Nagła utrata/wyłączenie/zmiana lidera nadal spowoduje niewielką anomalię (30-sekundowy interwał nadal nie jest zsynchronizowany), ale jeśli między węzłami istnieje komunikacja, problemy te można zminimalizować, na przykład wysyłając pakiety synchronizacyjne .

Trochę o strukturze wewnętrznej. Aplikacja jest oczywiście wielowątkowa, jednak architektura wątków różni się od tej stosowanej w brubecku. Wątki w brubeck są takie same - każdy z nich odpowiada zarówno za zbieranie, jak i agregację informacji. W bioyino pracownicy dzielą się na dwie grupy: osoby odpowiedzialne za sieć i osoby odpowiedzialne za agregację. Podział ten pozwala na bardziej elastyczne zarządzanie aplikacją w zależności od rodzaju metryk: tam, gdzie wymagana jest intensywna agregacja, można dodać agregatory, tam, gdzie jest duży ruch sieciowy, można dodać liczbę przepływów sieciowych. W tej chwili na naszych serwerach pracujemy w 8 przepływach sieciowych i 4 agregacyjnych.

Część licząca (odpowiedzialna za agregację) jest dość nudna. Bufory wypełnione przepływami sieciowymi rozdzielane są pomiędzy przepływy zliczające, gdzie są następnie analizowane i agregowane. Na żądanie podawane są metryki do przesłania do innych węzłów. Wszystko to, łącznie z przesyłaniem danych pomiędzy węzłami i pracą z Consulem, odbywa się asynchronicznie, działając na frameworku Tokio.

Znacznie więcej problemów w fazie rozwoju sprawiała część sieciowa odpowiedzialna za odbieranie metryk. Głównym celem rozdzielenia przepływów sieciowych na odrębne podmioty była chęć skrócenia czasu spędzanego przez przepływ nie do odczytania danych z gniazda. Opcje wykorzystujące asynchroniczny UDP i zwykły recvmsg szybko zniknęły: pierwsza zużywa zbyt dużo procesora przestrzeni użytkownika do przetwarzania zdarzeń, druga wymaga zbyt wielu przełączeń kontekstu. Dlatego jest teraz używany recvmmsg z dużymi buforami (a zderzaki, panowie oficerowie, dla Was to nic!). Obsługa zwykłego UDP jest zarezerwowana dla lekkich przypadków, w których recvmmsg nie jest potrzebny. W trybie multimessage można osiągnąć najważniejsze: w zdecydowanej większości przypadków wątek sieciowy przeczesuje kolejkę systemu operacyjnego - odczytuje dane z gniazda i przesyła je do bufora przestrzeni użytkownika, tylko sporadycznie przełączając się na oddanie wypełnionego bufora do agregatory. Kolejka w gnieździe praktycznie się nie kumuluje, liczba odrzuconych pakietów praktycznie nie rośnie.

Operacja

W ustawieniach domyślnych rozmiar bufora jest ustawiony na dość duży. Jeśli nagle zdecydujesz się na samodzielne wypróbowanie serwera, możesz spotkać się z faktem, że po wysłaniu niewielkiej liczby metryk nie dotrą one w Graphite, pozostając w buforze strumienia sieciowego. Aby pracować z małą liczbą metryk, musisz ustawić bufsize i task-queue-size na mniejsze wartości w konfiguracji.

Na koniec kilka wykresów dla miłośników wykresów.

Statystyki dotyczące liczby przychodzących metryk dla każdego serwera: ponad 2 miliony MPS.

Bioyino - rozproszony, skalowalny agregator metryk

Wyłączenie jednego z węzłów i redystrybucja przychodzących metryk.

Bioyino - rozproszony, skalowalny agregator metryk

Statystyki dotyczące metryk wychodzących: zawsze wysyła tylko jeden węzeł – szef rajdu.

Bioyino - rozproszony, skalowalny agregator metryk

Statystyka pracy każdego węzła z uwzględnieniem błędów w różnych modułach systemu.

Bioyino - rozproszony, skalowalny agregator metryk

Szczegóły przychodzących metryk (nazwy metryk są ukryte).

Bioyino - rozproszony, skalowalny agregator metryk

Co mamy zamiar z tym wszystkim dalej zrobić? Oczywiście, napisz kod, cholera…! Pierwotnie projekt miał mieć charakter open source i tak pozostanie przez cały okres jego istnienia. Nasze najbliższe plany obejmują przejście na własną wersję Raft, zmianę protokołu równorzędnego na bardziej przenośny, wprowadzenie dodatkowych statystyk wewnętrznych, nowych typów metryk, poprawki błędów i inne ulepszenia.

Oczywiście każdy może pomóc w rozwoju projektu: stworzyć PR, Issues, w miarę możliwości odpowiemy, ulepszymy itp.

Mając to na uwadze, to wszystko, kupcie nasze słonie!



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

Dodaj komentarz