
„Message brokers” to inaczej pośrednicy w wysyłce i odbiorze wiadomości. Najpopularniejszymi z nich są niewątpliwie Kafka i RabbitMQ. W dzisiejszym odcinku serii „Czym jest…?” zapoznamy się z oboma tymi rozwiązaniami – gdzie je stosować oraz jakie są pomiędzy nimi różnice.
Uwaga! W tej serii zajmuję się opisowym przedstawieniem tematu, a celem jest zapoznanie z zagadnieniem w sposób luźny i zrozumiały dla nowicjusza. W związku z tym wiele elementów siłą rzeczy musi zostać pominiętych. Tych z was, którzy chcieliby się dowiedzieć więcej, zapraszam do źródeł na końcu artykułu.
Kiedy w 2015 roku przybyłem do Chin po raz pierwszy, to (jeśli chodzi o elektronikę) miałem przy sobie tylko telefon (klasyczny, nie smartfon!), aparat cyfrowy i tablet (który dawał radę przy publikacji wpisów na tym blogu i przy okazji nie zajmował wiele miejsca w plecaku).
W założeniu miałem spędzić tu góra 2 miesiące, ale wyszło jak wyszło i zostałem na dłużej. W związku ze znalezieniem pracy i mieszkania, powyższy setup przestał mi wystarczać. Musiałem kupić pełnoprawny komputer.
Wybór padł na minipeceta od nieznanej w Polsce firmy Mele. Z plusów to z pewnością: rozmiar, okazyjna cena, bezgłośna praca oraz minimalne zużycie prądu przez co mógł być uruchomiony 24/7. No i działał!

To działanie pozostawiało jednak sporo do życzenia – no bo czego można oczekiwać od Windowsa 8 pracującego na karcie SD, z 2GB RAM, prockiem Intel Atom i integrą? Po roku użytkowania zdecydowałem się na update do nowszego modelu Mele PCG03 Apo i nie żałuję, bo przyrost mocy był zaiste odczuwalny, a wszystkie zalety poprzednika zostały zachowane.
Mimo to, ów sprzęt nie był jeszcze tym czego oczekiwałem od komputera. Wciąż był wolniejszy ode mnie. Blokował mnie.
Kupując jakąś elektronikę użytkową oczekujemy od niej, że będzie… „snappy” co po angielsku tłumaczy się na „żwawa” i niezbyt pasuje do naszego kontekstu. A chodzi po prostu o to abyśmy nie musieli czekać aż coś się załaduje. Żeby nas nie blokowała.
Pewnie was w tym momencie zaskoczę,
ale zupełnie tak samo jest z API.

Klasyczna usługa REST działa w sposób synchroniczny:
Wchodzimy na stronę www wysyłając zapytanie do metody np. GET po stronie serwera. Ten przejmuje pałeczkę: generuje nam zawartość i wysyła ją z powrotem. Potem my odczytujemy co nam przesłał i na tej podstawie generujemy nowy request, następnie znowu serwer, znowu my, znowu serwer… i tak w koło Macieju.
Komunikacja synchroniczna jest jak gra w tenisa: najpierw uderza jeden zawodnik, a potem drugi.
Problem w powyższym jest taki, że kiedy piłka jest po stronie serwera, to my musimy czekać i nie możemy zrobić niczego innego – jesteśmy zablokowani. Aby zaadresować ten problem powstało coś takiego jak
Komunikacja Asynchroniczna
Jeśli chciałbym ją analogicznie przyrównać do jakiegoś sportu to pierwsza na myśl przychodzi mi gra w zbijaka – piłek jest sporo, a rzuciwszy jedną możemy zaraz rzucić kolejną, nawet nie patrząc czy poprzednia trafiła gdzie trzeba.

