RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach

Odporność i wysoka dostępność to duże tematy, dlatego poświęcimy RabbitMQ i Kafka osobne artykuły. Ten artykuł dotyczy RabbitMQ, a następny będzie o Kafka w porównaniu do RabbitMQ. Artykuł jest długi, więc rozsiądź się wygodnie.

Zastanowimy się nad strategiami odporności, spójności oraz wysokiej dostępności (HA), a także kompromisami, które trzeba zaakceptować w każdej strategii. RabbitMQ może działać w klastrze węzłów, przez co klasyfikuje się jako system rozproszony. Gdy mówimy o systemach rozproszonych, często poruszamy tematy spójności i dostępności.

Te pojęcia opisują, jak system zachowuje się w przypadku awarii. Awaria połączenia sieciowego, awaria serwera, awaria dysku twardego, tymczasowa niedostępność serwera z powodu zbierania śmieci, utrata pakietów lub spowolnienie połączenia sieciowego. Wszystko to może prowadzić do utraty danych lub konfliktów. Okazuje się, że praktycznie niemożliwe jest utrzymanie systemu, który jednocześnie byłby całkowicie spójny (bez utraty danych, bez rozbieżności danych) oraz dostępny (akceptującego operacje odczytu i zapisu) we wszystkich scenariuszach awarii.

Zobaczymy, że spójność i dostępność znajdują się na przeciwnych końcach spektrum, a Ty musisz wybrać, w którą stronę optymalizować. Dobrą wiadomością jest to, że z RabbitMQ taki wybór jest możliwy. Masz „nerdowskie” dźwignie, aby przesunąć równowagę w stronę większej spójności lub większej dostępności.

Szczególną uwagę zwrócimy na to, jakie konfiguracje prowadzą do utraty danych z powodu potwierdzonych zapisów. Istnieje łańcuch odpowiedzialności między publikującymi, brokerami i konsumentami. Po tym jak wiadomość zostanie przekazana brokerowi, to jego obowiązek — nie zgubić tej wiadomości. Gdy broker potwierdza publikującemu otrzymanie wiadomości, nie oczekujemy, że zostanie ona utracona. Jednak zobaczymy, że może się to zdarzyć w zależności od konfiguracji Twojego brokera i wydawcy.

Primitives of node resilience

Odporną kolejki/routing

W RabbitMQ istnieją dwa typy kolejek: trwałe (durable) i nietrwałe (non-durable). Wszystkie kolejki są przechowywane w bazie danych Mnesia. Trwałe kolejki są ponownie deklarowane podczas uruchamiania węzła, co oznacza, że przetrwają ponowne uruchomienie, awarię systemu lub serwera (o ile dane są zachowane). Oznacza to, że dopóki deklarujesz routowanie (exchange) i kolejkę jako trwałe, infrastruktura kolejek/routingu powróci do trybu operacyjnego.

Nietrwałe kolejki i routowanie są usuwane podczas ponownego uruchomienia węzła.

Trwałe wiadomości

To, że kolejka jest trwała, nie oznacza, że wszystkie jej wiadomości przetrwają ponowne uruchomienie węzła. Zostaną odzyskane tylko wiadomości, które zostały oznaczone przez publikatora jako trwałe (persistent). Trwałe wiadomości rzeczywiście zwiększają obciążenie brokera, ale jeśli utrata wiadomości jest nieakceptowalna, to nie ma innej opcji.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 1. Macierz trwałości

Klasteryzacja z lustrzanym odbiciem kolejek

Aby przetrwać utratę brokera, potrzebujemy nadmiarowości. Możemy połączyć kilka węzłów RabbitMQ w klaster, a następnie dodać dodatkową nadmiarowość poprzez replikację kolejek pomiędzy wieloma węzłami. W ten sposób, jeśli jeden węzeł zawiedzie, nie tracimy danych i pozostajemy dostępni.

Lustrzane odbicie kolejki:

  • jedna główna kolejka (master), która otrzymuje wszystkie polecenia zapisu i odczytu
  • jedno lub więcej luster, które otrzymują wszystkie wiadomości i metadane z głównej kolejki. Te lustra istnieją nie do skalowania, ale wyłącznie dla nadmiarowości.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 2. Lustrzane odbicie kolejki

