Zrozumienie brokerów komunikatów. Poznanie mechaniki przesyłania wiadomości za pomocą ActiveMQ i Kafki. Rozdział 3. Kafka

Kontynuacja tłumaczenia małej książeczki:
Zrozumienie brokerów komunikatów
autor: Jakub Korab, wydawca: O'Reilly Media, Inc., data publikacji: czerwiec 2017, ISBN: 9781492049296.

Poprzednia przetłumaczona część: Zrozumienie brokerów komunikatów. Poznanie mechaniki przesyłania wiadomości za pomocą ActiveMQ i Kafki. Rozdział 1 Wstęp

ROZDZIAŁ 3

Kafka

Kafka została opracowana przez LinkedIn w celu obejścia niektórych ograniczeń tradycyjnych brokerów wiadomości i uniknięcia konieczności konfigurowania wielu brokerów wiadomości dla różnych interakcji typu punkt-punkt, co opisano w tej książce w części „Skalowanie w górę i w dół” na stronie 28 Przypadki użycia LinkedIn w dużej mierze opierał się na jednokierunkowym przetwarzaniu bardzo dużych ilości danych, takich jak kliknięcia na strony i dzienniki dostępu, jednocześnie umożliwiając wykorzystywanie tych danych przez wiele systemów bez wpływu na produktywność producentów lub innych konsumentów. W rzeczywistości powodem, dla którego Kafka istnieje, jest uzyskanie architektury przesyłania wiadomości, którą opisuje Universal Data Pipeline.

Biorąc pod uwagę ten ostateczny cel, w naturalny sposób pojawiły się inne wymagania. Kafka powinien:

  • Bądź bardzo szybki
  • Zapewnij większą przepustowość podczas pracy z wiadomościami
  • Obsługa modeli wydawca-subskrybent i punkt-punkt
  • Nie zwalniaj z dodawaniem konsumentów. Na przykład wydajność zarówno kolejki, jak i tematu w ActiveMQ spada wraz ze wzrostem liczby konsumentów w miejscu docelowym.
  • Skalowalność w poziomie; jeśli jeden broker, który utrzymuje komunikaty, może to robić tylko przy maksymalnej szybkości dysku, sensowne jest wyjście poza pojedynczą instancję brokera w celu zwiększenia wydajności
  • Ogranicz dostęp do przechowywania i ponownego pobierania wiadomości

Aby to wszystko osiągnąć, Kafka przyjął architekturę, która na nowo zdefiniowała role i obowiązki klientów i brokerów wiadomości. Model JMS jest bardzo zorientowany na brokera, w którym broker jest odpowiedzialny za dystrybucję komunikatów, a klienci muszą martwić się tylko o wysyłanie i odbieranie komunikatów. Z drugiej strony Kafka jest zorientowana na klienta, a klient przyjmuje wiele cech tradycyjnego brokera, takich jak uczciwa dystrybucja odpowiednich wiadomości do konsumentów, w zamian za niezwykle szybkiego i skalowalnego brokera. Dla osób, które pracowały z tradycyjnymi systemami przesyłania wiadomości, praca z Kafką wymaga fundamentalnej zmiany myślenia.
Ten kierunek inżynierii doprowadził do stworzenia infrastruktury komunikacyjnej zdolnej do zwiększenia przepustowości o wiele rzędów wielkości w porównaniu z konwencjonalnym brokerem. Jak zobaczymy, takie podejście wiąże się z kompromisami, co oznacza, że ​​Kafka nie nadaje się do niektórych rodzajów obciążeń i zainstalowanego oprogramowania.

Ujednolicony model docelowy

Aby spełnić wymagania opisane powyżej, Kafka połączył przesyłanie wiadomości typu „publikuj-subskrybuj” i „punkt-punkt” w ramach jednego rodzaju miejsca docelowego — temat. Jest to mylące dla osób, które pracowały z systemami przesyłania wiadomości, gdzie słowo „temat” odnosi się do mechanizmu rozgłaszania, z którego (z tematu) czytanie jest nietrwałe. Tematy Kafki należy uznać za hybrydowy typ docelowy, jak zdefiniowano we wstępie do tej książki.

W pozostałej części tego rozdziału, o ile wyraźnie nie zaznaczymy inaczej, termin „temat” będzie odnosił się do tematu Kafki.

Aby w pełni zrozumieć, jak zachowują się tematy i jakie dają gwarancje, musimy najpierw przyjrzeć się, jak są one implementowane w Kafce.
Każdy temat w Kafce ma swój własny dziennik.
Producenci wysyłający wiadomości do Kafki zapisują do tego dziennika, a konsumenci czytają z dziennika za pomocą wskaźników, które stale przesuwają się do przodu. Okresowo Kafka usuwa najstarsze części dziennika, niezależnie od tego, czy wiadomości w tych częściach zostały przeczytane, czy nie. Centralną częścią projektu Kafki jest to, że broker nie dba o to, czy wiadomości są czytane, czy nie - to odpowiedzialność klienta.

Terminy „log” i „wskaźnik” nie pojawiają się w Dokumentacja Kafki. Te dobrze znane terminy zostały tutaj użyte, aby ułatwić zrozumienie.