Wyobraźcie sobie stronę, która ma pojedynczy przycisk i licznik ile razy ten przycisk został wciśnięty. Kiedy na niego klikamy zostaje wysłany sygnał do serwera aby podnieść wartość licznika o 1. Proste.
A teraz pomyślcie, że napierdzielacie w ten button jak maszyna, 1000 klików na sekundę. W czasie kiedy backend „przemiela” jedno kliknięcie, pozostałe nie zostaną zliczone.
System jest zablokowany i „myśli”.
Z kolei asynchronicznie wyglądałoby to w ten sposób, że po tym tysiącu kliknięć odświeżacie stronę i macie, powiedzmy 180, odświeżacie znowu i licznik wskazuje już na 400, kolejne odświeżenia i w końcu licznik zatrzymuje się na liczbie 1000. Żaden klik nie został pominięty.
Aby zrozumieć ten mechanizm i osiągnąć jedność z pośrednikami wiadomości musimy wpierw wrócić do podstaw Javy i przypomnieć sobie
Collections API
Zaraz po poznaniu tabel wchodzimy w świat kolekcji, gdzie niepodzielnie króluje uniwersalna ArrayLista. Przejdźmy sobie na szybko przez najpopularniejsze listy i zbiory w celu przypomnienia wiadomości:
- ArrayList sprawdza się tam, gdzie potrzebujemy zachować kolejność, a do tego więcej czytamy niż dodajemy
- LinkedList podobnie jak powyżej, ale tu więcej dodajemy niż czytamy
- HashSet ma to do siebie, że nie zachowuje kolejności, ale za to z automatu wywala duplikaty
- TreeSet też wywala duplikaty, ale zachowuje kolejność
- Stack to wariacja na temat listy – wrzucamy element na stos i od razu możemy go pobrać (LIFO – last in, first out)
- Queue to kolejka czyli odwrotność stosu (FIFO – first in, first out), kto pierwszy ustawi się w kolejce – pierwszy zostanie obsłużony
Oczywiście jest tego więcej, bo mamy też wersje dla operacji wielowątkowych (przedrostek „Concurrent„), kolejki priorytetowe albo „dwugłowe” („Deque„), ale to nas dzisiaj nie interesuje.

Esencją działania message brokerów jest bez wątpienia kolejka/queue pracująca zgodnie ze wzorcem Producer-Consumer (producent-konsument), tutaj przemianowanego na:
Publisher-Subscriber
Aby słowo stało się ciałem, potrzebujemy dwóch* serwisów: tego co produkuje wiadomości (Publisher dodaje elementy do kolejki) i tego co je odbiera (Subscriber ściąga elementy z kolejki). Mogą to być osobne moduły w monolicie, dwa* różne mikroserwisy, albo nawet dwie* niepowiązane (poza API) ze sobą aplikacje.
(*) Mówię „dwa” w odniesieniu do funkcji nadawca/odbiorca, ale nic nie stoi na przeszkodzie aby było np. kilka instancji tego samego nadawcy, albo kilku różnych odbiorców.
Zanim przejdziemy dalej, jeszcze małe wyjaśnienie, bo wiem z autopsji, że może się u was teraz w głowach kołatać pytanie:
Czym w zasadzie jest „Wiadomość”,
którą wysyłamy do message brokera?
Wiadomość to po prostu obiekt, instancja jakiejś klasy. To może być String i jako taki będzie zrozumiały dla właściwie każdego – od samego pośrednika po dowolnego subscribera. W ten sposób często nadawane są logi.
Ale równie dobrze możemy publikować dowolne inne obiekty, z tym że teraz musimy pamiętać o poprawnym zaimplementowaniu ich serializacji i deserializacji, np. do ciągu bajtowego albo popularnego JSON-a.

Ok, ustalmy co do tej pory wiemy o message brokerach:
- umożliwiają komunikację asynchroniczną
- pośredniczą w wysyłce wiadmości pomiędzy nadawacą (publisher) i odbiorcą (subscriber)
- wiadomością może być dowolny obiekt (albo typ prosty)
- wysłana wiadomość trafia na kolejkę i tam czeka na przekierowanie do odbiorcy (subscriber)
Najpopularniejszymi pośrednikami są obecnie RabbitMQ oraz Apache Kafka, ale nie są to bliźniacze rozwiązania i występują pomiędzy nimi różnice, które mają istotny wpływ na wybór właściwego brokera.