Lustrzane odbicie jest ustawiane odpowiednią polityką. Można w niej wybrać współczynnik replikacji oraz nawet węzły, na których powinna się znajdować kolejka. Przykłady:

  • ha-mode: all
  • ha-mode: exactly, ha-params: 2 (jeden master i jedno lustro)
  • ha-mode: nodes, ha-params: rabbit@node1, rabbit@node2

Potwierdzenie dla publikatora

Aby uzyskać spójną rejestrację, wymagane są potwierdzenia od wydawcy (Publisher Confirms). Bez nich istnieje ryzyko utraty wiadomości. Potwierdzenie jest wysyłane do wydawcy po zapisaniu wiadomości na dysku. RabbitMQ zapisuje wiadomości na dysku nie w momencie odbioru, ale w regularnych odstępach, co kilka setek milisekund. Gdy kolejka jest replikowana, potwierdzenie jest wysyłane dopiero po tym, jak wszystkie repliki również zapisały swoją kopię wiadomości na dysku. Oznacza to, że użycie potwierdzeń dodaje opóźnienie, ale jeśli bezpieczeństwo danych jest ważne, są one niezbędne.

Odporna kolejka

Gdy broker kończy pracę lub ulega awarii, wszystkie główne kolejki (master) na tym węźle przestają działać. Następnie klaster wybiera najstarszą replikę każdego mastera i promuje ją do nowego mastera.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 3. Kilka replikowanych kolejek i ich polityki

Broker 3 ulega awarii. Zauważ, że replikacja Kolejki C na Brokerze 2 jest promowana do mastera. Zauważ także, że dla Kolejki C utworzono nową replikę na Brokerze 1. RabbitMQ zawsze stara się utrzymać wskaźnik replikacji określony w twoich politykach.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 4. Broker 3 ulega awarii, co powoduje awarię kolejki C

Ulega awarii kolejny Broker 1! Został nam tylko jeden broker. Replika Kolejki B jest promowana do mastera.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 5

Przywróciliśmy Brokera 1. Niezależnie od tego, jak pomyślnie dane przetrwały utratę i przywrócenie brokera, wszystkie replikowane wiadomości kolejki są odrzucane podczas ponownego uruchamiania. Warto to zaznaczyć, gdyż będą tego konsekwencje. Wkrótce przyjrzymy się tym konsekwencjom. W związku z tym Broker 1 znów jest członkiem klastra, a klaster stara się przestrzegać polityk i dlatego tworzy replikę na Brokerze 1.

W tym przypadku utrata Brokera 1 była całkowita, jak i danych, dlatego niezreplikowana Kolejka B została całkowicie utracona.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 6. Broker 1 wraca do działania

Broker 3 wrócił do działania, więc kolejki A i B odzyskują utworzone na nim lustra, aby zaspokoić swoje polityki HA. Ale teraz wszystkie główne kolejki są na jednym węźle! To nie jest idealne, lepiej byłoby równomierne rozłożenie między węzłami. Niestety, nie ma tutaj szczególnych opcji do przeważenia masterów. Wrócimy do tego problemu później, ponieważ najpierw musimy rozważyć synchronizację kolejki.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 7. Broker 3 wraca do działania. Wszystkie główne kolejki na jednym węźle!

W ten sposób powinieneś już mieć wyobrażenie, jak lustra zapewniają nadmiarowość i odporność na awarie. Gwarantuje to dostępność w przypadku awarii jednego węzła i chroni przed utratą danych. Ale nie skończyliśmy jeszcze, ponieważ w rzeczywistości wszystko jest znacznie bardziej skomplikowane.

Synchronizacja

Podczas tworzenia nowego lustra wszystkie nowe wiadomości zawsze będą replikowane na to lustro i wszelkie inne. Jeśli chodzi o istniejące dane w głównej kolejce, możemy je zreplikować w nowe lustro, które staje się pełną kopią mastera. Możemy również nie replikować istniejących wiadomości i pozwolić głównej kolejce i nowemu lustrze synchronizować się w czasie, w którym nowe wiadomości trafiają na koniec, a istniejące wiadomości opuszczają początek głównej kolejki.

