Analiza przypadków użycia Hazelcast w środowisku rozproszonym

Wykorzystanie Hazelcast w projekcie niesie za sobą nowe wyzwania techniczne, z którymi musieliśmy się zmierzyć. W tym artykule postanowiliśmy omówić najciekawsze z nich. Dowiecie się jak użyć Hazelcasta do komunikacji pomiędzy mikroserwisami w oparciu o eventy oraz jak wygląda inicjalizacja Hazelcasta z różnych źródeł danych.

analiza hazelcast

Piotr Dogoda. Backend java developer szczególnie zainteresowany systemami rozproszonymi i reaktywnymi. W BNY Mellon zajmuje się modernizacją wewnętrznych systemów do przesyłania informacji oraz tworzeniem aplikacji. Prywatnie fan koszykówki i cyfrowych gier karcianych.

 

analizowanie hazelcast

Mariusz Mikołajczak. Java developer z zamiłowaniem do DevOpsowania. W BNY Mellon zajmuje się implementacją nowych funkcjonalności, integracją z bogatym ekosystemem, a także wprowadzaniem nowinek technologicznych do projektu. Wiecznie głodny nowych wyzwań, zarówno w życiu zawodowym jak i prywatnym. W wolnym czasie eksploruje Bory Dolnośląskie lub przenosi ciężary w przydomowej siłowni.


Użycie Hazelcasta do komunikacji pomiędzy mikroserwisami w oparciu o eventy

Rozproszona architektura oparta o mikroserwisy stała się niepisanym standardem przy budowie bardziej złożonych aplikacji. Oprócz licznych zalet, niesie ona jednak ze sobą pewne wyzwania – za jedno z nich można uznać przesyłanie informacji między serwisami tak, żeby każda instancja serwisu otrzymała daną wiadomość.

Dlaczego warto wybrać Hazelcast?

Kiedy chcemy wysłać wiadomość do naszego serwisu w trybie broadcast, tzn. tak, aby dotarła do wielu odbiorców – w tym przypadku wszystkich instancji naszego serwisu – możemy użyć jednego z gotowych, wyspecjalizowanych rozwiązań jak Apache ZooKeeper czy Spring Cloud Bus. Niosą one jednak ze sobą pewien dodatkowy nakład pracy związany z instalacją i konfiguracją lub wymagają dodatkowych komponentów (w przypadku Spring Cloud Bus potrzebna jest Kafka lub RabbitMQ).

Alternatywnie, jeśli korzystamy już z Hazelcasta, możemy użyć jego systemu eventów. Pozwoli nam to w prosty sposób stworzyć rozwiązanie, które zsynchronizuje mikroserwisy poprzez przesyłanie im wiadomości na zasadzie publish-subscribe. W ten sposób, opublikowaną wiadomość otrzymają wszystkie instancje serwisu, które zasubskrybują dany typ eventu.

Implementacja

1. Publikowanie eventu

Podstawową strukturą danych w Hazelcast są rozproszone mapy. Dostępna jest dla nich funkcjonalność publikowania eventów przy interakcjach takich jak dodawanie, modyfikowanie czy usuwanie elementów.

W tym artykule użyjemy dwóch typów eventów z Hazelcasta: EntryEventType.ADDED i EntryEventType.UPDATED publikowanych odpowiednio przy dodaniu i modyfikacji wpisu w mapie.

Przechodząc do samego publikowania eventów – będziemy potrzebować mapy parametryzowanej typem danych, które chcemy publikować. Wykorzystamy mapę typu <CommandType, String> – pierwszy określający typ wysyłanego polecenia, od którego zależeć będzie w jaki sposób przetworzymy event, a drugi to wartość do przetworzenia.

Mamy tutaj pełną dowolność implementacji. Nic nie stoi na przeszkodzie, aby utworzyć i zarejestrować listenery dla kilku map, sparametryzowanych w inny sposób, w których obsługiwać będziemy eventy różnego typu.

Tworzymy więc taką mapę:

Teraz, aby opublikować event typu ADDED lub UPDATED dla tej mapy, wywołujemy na niej jedną z metod, która doda lub nadpisze element jak np: put, set lub executeOnKey.

Po wywołaniu tej linii, wszyscy subskrybenci danej mapy otrzymają event typu ADDED, jeśli w mapie nie było wcześniej wartości dla tego klucza, a w przeciwnym wypadku, typu UPDATED.

2. Odbiór eventu

Do przetwarzania eventów publikowanych w pierwszym kroku, użyjemy interfejsu MapListener, a dokładniej jego dwóch rozszerzeń:

  • EntryAddedListener<K, V> – otrzymamy wiadomość po dodaniu nowej wartości do mapy (EntryEventType.ADDED),
  • EntryUpdatedListener<K, V> – otrzymamy wiadomość po zmodyfikowaniu istniejące wartości w mapie (EntryEventType.UPDATED),

Parametry K i V to oczywiście typ klucza i wartości w mapie.

Przykładowy event listener wykorzystujący powyższe interfejsy będzie więc wyglądał następująco:

3. Rejestracja event listenera

Ostatni krok, to zasubskrybowanie event listenera do naszej mapy. Aby to zrobić, każda instancja serwisu zainteresowana odbieraniem wiadomości powinna wywołać metodę addEntryListener na rozproszonej mapie Hazelcasta.