Ten model jest zupełnie inny niż ActiveMQ, gdzie wiadomości ze wszystkich kolejek są przechowywane w tym samym logu, a broker oznacza wiadomości jako usunięte po ich przeczytaniu.
Zagłębmy się teraz trochę głębiej i przyjrzyjmy się dziennikowi tematu bardziej szczegółowo.
Dziennik Kafki składa się z kilku partycji (Rysunek 3-1). Kafka gwarantuje ścisłe uporządkowanie w każdej partycji. Oznacza to, że wiadomości zapisane na partycji w określonej kolejności będą odczytywane w tej samej kolejności. Każda partycja jest zaimplementowana jako kroczący plik dziennika, który zawiera podzbiór (podzbiór) wszystkich komunikatów wysłanych do tematu przez jego producentów. Utworzony temat zawiera domyślnie jedną partycję. Idea partycji jest centralną ideą Kafki dla skalowania poziomego.

Zrozumienie brokerów komunikatów. Poznanie mechaniki przesyłania wiadomości za pomocą ActiveMQ i Kafki. Rozdział 3. Kafka
Rysunek 3-1. Partycje Kafki

Kiedy producent wysyła wiadomość do tematu Kafki, decyduje, do której partycji wysłać wiadomość. Później przyjrzymy się temu bardziej szczegółowo.

Czytanie wiadomości

Klient, który chce czytać wiadomości, zarządza nazwanym wskaźnikiem o nazwie grupa konsumencka, co wskazuje na zrównoważyć wiadomości w partycji. Przesunięcie to pozycja przyrostowa, która zaczyna się od 0 na początku partycji. Ta grupa konsumentów, do której odwołuje się interfejs API za pośrednictwem zdefiniowanego przez użytkownika identyfikatora grupy, odpowiada jeden logiczny konsument lub system.

Większość systemów przesyłania wiadomości odczytuje dane z miejsca docelowego przy użyciu wielu instancji i wątków w celu równoległego przetwarzania wiadomości. W związku z tym zwykle będzie wiele wystąpień konsumentów korzystających z tej samej grupy konsumentów.

Problem czytania można przedstawić w następujący sposób:

  • Temat ma wiele partycji
  • Wiele grup konsumentów może korzystać z tematu w tym samym czasie
  • Grupa konsumentów może mieć wiele oddzielnych wystąpień

Jest to nietrywialny problem typu „wiele do wielu”. Aby zrozumieć, w jaki sposób Kafka obsługuje relacje między grupami konsumentów, instancjami konsumentów i partycjami, przyjrzyjmy się serii coraz bardziej złożonych scenariuszy czytania.

Konsumenci i grupy konsumenckie

Weźmy jako punkt wyjścia temat z jedną partycją (Rysunek 3-2).

Zrozumienie brokerów komunikatów. Poznanie mechaniki przesyłania wiadomości za pomocą ActiveMQ i Kafki. Rozdział 3. Kafka
Rysunek 3-2. Konsument czyta z partycji

Gdy instancja konsumenta łączy się z własnym identyfikatorem grupy do tego tematu, przypisywana jest jej partycja do odczytu i przesunięcie w tej partycji. Pozycja tego przesunięcia jest konfigurowalna w kliencie jako wskaźnik do najnowszej pozycji (najnowsza wiadomość) lub najwcześniejszej pozycji (najstarsza wiadomość). Konsument żąda (ankiety) wiadomości z tematu, co powoduje, że są one sekwencyjnie odczytywane z dziennika.
Pozycja przesunięcia jest regularnie przekazywana z powrotem do Kafki i przechowywana jako komunikaty w temacie wewnętrznym _przesunięcia_konsumenckie. Przeczytane wiadomości nadal nie są usuwane, w przeciwieństwie do zwykłego brokera, a klient może cofnąć przesunięcie, aby ponownie przetworzyć już przeglądane wiadomości.

Gdy drugi logiczny konsument łączy się przy użyciu innego identyfikatora grupy, zarządza drugim wskaźnikiem, który jest niezależny od pierwszego (Rysunek 3-3). W związku z tym temat Kafki działa jak kolejka, w której jest jeden konsument, i jak normalny temat publikowania-subskrybowania (pub-sub), do którego subskrybuje wielu konsumentów, z dodatkową korzyścią polegającą na tym, że wszystkie wiadomości są przechowywane i mogą być przetwarzane wiele razy.

Zrozumienie brokerów komunikatów. Poznanie mechaniki przesyłania wiadomości za pomocą ActiveMQ i Kafki. Rozdział 3. Kafka
Rysunek 3-3. Dwóch konsumentów w różnych grupach konsumentów odczytuje z tej samej partycji

Konsumenci w grupie konsumenckiej

Gdy jedna instancja konsumenta odczytuje dane z partycji, ma pełną kontrolę nad wskaźnikiem i przetwarza komunikaty zgodnie z opisem w poprzedniej sekcji.
Jeśli kilka instancji konsumentów zostało połączonych z tym samym identyfikatorem grupy do tematu z jedną partycją, to instancja, która połączyła się jako ostatnia, otrzyma kontrolę nad wskaźnikiem i od tego momentu będzie otrzymywać wszystkie komunikaty (Rysunek 3-4).