Taka synchronizacja odbywa się automatycznie lub ręcznie i jest zarządzana za pomocą polityki kolejek. Rozważmy przykład.

Mamy dwie lustrowane kolejki. Kolejka A synchronizuje się automatycznie, a Kolejka B ręcznie. W obu kolejkach jest po dziesięć wiadomości.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 8. Dwie kolejki z różnymi trybami synchronizacji

Teraz tracimy Brokera 3.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 9. Broker 3 upadł

Broker 3 wraca do działania. Klaster tworzy lustro dla każdej kolejki na nowym węźle i automatycznie synchronizuje nową Kolejkę A z masterem. Jednak lustro nowej Kolejki B pozostaje puste. W ten sposób mamy pełną nadmiarowość Kolejki A i tylko jedno lustro dla istniejących wiadomości Kolejki B.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 10. Nowe lustro Kolejki A otrzymuje wszystkie istniejące wiadomości, a nowe lustro Kolejki B — nie

Do obu kolejek dociera po dziesięć wiadomości. Następnie Broker 2 się zawiesza, a Kolejka A cofa się do najstarszego lustra, które znajduje się na Brokerze 1. W przypadku awarii nie dochodzi do utraty danych. W Kolejce B znajduje się dwadzieścia wiadomości w masterze i tylko dziesięć w lustrze, ponieważ ta kolejka nigdy nie replikowała początkowych dziesięciu wiadomości.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 11. Kolejka A cofa się do Brokera 1 bez utraty wiadomości.

Do obu kolejek dociera jeszcze po dziesięć wiadomości. Teraz pada Broker 1. Kolejka A bez problemu przełącza się na lustro bez utraty wiadomości. Jednak Kolejka B ma problemy. Na tym etapie możemy optymalizować albo dostępność, albo spójność.

Jeśli chcemy optymalizować dostępność, to politykę ha-promote-on-failure należy ustawić na always. To wartość domyślna, więc można po prostu nie określać polityki w ogóle. W takim przypadku zasadniczo dopuszczamy awarie w niesynchronizowanych lustrach. Doprowadzi to do utraty wiadomości, ale kolejka pozostaje dostępna do odczytu i zapisu.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 12. Kolejka A cofa się do Brokera 3 bez utraty wiadomości. Kolejka B cofa się do Brokera 3 z utratą dziesięciu wiadomości.

Możemy również ustawić ha-promote-on-failure na wartość when-synced. W tym przypadku zamiast cofać się do lustra kolejka będzie czekać, aż Broker 1 z danymi wróci do trybu operacyjnego. Po jego powrocie główna kolejka znowu znajduje się na Brokerze 1 bez utraty danych. Dostępność jest poświęcana bezpieczeństwu danych. Ale to ryzykowny tryb, który może prowadzić nawet do całkowitej utraty danych, co omówimy wkrótce.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 13. Kolejka B pozostaje niedostępna po utracie Brokera 1.

Możesz zadać pytanie: „Może lepiej nigdy nie używać automatycznej synchronizacji?”. Odpowiedź brzmi, że synchronizacja jest operacją blokującą. Podczas synchronizacji główna kolejka nie może wykonywać żadnych operacji odczytu lub zapisu!

Rozważmy przykład. Obecnie mamy bardzo duże kolejki. Jak mogą urosnąć do takiego rozmiaru? Z kilku powodów:

  • Kolejki nie są aktywnie wykorzystywane.
  • To szybkie kolejki, a obecnie konsumenci działają wolno.
  • To szybkie kolejki, doszło do awarii i konsumenci doganiają.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 14. Dwie duże kolejki z różnymi trybami synchronizacji

Teraz pada Broker 3.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 15. Broker 3 pada, zostawiając po jednym masterze i lustrze w każdej kolejce

Broker 3 wraca do działania, a nowe lustra są tworzone. Główna Kolejka A zaczyna replikować istniejące wiadomości na nowe lustro, a w tym czasie Kolejka jest niedostępna. Replikacja danych zajmuje dwie godziny, co prowadzi do dwóch godzin przestoju dla tej Kolejki!