ZOBACZ TEŻ:  Analiza dźwięku i czasu pogłosu w pomieszczeniach z wykorzystaniem Python

Podsumowanie

Stworzony w ten sposób system możemy wykorzystać, żeby zsynchronizować rozproszoną aplikację poprzez publikowanie przez Hazelcast informacji, które dotrą do wszystkich instancji serwisu. Pozwala to na dynamiczne zmiany w konfiguracji działających serwisów bez konieczności ponownego ich wdrażania.

Inicjalizacja Hazelcasta z różnych źródeł danych

Czasami chcielibyśmy, aby w momencie inicjalizacji Hazelcasta zostały do niego załadowane dane. Dzięki temu, dostęp nich będzie bardziej wydajny niż gdyby trzeba było je pobrać z persystentnego źródła. W poniższej analizie przyjrzymy się, jak należałoby do tego podejść w sytuacji, gdy jest kilka źródeł danych.

Jak zbudowany jest klaster Hazelcast?

Hazelcast IMDG (In Memory Data Grid) to cache, który używany jest do przechowywania danych w pamięci głównej (RAM), dzięki czemu dostęp do nich jest szybki, około rząd wielkości szybszy niż w przypadku przechowywania ich na dyskach SSD. Dane te są rozprzestrzenione i zreplikowane na współpracujące ze sobą instancje Hazelcasta, które razem tworzą klaster. Każdy node w klastrze przechowuje pewną część danych.

Gdyby z jakiegoś powodu (z powodu awarii serwera, braku prądu itp.) taka instancja została zatrzymana dane zostałyby bezpowrotnie utracone.

instancje hazelcast

Rys.1. Architektura klient-serwer typu embedded. Źródło: smallbusiness.chron.com

Dlatego tworzenie kopii zapasowych i przechowywanie ich na innych instancjach jest niezbędne. Hazelcast umożliwia wykonanie operacji na lokalnej porcji danych – jest to kluczowe i zostanie poruszone w dalszej części artykułu.

Rozproszona mapa – interfejs IMap

IMap, prawdopodobnie najczęściej wykorzystywana struktura danych Hazelcasta, rozszerza Javową klasę ConcurrentMap, a pośrednio także klasę java.util.Map. W odróżnieniu od klasycznej mapy, znanej każdemu developerowi piszącemu w językach działających w oparciu o JVM, IMap jest rozproszoną strukturą danych. Mapa ta zostaje podzielona na partycje, które są równomiernie rozdystrybuowane pomiędzy node’y klastra.

spring boot app hazelcast

Przy dodawaniu wpisu do mapy, następuje wywołanie funkcji hashującej na kluczu, a na podstawie uzyskanej wartości następuje przyporządkowanie pary klucz-wartość do odpowiedniej partycji.

MapStore, MapLoader – interfejs będzący pomostem pomiędzy Hazelcastem a persystencją

Hazelcast umożliwia wczytywanie/zapisywanie danych z/do rozproszonej mapy np. z/do bazy danych. Aby to osiągnąć, należy zaimplementować odpowiednio interfejs MapLoader lub MapStore. Gdy następuje wywołanie metody IMap.get() i żądana wartość nie znajduje się w mapie, wywoływana jest metoda load() bądź loadAll() z wymienionych wcześniej interfejsów. Wczytana wartość trafia do Hazelcasta, gdzie zostaje zapisana i żyje do momentu aż zostanie jawnie usunięta lub wygaśnie (evicted). Można zauważyć, że wspomniany mechanizm łatwo wykorzystać do inicjalizacji Hazelcasta danymi, które zostały zapisane w sposób trwały.

Kontrakt metod load(), loadAll(), loadAllKeys()

Wygodnym rozwiązaniem jest inicjalizacja pamięci cache danymi pobranymi ze źródła danych, takich jak bazy danych, czy zewnętrzny serwis. Naturalnym sposobem zrealizowania tego jest zaimplementowanie metod: loadAllKeys(), loadAll() oraz load(), które oferują interfejsy MapStore oraz MapLoader. Tryb wczytywania danych określa enumeracja InitialLoadMode – przyjmuje ona dwie wartości: EAGER lub LAZY:

  • InitialLoadMode.EAGER oznacza, że wszystkie partycje zostaną załadowane w momencie wystąpienia pierwszej interakcji z mapą (konkretnie w momencie pierwszego wywołania metody HazelcastInstance.getMap(‘’map-name’’)),
  • InitialLoadMode.LAZY oznacza, że dane ładowane są partycja po partycji, w momencie wystąpienia pierwszej interakcji z daną partycją.

Metoda loadAllKeys() nie przyjmuje żadnych argumentów oraz zwraca Iterable<K>, gdzie K, to typ wskazujący na klucz mapy. Hazelcast wywołuje tę metodę na jednym ze swoich node’ów, następnie dystrybuuje odczytane klucze pośród wszystkich uczestników klastra. Każdy uczestnik wywołuje wtedy metodę MapLoader.loadAll(keys) i odczytane wpisy zostają umieszczone w IMap’ie.

Zapraszamy do dyskusji

Patronujemy

 
 
More Stories
Od „informatyka” do CEO firmy w Teksasie. Historia Damiana Nowaka