Zrozumienie brokerów komunikatów. Poznanie mechaniki przesyłania wiadomości za pomocą ActiveMQ i Kafki. Rozdział 3. Kafka
Rysunek 3-4. Dwóch konsumentów w tej samej grupie konsumentów odczytuje z tej samej partycji

Ten tryb przetwarzania, w którym liczba wystąpień konsumentów przekracza liczbę partycji, można traktować jako rodzaj wyłącznego konsumenta. Może to być przydatne, jeśli potrzebujesz grupowania instancji konsumenckich w trybie „aktywne-pasywne” (lub „gorące-ciepłe”), chociaż równoległe uruchamianie wielu konsumentów („aktywne-aktywne” lub „gorące-gorące”) jest znacznie bardziej typowe niż konsumenci W trybie gotowości.

Opisane powyżej zachowanie związane z dystrybucją komunikatów może być zaskakujące w porównaniu z zachowaniem normalnej kolejki JMS. W tym modelu komunikaty wysyłane do kolejki będą równomiernie rozdzielane między dwóch konsumentów.

Najczęściej, gdy tworzymy wiele instancji konsumentów, robimy to albo w celu równoległego przetwarzania wiadomości, albo w celu zwiększenia szybkości czytania, albo w celu zwiększenia stabilności procesu czytania. Ponieważ tylko jedna instancja konsumenta może jednocześnie odczytywać dane z partycji, jak to osiągnąć w Kafce?

Jednym ze sposobów na to jest użycie pojedynczej instancji konsumenta do odczytania wszystkich komunikatów i przekazania ich do puli wątków. Chociaż takie podejście zwiększa przepustowość przetwarzania, zwiększa złożoność logiki konsumenta i nie zwiększa niezawodności systemu odczytu. Jeśli jedna kopia konsumenta ulegnie awarii z powodu awarii zasilania lub podobnego zdarzenia, odejmowanie zostanie zatrzymane.

Kanonicznym sposobem rozwiązania tego problemu w Kafce jest użycie bОwięcej partycji.

Partycjonowanie

Partycje to główny mechanizm równoległego odczytu i skalowania tematu poza przepustowością pojedynczej instancji brokera. Aby to lepiej zrozumieć, rozważmy sytuację, w której istnieje temat z dwiema partycjami i jeden konsument subskrybuje ten temat (Rysunek 3-5).

Zrozumienie brokerów komunikatów. Poznanie mechaniki przesyłania wiadomości za pomocą ActiveMQ i Kafki. Rozdział 3. Kafka
Rysunek 3-5. Jeden konsument odczytuje z wielu partycji

W tym scenariuszu konsument otrzymuje kontrolę nad wskaźnikami odpowiadającymi jego group_id w obu partycjach i rozpoczyna odczytywanie komunikatów z obu partycji.
Gdy do tego tematu zostanie dodany dodatkowy konsument dla tego samego group_id, Kafka przenosi jedną z partycji z pierwszego do drugiego konsumenta. Następnie każda instancja konsumenta będzie czytać z jednej partycji tematu (Rysunek 3-6).

Aby wiadomości były przetwarzane równolegle w 20 wątkach, potrzebujesz co najmniej 20 partycji. Jeśli jest mniej partycji, zostaniesz z konsumentami, którzy nie mają nad czym pracować, jak opisano wcześniej w omówieniu wyłącznych konsumentów.

Zrozumienie brokerów komunikatów. Poznanie mechaniki przesyłania wiadomości za pomocą ActiveMQ i Kafki. Rozdział 3. Kafka
Rysunek 3-6. Dwóch konsumentów w tej samej grupie konsumentów odczytuje z różnych partycji

Schemat ten znacznie zmniejsza złożoność brokera Kafka w porównaniu z dystrybucją komunikatów wymaganą do obsługi kolejki JMS. Tutaj nie musisz się martwić o następujące punkty:

  • Który konsument powinien otrzymać następny komunikat, na podstawie alokacji okrężnej, aktualnej pojemności buforów pobierania wstępnego lub poprzednich komunikatów (jak w przypadku grup komunikatów JMS).
  • Jakie wiadomości są wysyłane do jakich konsumentów i czy należy je ponownie dostarczyć w przypadku awarii.

Wszystko, co musi zrobić broker Kafka, to sekwencyjne przekazywanie wiadomości konsumentowi, gdy ten o to poprosi.

Jednak wymagania dotyczące zrównoleglania korekty i ponownego wysyłania nieudanych wiadomości nie znikają - odpowiedzialność za nie po prostu przechodzi z brokera na klienta. Oznacza to, że należy je uwzględnić w swoim kodzie.

Wysyłanie wiadomości

Producent tej wiadomości jest odpowiedzialny za podjęcie decyzji, do której partycji wysłać wiadomość. Aby zrozumieć mechanizm, za pomocą którego to się dzieje, musimy najpierw zastanowić się, co właściwie wysyłamy.