Jednak Kolejka B pozostaje dostępna przez cały ten czas. Poświęciła część redundancji na rzecz dostępności.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 16. Kolejka pozostaje niedostępna podczas synchronizacji

Po dwóch godzinach Kolejka A również staje się dostępna i może znowu rozpocząć operacje odczytu i zapisu.

Aktualizacje

Tego rodzaju blokujące zachowanie podczas synchronizacji utrudnia aktualizację klastrów z bardzo dużymi kolejkami. W pewnym momencie węzeł z masterem musi być ponownie uruchomiony, co oznacza przejście na lustro lub odłączenie kolejki podczas aktualizacji serwera. Jeśli wybierzemy przejście, stracimy wiadomości, jeśli lustra nie są zsynchronizowane. Domyślnie w czasie wyłączenia brokera przejście na niesynchronizowane lustro nie jest realizowane. Oznacza to, że gdy broker powraca, nie tracimy żadnych wiadomości, jedyną szkodą był jedynie przestój kolejki. Zasady zachowania podczas wyłączania brokera określa polityka ha-promote-on-shutdown. Można ustawić jedną z dwóch wartości:

  • always= włączone przejście na niesynchronizowane lustra
  • when-synced= przejście tylko na zsynchronizowane lustro, w przeciwnym razie kolejka staje się niedostępna do odczytu i zapisu. Kolejka wraca do działania, gdy broker wróci

Tak czy inaczej, z dużymi kolejkami trzeba wybierać między utratą danych a niedostępnością.

Gdy dostępność zwiększa bezpieczeństwo danych

Zanim podejmiemy decyzję, należy wziąć pod uwagę jeszcze jedno utrudnienie. Chociaż automatyczna synchronizacja jest lepsza dla redundancji, jak wpływa na bezpieczeństwo danych? Oczywiście, dzięki lepszej redundancji RabbitMQ z mniejszym prawdopodobieństwem straci istniejące wiadomości, ale co z nowymi wiadomościami od wydawców?

Należy wziąć pod uwagę następujące kwestie:

  • Czy wydawca może po prostu zwrócić błąd, a wyższa usługa lub użytkownik spróbują ponownie później?
  • Czy wydawca może zapisać wiadomość lokalnie lub w bazie danych, aby spróbować ponownie później?

Jeśli wydawca może jedynie odrzucić wiadomość, to poprawa dostępności zwiększa również bezpieczeństwo danych.

W związku z tym należy szukać równowagi, a decyzja zależy od konkretnej sytuacji.

Problemy z ha-promote-on-failure=when-synced

Idea ha-promote-on-failure= when-synced polegają na tym, że uniemożliwiamy przełączenie na niesynchronizowane lustro, unikając tym samym utraty danych. Kolejka pozostaje niedostępna do odczytu lub zapisu. Zamiast tego staramy się przywrócić upadły broker z nienaruszonymi danymi, aby mógł wznowić pracę jako mistrz bez utraty danych.

Jednak (i to jest duże ale) jeśli broker stracił swoje dane, mamy duży problem: kolejka jest stracona! Wszystkie dane zniknęły! Nawet jeśli masz lustra, które w większości doganiają główną kolejkę, te lustra również są odrzucane.

Aby ponownie dodać węzeł o tej samej nazwie, mówimy klastrowi, aby zapomniał o utraconym węźle (poleceniem rabbitmqctl forget_cluster_node) i uruchamiamy nowego brokera o tej samej nazwie hosta. Dopóki klaster pamięta utracony węzeł, pamięta starą kolejkę i niesynchronizowane lustra. Gdy klasterowi każe się zapomnieć o utraconym węźle, ta kolejka również zostaje zapomniana. Teraz trzeba ją ponownie ogłosić. Straciliśmy wszystkie dane, chociaż mieliśmy lustra z częściowym zestawem danych. Lepiej byłoby przełączyć się na niesynchronizowane lustro!

Dlatego ręczna synchronizacja (i nie wykonywanie synchronizacji) w połączeniu z ha-promote-on-failure=when-synced, moim zdaniem, jest dość ryzykowna. Dokumenty mówią, że taka opcja istnieje dla bezpieczeństwa danych, ale to podwójnie ostrym nożem.

Przebalansowanie mistrzów