To kiedy Rabbit, a kiedy Kafka?
RabbitMQ jest częstym rozwiązaniem w świecie mikroserwisów, dzięki czemu zmniejsza się ich zależność między sobą (ang. decoupling). Zazwyczaj działa w pamięci operacyjnej, a jego najważniejszym zadaniem jest zarządzanie kolejką na którą trafiają wiadomości – mają tam zostać póki nie trafi się wolny odbiorca zdolny do jej obsłużenia. Po tej operacji owa wiadomość PRZEPADA NA ZAWSZE i zwalnia miejsce dla kolejnych zawodników. Rabbita nie interesuje również kolejność przesyłania wiadomości – nadaje je w myśl zasady „kto pierwszy ten lepszy”.
Apache Kafka, z kolei, według oficjalnej nomenklatury jest nie tyle message brokerem co message streamerem:

Wiadomość wysłana na Kafkę trafia nie na kolejkę ale na tzw. Topic, który swoją konstrukcją bardziej przypomina ArrayListę: wiadomości tam trzymane są zachowywane w systemie partycji/segmentów na fizycznym dysku, a podłączony subscriber ma w swoich metadanych informację na której wiadomości skończył czytanie „feeda” – po wznowieniu swojej pracy zacznie właśnie od tego indeksu na liście. Już przeczytane wiadomości wciąż pozostają na topicu (chyba, że sami ustawimy im „przydatność do spożycia”).
W Kafce kluczową rolę odgrywa zachowanie pierwotnej kolejności w jakiej przychodzą do niej wiadomości, przez co świetnie nadaje się do pracy w środowiskach wielowątkowych.
Na koniec chciałbym jeszcze pokazać przykłady wielkich platform korzystających z obu w/w rozwiązań.
RabbitMQ
- StackOveflow (aktualizacja punktacji użytkowników)
- Twitch (Rabbit pośredniczy w komunikacji za pośrednictwem wbudowanego czatu)
- Dropbox (zarządzanie uploadem i notyfikacjami)
Apache Kafka
- LinkedIn (analiza integracji systemów w czasie rzeczywistym)
- Netflix (streaming video w czasie rzeczywistym)
- Uber (monitoring kierowców i klientów w czasie rzeczywistym)

Nieprzypadkowo trzykrotnie powtórzyłem słowa „w czasie rzeczywistym” w odniesieniu do Kafki. To fraza-klucz definiująca sens istnienia tego rozwiązania.
Tak jak napisałem powyżej – przewagą systemu do Apache nad swoim konkurentem jest zachowanie kolejności otrzymywanych wiadomości, podczas gdy „Królik” wali nimi na ślepo, aby tylko jak najszybciej zwolnić kolejkę i iść na fajrant.
Z drugiej strony nie chciałbym was również zostawiać z przemyśleniami w stylu „Kafka jest lepsza od Rabbita„, bo to dwa różne rozwiązania dwóch różnych problemów. Co jednak nie zmienia faktu, że
RabbitMQ ma niższy próg wejścia niż Kafka
zatem jeśli już chcecie wejść w świat message brokerów, to wygodniej będzie zacząć właśnie od niego.

Na koniec
Tym wpisem chciałbym zakończyć tę część naszego cyklu, która poświęcona była pisaniu kodu. Mając opanowane podstawy Spring Boota, Hibernate’a, a do tego znając dowolnego message brokera możemy już pokusić się o stworzenie naprawdę skomplikowanego programu, gdzie jedynym ograniczeniem będzie nasza wyobraźnia.
Najprawdopodobniej pod koniec serii wrzucę jeszcze co nieco o Elasticsearch (i może coś o Redisie), ale w tym miejscu zamykamy ten segment i otwieramy kolejny, którego tematem przewodnim będzie
DEPLOYMENT
Czyli: mamy już gotową aplikację, teraz pora ją udostępnić światu, w czym pomogą nam:
- Docker
- Kubernetes
- Jenkins
Jako, że tutaj wchodzimy już w trudniejsze zagadnienia z pogranicza DevOps’u z którymi jeszcze nie miałem do czynienia, to bądźcie przygotowani na delikatne obsuwy w terminach kolejnych publikacji.
Dzięki za uwagę i do następnego!
Źródła:
Kurs Kafka YT/POL
Kurs Mikroserwisy + RabbitMQ na Udemy (płatne) POL
RabbitMQ & Apache Kafka – różnice YT/ANG