Podczas gdy w JMS używamy struktury wiadomości z metadanymi (nagłówki i właściwości) oraz treścią zawierającą ładunek (ładunek), w Kafce wiadomość jest sparuj „klucz-wartość”. Ładunek wiadomości jest wysyłany jako wartość. Z drugiej strony klucz jest używany głównie do partycjonowania i musi zawierać klucz specyficzny dla logiki biznesowejaby umieścić powiązane wiadomości w tej samej partycji.

W rozdziale 2 omówiliśmy scenariusz zakładów online, w którym powiązane zdarzenia muszą być przetwarzane w kolejności przez jednego konsumenta:

  1. Konto użytkownika jest skonfigurowane.
  2. Pieniądze są przelewane na konto.
  3. Stawiany jest zakład, który pobiera pieniądze z konta.

Jeśli każde zdarzenie jest wiadomością opublikowaną w temacie, kluczem naturalnym byłby identyfikator konta.
Gdy wiadomość jest wysyłana za pomocą Kafka Producer API, jest ona przekazywana do funkcji partycji, która biorąc pod uwagę wiadomość i aktualny stan klastra Kafka, zwraca identyfikator partycji, do której wiadomość ma zostać wysłana. Ta funkcja jest zaimplementowana w Javie za pośrednictwem interfejsu Partitioner.

Ten interfejs wygląda tak:

interface Partitioner {
    int partition(String topic,
        Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);
}

Implementacja Partitioner używa domyślnego algorytmu wyznaczania wartości skrótu ogólnego przeznaczenia dla klucza w celu określenia partycji lub w trybie okrężnym, jeśli nie określono żadnego klucza. Ta wartość domyślna działa dobrze w większości przypadków. Jednak w przyszłości będziesz chciał pisać własne.

Pisanie własnej strategii partycjonowania

Spójrzmy na przykład, w którym chcesz wysłać metadane wraz z ładunkiem wiadomości. Ładunek w naszym przykładzie to instrukcja dokonania wpłaty na konto gry. Instrukcja to coś, co do której chcielibyśmy mieć gwarancję, że nie zostanie zmodyfikowana podczas transmisji, i chcemy mieć pewność, że tylko zaufany system nadrzędny może zainicjować tę instrukcję. W takim przypadku systemy wysyłające i odbierające uzgadniają użycie podpisu w celu uwierzytelnienia wiadomości.
W normalnym JMS po prostu definiujemy właściwość „sygnatury wiadomości” i dodajemy ją do wiadomości. Jednak Kafka nie udostępnia nam mechanizmu przekazywania metadanych, a jedynie klucz i wartość.

Ponieważ wartością jest ładunek przelewu bankowego, którego integralność chcemy zachować, nie mamy innego wyboru, jak tylko zdefiniować strukturę danych do użycia w kluczu. Zakładając, że potrzebujemy identyfikatora konta do partycjonowania, ponieważ wszystkie wiadomości związane z kontem muszą być przetwarzane w określonej kolejności, otrzymamy następującą strukturę JSON:

{
  "signature": "541661622185851c248b41bf0cea7ad0",
  "accountId": "10007865234"
}

Ponieważ wartość podpisu będzie się różnić w zależności od ładunku, domyślna strategia mieszania interfejsu Partitioner nie będzie niezawodnie grupować powiązane wiadomości. Dlatego będziemy musieli napisać własną strategię, która przeanalizuje ten klucz i podzieli wartość accountId na partycje.

Kafka zawiera sumy kontrolne do wykrywania uszkodzonych wiadomości w sklepie i posiada pełny zestaw funkcji bezpieczeństwa. Mimo to czasami pojawiają się wymagania specyficzne dla branży, takie jak powyższe.

Strategia partycjonowania użytkownika musi zapewniać, że wszystkie powiązane komunikaty trafią do tej samej partycji. Chociaż wydaje się to proste, wymaganie może być skomplikowane ze względu na znaczenie uporządkowania powiązanych komunikatów i stałą liczbę partycji w temacie.

Liczba partycji w temacie może zmieniać się w czasie, ponieważ można je dodać, jeśli ruch przekroczy początkowe oczekiwania. W ten sposób klucze komunikatów można powiązać z partycją, do której zostały pierwotnie wysłane, co oznacza, że ​​część stanu ma być współdzielona między instancjami producenta.

Innym czynnikiem, który należy wziąć pod uwagę, jest równomierna dystrybucja komunikatów między partycjami. Zazwyczaj klucze nie są równomiernie rozłożone w wiadomościach, a funkcje skrótu nie gwarantują sprawiedliwego rozłożenia wiadomości dla małego zestawu kluczy.
Należy pamiętać, że niezależnie od tego, jak zdecydujesz się podzielić wiadomości, konieczne może być ponowne użycie samego separatora.

Rozważ wymóg replikacji danych między klastrami Kafki w różnych lokalizacjach geograficznych. W tym celu Kafka dostarcza narzędzie wiersza poleceń o nazwie MirrorMaker, które służy do odczytywania wiadomości z jednego klastra i przesyłania ich do innego.

MirrorMaker musi rozumieć klucze replikowanego tematu, aby zachować względną kolejność komunikatów podczas replikacji między klastrami, ponieważ liczba partycji dla tego tematu może nie być taka sama w dwóch klastrach.

Niestandardowe strategie partycjonowania są stosunkowo rzadkie, ponieważ domyślne mieszanie lub działanie okrężne działa dobrze w większości scenariuszy. Jeśli jednak potrzebujesz silnych gwarancji porządkowania lub musisz wyodrębnić metadane z ładunków, partycjonowanie jest czymś, czemu powinieneś przyjrzeć się bliżej.

Skalowalność i wydajność Kafki wynikają z przeniesienia części obowiązków tradycyjnego brokera na klienta. W takim przypadku podejmowana jest decyzja o dystrybucji potencjalnie powiązanych wiadomości między kilku pracujących równolegle konsumentów.

Brokerzy JMS również muszą sprostać takim wymaganiom. Co ciekawe, mechanizm wysyłania powiązanych wiadomości do tego samego konsumenta, zaimplementowany za pośrednictwem JMS Message Groups (odmiana strategii SLB) również wymaga od nadawcy oznaczania wiadomości jako powiązanych. W przypadku JMS broker jest odpowiedzialny za wysłanie tej grupy powiązanych ze sobą komunikatów do jednego konsumenta z wielu oraz przeniesienie własności grupy w przypadku odpadnięcia konsumenta.

Umowy Producentów

Partycjonowanie nie jest jedyną rzeczą, którą należy wziąć pod uwagę podczas wysyłania wiadomości. Przyjrzyjmy się metodom send() klasy Producer w Java API:

Future < RecordMetadata > send(ProducerRecord < K, V > record);
Future < RecordMetadata > send(ProducerRecord < K, V > record, Callback callback);

Należy od razu zauważyć, że obie metody zwracają Future, co oznacza, że ​​operacja wysyłania nie jest wykonywana natychmiast. W rezultacie komunikat (ProducerRecord) jest zapisywany w buforze wysyłania dla każdej aktywnej partycji i wysyłany do brokera jako wątek tła w bibliotece klienta Kafka. Chociaż sprawia to, że wszystko jest niewiarygodnie szybkie, oznacza to, że niedoświadczona aplikacja może utracić wiadomości, jeśli jej proces zostanie zatrzymany.

Jak zawsze istnieje sposób na zwiększenie niezawodności operacji wysyłania kosztem wydajności. Rozmiar tego bufora można ustawić na 0, a wątek aplikacji wysyłającej będzie zmuszony czekać, aż zakończy się przesyłanie komunikatu do brokera, w następujący sposób:

RecordMetadata metadata = producer.send(record).get();

Więcej informacji o czytaniu wiadomości

Czytanie wiadomości wiąże się z dodatkowymi komplikacjami, o których należy spekulować. W przeciwieństwie do interfejsu API JMS, który może uruchamiać nasłuchiwanie komunikatów w odpowiedzi na komunikat, interfejs konsument Ankiety tylko dla Kafki. Przyjrzyjmy się bliżej metodzie głosowanie()wykorzystywane w tym celu:

ConsumerRecords < K, V > poll(long timeout);

Wartością zwracaną przez metodę jest struktura kontenera zawierająca wiele obiektów rekord konsumencki z potencjalnie kilku partycji. rekord konsumencki sam jest obiektem posiadacza dla pary klucz-wartość z powiązanymi metadanymi, takimi jak partycja, z której pochodzi.

Jak omówiono w rozdziale 2, musimy pamiętać o tym, co dzieje się z komunikatami po ich pomyślnym lub niepomyślnym przetworzeniu, na przykład, jeśli klient nie jest w stanie przetworzyć komunikatu lub przerywa działanie. W JMS było to obsługiwane w trybie potwierdzenia. Broker albo usunie pomyślnie przetworzoną wiadomość, albo ponownie dostarczy nieprzetworzoną lub fałszywą wiadomość (zakładając, że wykorzystano transakcje).
Kafka działa zupełnie inaczej. Wiadomości nie są usuwane w brokerze po sprawdzeniu, a za to, co dzieje się w przypadku niepowodzenia, odpowiada sam kod sprawdzający.

Jak powiedzieliśmy, grupa konsumentów jest powiązana z przesunięciem w dzienniku. Pozycja dziennika powiązana z tym przesunięciem odpowiada następnemu komunikatowi, na który ma zostać wysłana odpowiedź głosowanie(). Moment, w którym to przesunięcie wzrasta, jest decydujący dla odczytu.

Wracając do omówionego wcześniej modelu czytania, przetwarzanie wiadomości składa się z trzech etapów:

  1. Pobierz wiadomość do przeczytania.
  2. Przetwórz wiadomość.
  3. Potwierdź wiadomość.

Konsument Kafki ma opcję konfiguracji włącz.auto.zatwierdzenie. Jest to często używane ustawienie domyślne, podobnie jak w przypadku ustawień zawierających słowo „auto”.

Przed Kafką 0.10 klient korzystający z tej opcji wysyłał offset ostatniej odczytanej wiadomości przy następnym wywołaniu głosowanie() po przetworzeniu. Oznaczało to, że wszelkie wiadomości, które zostały już pobrane, mogły zostać ponownie przetworzone, jeśli klient już je przetworzył, ale został nieoczekiwanie zniszczony przed wywołaniem głosowanie(). Ponieważ broker nie przechowuje informacji o tym, ile razy wiadomość została przeczytana, następny konsument, który pobierze tę wiadomość, nie będzie wiedział, że stało się coś złego. To zachowanie było pseudotransakcyjne. Przesunięcie zostało zatwierdzone tylko wtedy, gdy komunikat został pomyślnie przetworzony, ale jeśli klient przerwał działanie, broker ponownie wysłałby ten sam komunikat do innego klienta. To zachowanie było zgodne z gwarancją dostarczenia wiadomości "przynajmniej raz".

W Kafce 0.10 kod klienta został zmieniony tak, że zatwierdzenie jest okresowo wyzwalane przez bibliotekę klienta, zgodnie z konfiguracją auto.commit.interval.ms. To zachowanie jest gdzieś pomiędzy trybami JMS AUTO_ACKNOWLEDGE i DUPS_OK_ACKNOWLEDGE. Podczas korzystania z automatycznego zatwierdzania wiadomości mogą być zatwierdzane niezależnie od tego, czy zostały faktycznie przetworzone - może się to zdarzyć w przypadku powolnego konsumenta. W przypadku przerwania przez konsumenta wiadomości byłyby pobierane przez następnego konsumenta, zaczynając od zatwierdzonej pozycji, co mogłoby skutkować pominięciem wiadomości. W tym przypadku Kafka nie zgubił wiadomości, kod odczytu po prostu ich nie przetworzył.

Ten tryb ma taką samą obietnicę, jak w wersji 0.9: wiadomości mogą być przetwarzane, ale jeśli się nie powiedzie, przesunięcie może nie zostać zatwierdzone, co potencjalnie może spowodować podwojenie dostarczania. Im więcej wiadomości pobierzesz podczas wykonywania głosowanie(), tym więcej tego problemu.

Jak omówiono w sekcji „Odczytywanie komunikatów z kolejki” na stronie 21, nie ma czegoś takiego jak jednorazowe dostarczenie komunikatu w systemie przesyłania komunikatów, jeśli bierze się pod uwagę tryby awarii.

W Kafce istnieją dwa sposoby zatwierdzenia (zatwierdzenia) przesunięcia (przesunięcia): automatycznie i ręcznie. W obu przypadkach komunikaty mogą być przetwarzane wiele razy, jeśli przed zatwierdzeniem zostały przetworzone, ale zakończyły się niepowodzeniem. Możesz także w ogóle nie przetwarzać wiadomości, jeśli zatwierdzenie miało miejsce w tle, a Twój kod został ukończony, zanim mógł zostać przetworzony (być może w Kafce 0.9 i wcześniejszych).

Proces ręcznego zatwierdzania przesunięcia można kontrolować w konsumenckim interfejsie API platformy Kafka, ustawiając parametr włącz.auto.zatwierdzenie na false i jawnie wywołując jedną z następujących metod:

void commitSync();
void commitAsync();

Jeśli chcesz przetworzyć wiadomość „co najmniej raz”, musisz ręcznie zatwierdzić przesunięcie za pomocą zatwierdzenieSynchronizacja()wykonując to polecenie natychmiast po przetworzeniu komunikatów.

Metody te nie pozwalają na potwierdzanie komunikatów przed ich przetworzeniem, ale nie eliminują potencjalnych opóźnień w przetwarzaniu, jednocześnie sprawiając wrażenie transakcyjnych. W Kafce nie ma transakcji. Klient nie ma możliwości wykonania następujących czynności:

  • Automatycznie wycofaj sfałszowaną wiadomość. Konsumenci sami muszą radzić sobie z wyjątkami wynikającymi z problematycznych ładunków i awarii zaplecza, ponieważ nie mogą polegać na brokerze w zakresie ponownego dostarczania wiadomości.
  • Wysyłaj wiadomości do wielu tematów w ramach jednej niepodzielnej operacji. Jak wkrótce zobaczymy, kontrola nad różnymi tematami i partycjami może znajdować się na różnych maszynach w klastrze Kafka, które nie koordynują transakcji podczas wysyłania. W chwili pisania tego tekstu wykonano pewne prace, aby było to możliwe dzięki KIP-98.
  • Powiąż czytanie jednej wiadomości z jednego tematu z wysłaniem kolejnej wiadomości do innego tematu. Ponownie, architektura Kafki zależy od wielu niezależnych maszyn działających jako jedna magistrala i nie próbuje się tego ukryć. Na przykład nie ma żadnych komponentów API, które pozwoliłyby na połączenie konsument и Producent w transakcji. W JMS zapewnia to obiekt Sesjaz których powstają Producenci wiadomości и WiadomośćKonsumenci.

Jeśli nie możemy polegać na transakcjach, w jaki sposób możemy zapewnić semantykę bliższą tym zapewnianym przez tradycyjne systemy przesyłania wiadomości?

Jeśli istnieje możliwość, że przesunięcie konsumenta może wzrosnąć przed przetworzeniem komunikatu, na przykład podczas awarii konsumenta, wówczas konsument nie ma możliwości dowiedzenia się, czy jego grupa konsumentów nie przegapiła komunikatu, gdy jest przypisywana do partycji. Tak więc jedną ze strategii jest przewinięcie przesunięcia do poprzedniej pozycji. Konsument API Kafki udostępnia następujące metody:

void seek(TopicPartition partition, long offset);
void seekToBeginning(Collection < TopicPartition > partitions);

metoda szukać() może być używany metodą
offsetsForTimes(Map sygnatury czasowe do wyszukiwania) przewinąć do stanu w określonym momencie w przeszłości.

Niejawnie użycie tego podejścia oznacza, że ​​jest bardzo prawdopodobne, że niektóre wiadomości, które zostały wcześniej przetworzone, zostaną ponownie odczytane i przetworzone. Aby tego uniknąć, możemy użyć idempotentnego odczytu, jak opisano w rozdziale 4, aby śledzić poprzednio oglądane wiadomości i eliminować duplikaty.

Alternatywnie, Twój kod konsumencki może być prosty, o ile akceptowalna jest utrata lub powielenie wiadomości. Kiedy rozważamy przypadki użycia, w których Kafka jest powszechnie używana, takie jak obsługa zdarzeń dziennika, metryki, śledzenie kliknięć itp., rozumiemy, że utrata poszczególnych wiadomości raczej nie będzie miała znaczącego wpływu na otaczające aplikacje. W takich przypadkach wartości domyślne są całkowicie akceptowalne. Z drugiej strony, jeśli Twoja aplikacja ma przesyłać płatności, musisz uważnie zatroszczyć się o każdą pojedynczą wiadomość. Wszystko sprowadza się do kontekstu.

Osobiste obserwacje pokazują, że wraz ze wzrostem intensywności komunikatów wartość każdego pojedynczego komunikatu maleje. Duże wiadomości są zwykle cenne, gdy są przeglądane w formie zagregowanej.

Duża dostępność

Podejście Kafki do wysokiej dostępności bardzo różni się od podejścia ActiveMQ. Kafka została zaprojektowana wokół skalowalnych klastrów, w których wszystkie instancje brokera odbierają i dystrybuują komunikaty w tym samym czasie.

Klaster Kafka składa się z wielu instancji brokera działających na różnych serwerach. Kafka została zaprojektowana do działania na zwykłym samodzielnym sprzęcie, gdzie każdy węzeł ma własną dedykowaną pamięć masową. Korzystanie z magazynu podłączonego do sieci (SAN) nie jest zalecane, ponieważ wiele węzłów obliczeniowych może konkurować o czas.Ыe interwały przechowywania i tworzyć konflikty.

Kafka jest zawsze włączone system. Wielu dużych użytkowników Kafki nigdy nie zamyka swoich klastrów, a oprogramowanie zawsze aktualizuje się po sekwencyjnym ponownym uruchomieniu. Osiąga się to poprzez zagwarantowanie zgodności z poprzednią wersją komunikatów i interakcji między brokerami.

Brokerzy podłączeni do klastra serwerów Opiekun zoo, który działa jako rejestr danych konfiguracyjnych i służy do koordynowania ról każdego brokera. ZooKeeper sam w sobie jest systemem rozproszonym, który zapewnia wysoką dostępność poprzez replikację informacji poprzez ustanowienie kworum.

W podstawowym przypadku w klastrze Kafka tworzony jest temat o następujących właściwościach:

  • Liczba partycji. Jak omówiono wcześniej, dokładna wartość użyta tutaj zależy od pożądanego poziomu czytania równoległego.
  • Współczynnik replikacji (czynnik) określa, ile instancji brokera w klastrze powinno zawierać dzienniki dla tej partycji.

Wykorzystując ZooKeepers do koordynacji, Kafka próbuje sprawiedliwie rozdzielić nowe partycje wśród brokerów w klastrze. Odbywa się to za pomocą pojedynczej instancji, która działa jako kontroler.

W czasie wykonywania dla każdej sekcji tematycznej Kontroler przypisać role do brokera lider (lider, mistrz, prezenter) i Obserwujący (zwolennicy, niewolnicy, podwładni). Broker, pełniący rolę lidera tej partycji, jest odpowiedzialny za odbieranie wszystkich wiadomości wysyłanych do niego przez producentów i dystrybucję wiadomości do konsumentów. Gdy komunikaty są wysyłane do partycji tematu, są replikowane do wszystkich węzłów brokera działających jako śledzący dla tej partycji. Każdy węzeł zawierający logi dla partycji jest wywoływany replika. Broker może pełnić rolę lidera dla niektórych partycji i obserwatora dla innych.

Nazywa się obserwatora zawierającego wszystkie wiadomości posiadane przez lidera zsynchronizowana replika (replika, która jest w stanie zsynchronizowanym, replika w synchronizacji). W przypadku awarii brokera pełniącego rolę lidera partycji, rolę lidera może przejąć dowolny broker, który jest aktualny lub zsynchronizowany dla tej partycji. To niesamowicie zrównoważony projekt.

Częścią konfiguracji producenta jest parametr worki, która określa, ile replik musi potwierdzić (potwierdzić) odbiór komunikatu, zanim wątek aplikacji będzie kontynuował wysyłanie: 0, 1 lub wszystkie. Jeśli ustawione na cała kolekcja, to po otrzymaniu wiadomości lider wyśle ​​potwierdzenie z powrotem do producenta, gdy tylko otrzyma potwierdzenia (potwierdzenia) rekordu z kilku pamięci (w tym siebie) zdefiniowanych przez ustawienie tematu repliki min.insync (domyślnie 1). Jeśli wiadomość nie może zostać pomyślnie zreplikowana, producent zgłosi wyjątek aplikacji (Niewystarczająca liczba replik lub NotEnough Replicas AfterAppend).

Typowa konfiguracja tworzy temat ze współczynnikiem replikacji 3 (1 lider, 2 obserwujących na partycję) i parametrem repliki min.insync jest ustawiona na 2. W takim przypadku klaster pozwoli na wyłączenie jednego z brokerów zarządzających partycją tematu bez wpływu na aplikacje klienckie.

To prowadzi nas z powrotem do znanego już kompromisu między wydajnością a niezawodnością. Replikacja odbywa się kosztem dodatkowego czasu oczekiwania na potwierdzenia (potwierdzenia) od obserwujących. Chociaż, ponieważ działa równolegle, replikacja do co najmniej trzech węzłów ma taką samą wydajność jak dwa (pomijając wzrost wykorzystania przepustowości sieci).

Używając tego schematu replikacji, Kafka sprytnie unika konieczności fizycznego zapisywania każdej wiadomości na dysku wraz z operacją synchronizacja(). Każdy komunikat wysłany przez producenta zostanie zapisany w dzienniku partycji, ale jak omówiono w rozdziale 2, zapis do pliku odbywa się początkowo w buforze systemu operacyjnego. Jeśli ta wiadomość jest replikowana do innej instancji Kafki i znajduje się w jej pamięci, utrata lidera nie oznacza utraty samej wiadomości – może zostać przejęta przez zsynchronizowaną replikę.
Odmowa wykonania operacji synchronizacja() oznacza, że ​​Kafka może odbierać wiadomości tak szybko, jak zapisuje je w pamięci. I odwrotnie, im dłużej można uniknąć opróżniania pamięci na dysk, tym lepiej. Z tego powodu nierzadko zdarza się, że brokerom Kafki przydziela się 64 GB lub więcej pamięci. Takie użycie pamięci oznacza, że ​​pojedyncza instancja Kafki może z łatwością działać z prędkością wiele tysięcy razy większą niż tradycyjny broker komunikatów.

Kafkę można również skonfigurować do zastosowania operacji synchronizacja() do pakietów wiadomości. Ponieważ wszystko w Kafce jest zorientowane na pakiety, w rzeczywistości działa całkiem dobrze w wielu przypadkach użycia i jest użytecznym narzędziem dla użytkowników, którzy wymagają bardzo silnych gwarancji. Duża część czystej wydajności Kafki pochodzi z komunikatów, które są wysyłane do brokera jako pakiety i że te komunikaty są odczytywane z brokera w sekwencyjnych blokach przy użyciu kopia zerowa operacje (operacje, podczas których nie jest wykonywane zadanie kopiowania danych z jednego obszaru pamięci do drugiego). Ta ostatnia opcja zapewnia duży wzrost wydajności i zasobów i jest możliwa tylko dzięki wykorzystaniu podstawowej struktury danych dziennika, która definiuje schemat partycji.

W klastrze Kafka możliwa jest znacznie lepsza wydajność niż w przypadku pojedynczego brokera Kafka, ponieważ partycje tematyczne można skalować w poziomie na wiele oddzielnych maszyn.

Wyniki

W tym rozdziale przyjrzeliśmy się, jak architektura Kafka na nowo wyobraża sobie relacje między klientami a brokerami, aby zapewnić niewiarygodnie solidny potok przesyłania komunikatów o przepustowości wielokrotnie większej niż w przypadku konwencjonalnego brokera komunikatów. Omówiliśmy funkcjonalność używaną do osiągnięcia tego celu i pokrótce przyjrzeliśmy się architekturze aplikacji, które zapewniają tę funkcjonalność. W następnym rozdziale przyjrzymy się typowym problemom, które aplikacje obsługujące komunikaty muszą rozwiązywać, oraz omówimy strategie radzenia sobie z nimi. Zakończymy ten rozdział, opisując, jak ogólnie mówić o technologiach przesyłania wiadomości, abyś mógł ocenić ich przydatność w twoich przypadkach użycia.

Poprzednia przetłumaczona część: Zrozumienie brokerów komunikatów. Poznanie mechaniki przesyłania wiadomości za pomocą ActiveMQ i Kafki. Rozdział 1

Tłumaczenie wykonane: tele.gg/middle_java

To be continued ...

W ankiecie mogą brać udział tylko zarejestrowani użytkownicy. Zaloguj się, Proszę.

Czy w Twojej organizacji wykorzystywana jest Kafka?

  • Tak

  • Nie

  • Wcześniej używany, teraz nie

  • Planujemy użyć

Głosowało 38 użytkowników. 8 użytkowników wstrzymało się od głosu.

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

Dodaj komentarz