Jak obiecano, wracamy do problemu gromadzenia wszystkich mistrzów na jednym lub kilku węzłach. Może to się zdarzyć nawet w wyniku 'przechodniego' (rolling) aktualizowania klastra. W klastrze z trzema węzłami wszystkie główne kolejki zgromadzą się na jednym lub dwóch węzłach.

Przebalansowanie mistrzów może okazać się problematyczne z dwóch powodów:

  • Brak dobrych narzędzi do wykonania przebalansowania
  • Synchronizacja kolejek

Aby przeprowadzić rebalance, istnieje zewnętrzny plugin, który nie jest oficjalnie wspierany. Na temat zewnętrznych pluginów w dokumentacji RabbitMQ stwierdzono: «Plugin zapewnia dodatkowe narzędzia konfiguracyjne i raportowe, ale nie jest wspierany i nie został zweryfikowany przez zespół RabbitMQ. Korzystasz z niego na własne ryzyko».

Istnieje jeszcze jeden trik, aby przenieść główną kolejkę za pomocą polityk HA. W dokumentacji wspomniane jest skrypcie na ten temat. Działa to w następujący sposób:

  • Usuwa wszystkie lustra za pomocą tymczasowej polityki o wyższym priorytecie niż istniejąca polityka HA.
  • Zmienia tymczasową politykę HA, aby używała trybu „węzły” z określeniem węzła, na który ma zostać przeniesiona główna kolejka.
  • Synchronizuje kolejkę dla wymuszonej migracji.
  • Po zakończeniu migracji usuwa tymczasową politykę. Wchodzi w życie pierwotna polityka HA i tworzy odpowiednią liczbę luster.

Wadą tego podejścia jest to, że może nie zadziałać, jeśli masz duże kolejki lub rygorystyczne wymagania dotyczące nadmiarowości.

Teraz przyjrzyjmy się, jak klastry RabbitMQ działają w przypadku podziałów sieciowych.

Naruszenie spójności

Węzły rozproszonego systemu łączą się węzłami sieci, a połączenia sieciowe mogą i będą się przerywać. Częstotliwość przerwań zależy od lokalnej infrastruktury lub niezawodności wybranego chmury. W każdym razie rozproszone systemy muszą być w stanie sobie z nimi radzić. Ponownie mamy wybór między dostępnością a spójnością, a ponownie dobrą wiadomością jest to, że RabbitMQ zapewnia oba warianty (po prostu nie jednocześnie).

W RabbitMQ mamy dwie główne opcje:

  • Pozwolić na logiczny podział (split-brain). Zapewnia to dostępność, ale może prowadzić do utraty danych.
  • Zakazać logicznego podziału. Może prowadzić do krótkotrwałej utraty dostępności w zależności od sposobu połączenia klientów z klastrem. Może również prowadzić do całkowitej niedostępności w klastrze złożonym z dwóch węzłów.

Ale czym jest logiczny podział? To sytuacja, gdy klaster dzieli się na pół z powodu utraty połączeń sieciowych. Po każdej stronie lustra awansują do roli głównej, przez co ostatecznie na każdą kolejkę przypada kilka głównych.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 17. Główna kolejka i dwa lustra, każde na oddzielnym węźle. Następnie następuje awaria sieci, a jedno lustro się oddziela. Oddzielony węzeł widzi, że dwa inne odpadły i promuje swoje lustra do roli głównej. Teraz mamy dwie główne kolejki, z którymi można zarówno zapisywać, jak i odczytywać.

Jeżeli publikatorzy wysyłają dane do obu głównych, otrzymujemy dwa rozdzielające się kopie kolejki.

Różne tryby RabbitMQ zapewniają albo dostępność, albo spójność.

Tryb Ignoruj (domyślnie)

Ten tryb zapewnia dostępność. Po utracie spójności następuje logiczne podział. Po przywróceniu spójności administrator musi zdecydować, która strona ma być preferowana. Strona przegrana zostanie uruchomiona na nowo, a wszystkie nagromadzone dane z tej strony zostaną utracone.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 18. Trzech publikatorów jest połączonych z trzema brokerami. Wewnętrznie klaster kieruje wszystkie zapytania do głównej kolejki na Brokerze 2.

Teraz tracimy Brokera 3. Widział on, że inni brokerzy odpadli i promuje swoje lustro do roli głównej. Tak następuje logiczny podział.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 19. Logicznym podział (split-brain). Zapisy są kierowane do dwóch głównych kolejek, a dwie kopie się rozdzielają.

Spójność zostaje przywrócona, ale logiczny podział pozostaje. Administrator musi ręcznie wybrać przegraną stronę. W przedstawionym poniżej przypadku administrator restartuje Brokera 3. Wszystkie wiadomości, które nie zostały przez niego przekazane, zostaną utracone.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 20. Administrator wyłącza Brokera 3.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 21. Administrator uruchamia Brokera 3, a on dołącza do klastra, tracąc wszystkie wiadomości, które tam pozostały.

W czasie utraty spójności oraz po jej przywróceniu klaster i ta kolejka były dostępne do odczytu i zapisu.

Tryb Autoheal

Działa podobnie jak tryb Ignoruj, z wyjątkiem tego, że sam klaster automatycznie wybiera przegraną stronę po podziale i przywróceniu spójności. Przegrana strona wraca do klastra pusta, a kolejka traci wszystkie wiadomości, które były wysyłane tylko na tę stronę.

Tryb Pauza Mniejszości

Jeśli nie chcemy dopuścić do logicznego podziału, naszym jedynym rozwiązaniem jest rezygnacja z odczytu i zapisu po mniejszej stronie po podziale klastra. Kiedy broker widzi, że znajduje się po mniejszej stronie, wstrzymuje działanie, zamykając wszystkie istniejące połączenia i odrzucając wszelkie nowe. Co sekundę sprawdza odzyskanie łączności. Gdy tylko łączność zostanie przywrócona, wznawia działanie i ponownie łączy się z klastrem.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 22. Trzech wydawców jest połączonych z trzema brokerami. Wewnątrz klaster kieruje wszystkie zapytania do głównej kolejki na Brokerze 2.

Następnie Brokerzy 1 i 2 odłączają się od Brokera 3. Zamiast podnieść swoje lustro do roli mistrza, Broker 3 wstrzymuje działanie i staje się niedostępny.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 23. Broker 3 wstrzymuje działanie, odłącza wszystkich klientów i odrzuca zapytania o połączenie.

Gdy tylko łączność zostanie przywrócona, wraca do klastra.

Zobaczmy inny przykład, w którym główna kolejka znajduje się na Brokerze 3.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 24. Główna kolejka na Brokerze 3.

Następnie następuje ta sama utrata łączności. Broker 3 przechodzi w stan pauzy, ponieważ znajduje się po mniejszej stronie. Po drugiej stronie węzły widzą, że Broker 3 odpadł, więc starsze lustro z Brokerów 1 i 2 podnosi się do roli mistrza.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 25. Przejście do Brokera 2 przy niedostępności Brokera 3.

Gdy łączność zostanie przywrócona, Broker 3 dołączy do klastra.

RabbitMQ vs Kafka: odporność na awarie i wysoka dostępność w klastrach
Rys. 26. Klastar wrócił do normalnego działania.

Ważne jest, aby zrozumieć, że uzyskujemy spójność, ale możemy również uzyskać dostępność, jeśli sukcesywnie przeniesiemy klientów na większą część podziału. W większości sytuacji osobiście wybrałbym tryb Pauza Mniejszość, ale to naprawdę zależy od konkretnego przypadku.

Aby zapewnić dostępność, ważne jest, aby upewnić się, że klienci pomyślnie łączą się z węzłem. Rozważmy nasze opcje.

Zapewnienie łączności klientów

Mamy kilka sposobów, jak po utracie połączenia skierować klientów do głównej części klastra lub działających węzłów (po awarii jednego węzła). Najpierw przypomnijmy sobie, że konkretna kolejka jest umieszczana na określonym węźle, ale routingu i polityki są replikowane na wszystkich węzłach. Klienci mogą łączyć się z dowolnym węzłem, a wewnętrzne routingu skieruje ich tam, gdzie trzeba. Jednak gdy węzeł jest zawieszony, odrzuca połączenia, dlatego klienci muszą połączyć się z innym węzłem. Jeśli węzeł jest niedostępny, nie ma on za wiele do zaoferowania.

Nasze opcje:

  • Dostęp do klastra odbywa się za pomocą load balancera, który po prostu cyklicznie przegląda węzły, a klienci próbują się połączyć, aż do sukcesu. Jeśli węzeł jest niedziałający lub zawieszony, to próby połączenia z tym węzłem zakończą się niepowodzeniem, ale kolejne próby skierują się do innych serwerów (w trybie cyklicznym). To rozwiązanie jest odpowiednie w przypadku krótkotrwałej utraty połączenia lub awarii serwera, który szybko zostanie uruchomiony ponownie.
  • Dostęp do klastra przez load balancer i usunięcie zawieszonych/niedostępnych węzłów z listy, jak tylko będą wykryte. Jeśli uda się to szybko zrobić i jeśli klienci będą w stanie spróbować połączenia ponownie, to uzyskamy stałą dostępność.
  • Dać każdemu klientowi listę wszystkich węzłów, a klient przy połączeniu losowo wybiera jeden z nich. Jeżeli przy próbie połączenia otrzyma błąd, przechodzi do następnego węzła na liście, aż się połączy.
  • Usunąć ruch od niedziałającego/zawieszonego węzła za pomocą DNS. Robi się to przy użyciu małego TTL.

Wnioski

Klastrowanie RabbitMQ ma swoje zalety i wady. Najpoważniejsze wady to:

  • przy dołączeniu do klastra węzły odrzucają swoje dane;
  • blokująca synchronizacja prowadzi do niedostępności kolejki.

Wszystkie trudne decyzje wynikają z tych dwóch cech architektury. Gdyby RabbitMQ mogło przechowywać dane przy ponownym nawiązywaniu połączenia w klastrze, synchronizacja byłaby szybsza. Gdyby miało możliwość nienaładowej synchronizacji, lepiej obsługiwałoby duże kolejki. Rozwiązanie tych dwóch problemów znacznie poprawiłoby wydajność RabbitMQ jako technologii wymiany wiadomości o wysokiej dostępności i odporności na błędy. Nie odważyłbym się polecać RabbitMQ z klasteryzacją w następujących sytuacjach:

  • Niezawodna sieć.
  • Niezawodne przechowywanie.
  • Bardzo duże kolejki.

Jeśli chodzi o ustawienia wysokiej dostępności, rozważ takie:

  • ha-promote-on-failure=always
  • ha-sync-mode=manual
  • cluster_partition_handling=ignore (lub autoheal)
  • trwałe wiadomości
  • upewnij się, że klienci łączą się z aktywnym węzłem, gdy jakiś węzeł przestaje działać

Dla spójności (bezpieczeństwa danych) rozważ następujące ustawienia:

  • Potwierdzenia wydawcy i ręczne potwierdzenia po stronie konsumenta
  • ha-promote-on-failure=when-synced, jeśli wydawcy mogą spróbować ponownie później i jeśli masz bardzo niezawodne przechowywanie! W przeciwnym razie ustaw =always.
  • ha-sync-mode=automatic (ale dla dużych nieaktywnych kolejek może być wymagany tryb ręczny; ponadto zastanów się, czy niedostępność nie doprowadzi do utraty wiadomości)
  • tryb Pauza Mniejszości
  • trwałe wiadomości

Nie omówiliśmy jeszcze wszystkich zagadnień dotyczących odporności na błędy i wysokiej dostępności; na przykład, jak bezpiecznie przeprowadzać procedury administracyjne (takie jak aktualizacje bez przestojów). Należy również porozmawiać o federacji i wtyczce Shovel.

Jeśli coś jeszcze pominąłem, daj mi znać.

Zobacz także mój post, w którym dokonuję rozbicia klastra RabbitMQ przy użyciu Dockera i Blockade, aby przetestować niektóre scenariusze utraty wiadomości opisane w tym artykule.

Poprzednie artykuły z serii:
Nr 1 — habr.com/ru/company/itsumma/blog/416629
Nr 2 — habr.com/ru/company/itsumma/blog/418389
Nr 3 — habr.com/ru/company/itsumma/blog/437446

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster