The service provided by Consileon was professional and comprehensive with a very good understanding of our needs and constrains.

Wolfgang Hafenmayer, Managing partner, LGT Venture Philanthropy

Technical quality of staff offered, capability of performing various project roles, as well as motivation and dedication to the project (... [...]

dr Walter Benzing, Head of development B2O, net mobile AG

Technical quality of staff offered, capability of performing roles of developers, lead developers, trainers, team leaders, architects as wel [...]

Karl Lohmann, Itellium Systems & Services GmbH

Firma Consileon Polska jest niezawodnym i godnym zaufania partnerem w biznesie, realizującym usługi z należytą starannością (...)

Waldemar Ściesiek, Dyrektor zarządzający IT, Polski Bank

The team was always highly motivated and professional in every aspect to perform on critical needs of our startup environment.

Denis Benic, Founder of Ink Labs

Java i Go w jednym stały domu, na jednej malince, nie wadząc nikomu…

Category: Backend Tags: , ,

Java i GO w jednym stały domu...

Geneza

Inspiracją do napisania tego małego artykuliku był zakończony jakiś czas temu u nas w firmie projekt z urządzeniami sieciowymi. Kto był, ten wie, o czym mowa. Poza tym zainspirowany dyskusją z jednym z naszych adminów, który rzuca pomysłami jak karabinek Maxim pestkami na poligonie, stwierdziłem, że podejmę jednak, początkowo zarzucony pomysł, małego monitora różnych parametrów (nie-życiowych) w domu i na jego obrzeżach.

Chodziło w nim (w tym monitorze) jednak bardziej nie o te parametry, które można by było mierzyć środkami bardziej konwencjonalnymi (jak termometr), dzięki którym można by było zaoszczędzić masę czasu, ale o to…no właśnie, chodziło w nim o co?

Główny temat

Chodziło o zmierzenie się z tematem mikroserwisów.

Początkowe założenia

  1. Projekt początkowo zakładał, że system zostanie napisany w jednym z poniższych języków (tu nacisk na “w jednym z”):
    • GO
    • Java
  2. Monitor będzie działał na Raspberry Pi. Chciałem sprawdzić, co mogę, a czego nie mogę na tym małym komputerku. Raspberry Pi stało w domu, kurzyło się od dłuższego czasu, niewiele z niego miałem. Ponadto rozwiązania w chmurze kosztują. A nie chodziło mi o generowanie dodatkowych kosztów w budżecie domowym.

Krótko o początkowych założeniach

GO

To w miarę świeży temat jest w firmie. GO jest jakby takim nie do końca pełnoprawnym dzieckiem Javy, C++ i… pewnej – nazwę to – esencji twórczej jego twórców. Ma naprawdę wiele zalet. Jest szybki. Mało wymagający jeśli chodzi o zasoby i pisze się w nim całkiem przyjemnie (ma jeszcze parę innych zalet, ale ja nie o nich tu przyszedłem pisać). Ale jeśli ktoś próbował napisać mikroserwisy pod consul’a (taki discovery service dla Go), a potem spróbował swojego szczęścia z go-micro frameworkiem (https://github.com/micro/go-micro), który daje nam coś w rodzaju gateway proxy, oraz z jego wymuszonym sposobem pisania kodu w protobuffers, przy pomocy których osiągamy “szczęśliwy poziom”, z którego możemy zacząć komunikację między mikroserwisami po RPC – wie, jakie to może być “przyjemne”. Wcale.

Java

Tu mógłbym pisać same ochy i achy. Spring-boot jest bardzo dojrzały. Pisząc mikroserwisy w springu ma się wrażenie, że… się ich nie pisze. Człowiek przygotowuje sobie Eurekę – jeden mikroserwis. Dalej konfiguruje Zuul’a – kolejny mikroserwis. Trochę dłubie w mikroserwisie do konfiguracji – co by konfigurację dla kolejnych mikroserwisów udostępnić na przykład w repo i przy starcie one sobie odpytają konfigurację u już działającego serwisu konfiguracji. A potem tylko adnotacjami daje się znać swoim autorskim (lub mniej autorskim) mikroserwisom, co by korzystały z @FeignClienta i gdzie ewentualnie jest Eureka. I można pisać kod biznesowy. Czyli mamy jakby:

  • 2 zasadnicze serwisy: Eureka i Zuul
  • 1 opcjonalny serwis: konfiguracyjny serwis, powiedzmy ConfigurationManager

Razem 3. Słownie trzy.

Oczywiście do tego dochodzi jakieś z 8 autorskich (!) mikroserwisów, które coś tam robią. Razem 11. I wszystko byłoby super. Nie trzeba byłoby pisać tego wesołego artykułu.

Pojawił się jednak problem. Startując jedynie Eurekę na Pi, zauważyłem, że ilość wykorzystanej pamięci ze 120 MB (NOOBS i parę dodatkowych narzędzi) skoczyła na “dzień dobry” do 270 MB. Pomyślałem sobie – no dobra, to pewnie przez to, że musiała się odpalić java. Ale jak już się odpaliła, to już będzie hulała. Odpalam następny mikroserwis. Tym razem jest to Zuul. Wykorzystanie pamięci skoczyło z 270 MB do ok 360 MB. Przeszły mnie dreszcze. Odpalam mikroserwis, który zawiaduje konfiguracją. Finał z 470 MB wykorzystanymi. Zostało może 500 MB.
Łatwo policzyć, nawet jedynie szacunkowo, bo – jakby nie było – nie zacząłem jeszcze właściwej implementacji, że jeśli Raspberry Pi 3B+ ma 1 GB RAM, a każdy mikroserwis springowy zasysa mi jakieś 100 MB, to nie starczy mi pamięci na kolejnych 8 mikroserwisów. Sytuacja wyglądała nieciekawie. Zwłaszcza, że ambitnie (i nieświadomie) rzuciłem się na napisanie prostego angularowego klienta (który też mi pożre kolejne 100 MB).

Reasumując, języki miały:

Java Go
świetny discovery service (Eureka) cholernie ciężki w konfiguracji discovery service (consul)
działający, nie wymagający wielkiej konfiguracji, gateway proxy (Zuul) mizernie działający gateway proxy (przez micro framework), którego odpytywanie nie koniecznie działało tak, jak
się ”przypuszczało, że działa” – może po prostu nie wnikałem w szczegóły, ale straciłem na “próby” zrozumienia tego dzieła jakieś 2 dni, bez większego sukcesu
wielki apetyt na pamięć niewielki apetyt na pamięć
przyzwoitą prędkość działania przyzwoitą prędkość działania
doświadczonego developera doświadczonego developera Javy

Mix początkowych założeń

Porównując obie strony “równania” z tablicy powyżej, można by wysnuć wniosek, że fajnie by było dogadać ze sobą pewne elementy Javy i pewne elementy GO. Byłoby idealnie.
Chwila przerwy na wdech.
Które elementy warto dogadać ze sobą, patrząc na tabelkę powyżej?
Chwila przerwy na zastanowienie…
i wydech…

Pewnie wpadliście na to samo, co ja. Nie?

I nie, nie chciałem się poddać i poczekać na Raspberry Pi 4 z 4 GB RAMu. (No dobra, przeniosłem cały projekt w trakcie rozwoju na Pi4, ale tylko dlatego, że skusiła mnie możliwość zainstalowania jenkinsa i zwyczajnie możliwość budowania obrazów dockera dla kilku mikroserwisów właśnie z jenkinsa, klik i po 11 minutach mam działającą apkę. W ciągu tych 11 minut czekania mogę skoczyć do WC, zapalić papierosa, albo przygotować sobie kawę – co w gruncie rzeczy sprowadzało się do oderwania myśli od pomysłów “co by tu jeszcze…popsuć”).

Otóż, mix polegał na tym, żeby:

  • rejestrować mikroserwisy GO w discovery service Eureki
  • wysyłać co jakiś czas (tego wymaga Eureka w trybie, gdzie self-preservation mode jest wyłączony) heartbeaty z GO mikroserwisów do Eureki (coś w stylu: “hej, Eureka, żyję, działam i mam się dobrze, więc mnie nie odrejestruj, dalej tu jestem”)

Dzięki temu “małemu” kroczkowi, nagle, magicznie, springowo, mikroserwisy zarejestrowane w Eurece (serwisy napisane w GO), stawały się widoczne w Zuul proxy. Co przez to zyskałem?

Pierwsza zaleta mix’a

Adres np. endpointu, który nasłuchuje na wyniki z czujnika temperatury:

http://xdevicesdev.home:8181/temperature/

robi się:

http://xdevicesdev.home:8000/api/dispatcher/temperature

Pomyślicie: “no i po …co?”
Ano z prostego powodu.
adres ​http://xdevicesdev.home:8000/api/dispatcher/temperature​ będzie raczej adresem, który się nie zmieni. Chyba że w Zuul powiem, że prefix ma być nie “api” a “hej_ale_fajny_ten_zuul”.
A adres ​http://xdevicesdev.home:8181/temperature​ może się zmienić na http://xdevicesdev.home:8182/temperature

Jak takich endpointów jest więcej…np: ​http://xdevicesdev.home:8103/co​ (od carbon monoxide)
i zaczną się mieszać:
http://xdevicesdev.home:8011/co
http://xdevicesdev.home:8103/temperature
to teraz dojście do ładu i składu w kliencie, który miałby gadać z tym…no po prostu mógłby człowieka lekko szlag trafić.
Ale ten klient to nie główny powód. Podałem go na początku, bo najłatwiej go zrozumieć.

Druga zaleta mix’a

Główną zaletą takiego rozwiązania jest to, że w systemie z mikroserwisami możemy mieć kilka instancji mikroserwisów tego samego typu, np. 3.
Załóżmy, że mamy rack’a z kilkoma raspberry Pi. Jakiś serwis może mieć 3 instancje pod adresami:
http://192.168.1.49:8101
http://192.168.1.50:8101
http://192.168.1.51:8101
Po co to?
Ano jak jeden padnie (przestanie wysyłać heartbeaty do Eureki) z jakiegokolwiek powodu, Zuul o tym będzie wiedział (Zuul gada też z Eureką). Zuul podejmuje decyzję, do której z instancji mikroserwisu wysłać przychodzący request.
Wszystkie instancje gadają z tym samym rabbitem. Więc dla danych nie ma znaczenia, przez który mikroserwis sobie przemkną dalej.
A klient? Klient nie ma pojęcia co dzieje się za Zuulem. Klient zna tylko adresy:
http://xdevicesdev.home:8000/api/dispatcher/temperature
http://xdevicesdev.home:8000/api/dispatcher/co
http://xdevicesdev.home:8000/api/dispatcher/smoke
http://xdevicesdev.home:8000/api/dispatcher/movement
Wygląda to z lotu ptaka (w moim przypadku wielkiego strusia – strusie nie latają, ale mój lata) mniej więcej tak:

Trzecia zaleta mix’a

Walidację usera (token/user auth/cookies) można wykonać na poziomie Zuul’a – przy pomocy filtrów – przez co mikroserwisy nie mają pojęcia o uprawnieniach. Nie “śmiecimy” w nich niepotrzebnymi informacjami. One wykonują szczęśliwie swoje bardzo wąskie zadania. Dlatego nazywają się mikroserwisami właśnie.

Czwarta zaleta mix’a

Kiedy przychodzą requesty z kilku czujników jednocześnie, Zuul może np. podesłać dane z czujnika temperatury do dispatchera pod adresem 192.168.1.49:8101, a z tlenku węgla do dispatchera pod adresem 192.168.1.50:8101. Oba requesty zostaną obsłużone praktycznie równolegle. Temperatura wyląduje w kolejce rabbitowej temperatury, gdzie po drugiej stronie odbierze ją mikroserwis odpowiedzialny za archiwizację danych temperatury, a dane tlenku węgla trafią do kolejki rabbitowej “co” (carbon monoxide), gdzie po drugiej stronie czeka na nie mikroserwis odpowiedzialny za archiwizację danych dla “co”. Pamiętajmy – każdy mikroserwis ma swoją konkretną specjalizację i odpowiada za mały kawałek całego systemu. I robi to dobrze.
Jedynym negatywem takiego rozwiązania, jaki mi przychodzi do głowy, to to, że proxy gateway staje się w takim systemie bottleneckiem. Pada proxy – nie ma nic. Danych nie ma, bo nie znajdą drogi, nie ma klienta, bo nie wie, jak gadać z backendem. Armageddon. Więc o proxy trzeba dbać. Na szczęście takiej klęsce można trochę zaradzić. Mianowicie ładując mikroserwisy (w tym i Zuul’a) do dockera i konfigurując dockerowy kontener z Zuulem, że jak coś się stanie, to robimy restart kontenera.
Znowu…co będzie, jak padnie docker? To by znaczyło, że coś jest nie tak z hostem. Ale można dodać mały skrypt, który będzie odpalany po starcie Raspberry Pi. Czyli robimy restart. Hard reset, czy jakkolwiek. Pi startuje. Skrypt się wykonuje. Odpalany jest docker-compose up. I system działa od nowa.

Studium przypadku z czujnikiem temperatury

Ogólnie projekt można ogarnąć kilkoma ogólnymi skrótami myślowymi:

  1. Jest jednostka pobierająca temperaturę otoczenia
  2. Jest proxy
  3. Jest discovery service
  4. Jest dispatcher
  5. Jednostka mierząca temperaturę, wysyła pomiar do proxy
  6. Pomiar przez proxy trafia do dispatchera
  7. Dispatcher sprawdza, czy czujnik faktycznie należy do systemu i jest zarejestrowany (co by nam jakiś psotnik nie zaczął przesyłać pomiarów, które nie mają sensu)
  8. Po pomyślnej weryfikacji dispatcher wysyła pomiar do kolejki w exchange’u rabbitowym
  9. Po drugiej stronie czeka inny serwis, który zbiera pomiary dla temperatury
  10. Zapisuje je w swojej bazie.

Teoretycznie prosta sprawa. W bardzo uproszczonej formie (z odrobiną pikanterii), system mógłby wyglądać jakoś tak (z lotu strusia):

Ale…
Kiedy zaczyna się wchodzić w kolejne punkty bardziej szczegółowo, zaczynają się mnożyć nowe pytania i nowe problemy.
Głównym dylematem przez jakiś tydzień pracy, było samo rejestrowanie GO mikroserwisów w Eurece.

Rozgryzanie Eureki

Eureka domyślnie śmiga na porcie 8761.
Można to skustomować. Nie ma z tym problemu, ale dajmy jej hulać tam, gdzie jej miejsce.
Jak otworzymy główną stronicę Eureki, otrzymamy coś takiego (tu już śmigają sobie zarejestrowane mikroserwisy GOłowe):

Czyli adres był: [server]:8761 i już. W moim przypadku server == xdevicesdev.home (tak to się przedstawia mi w domu mój Raspberry Pi po mDNS).
Grzebiąc trochę w dokumentacji, znalazłem, jak dobrać się do pełnych informacji o instancjach mikroserwisów.
Zaczynałem od jednego zarejestrowanego serwisu w Eurece – od proxy. Nie żeby pozostałe, które są na obrazku powyżej jakoś same magicznie się rejestrowały. Nie nie. Na liście jedynie proxy był springowy. I jedynie on mógł podpiąć się automatycznie do Eureki. Resztę trzeba było przygotować.
No, ale wracajmy do dokumentacji (xdevicesdev.home == server)
Jak wpiszemy adres xdevicesdev.home:8761/eureka/apps to dostaniemy listę wszystkich zarejestrowanych aplikacji w formie xml’a:

Drążąc dalej, możemy dostać tylko informacje dla konkretnej aplikacji, wpisując np.:
http://xdevicesdev.home:8761/eureka/apps/proxy​ :

Nie jest to doskonałe, ale prawie już jesteśmy w domu. Teraz, odpalmy ten sam URL, ale z postmana, przy czym dodajmy w headerach “Accept” = “application/json” :

Teraz informacja o zarejestrowanej apce jest trochę bardziej czytelna. Jest w JSONie.
Na podstawie tych “meta-danych” o zarejestrowanym proxy, trzeba zrekonstruować ticket, który jest wysyłany do Eureki.

Mało tego, po rejestracji trzeba wysyłać w zdefiniowanym okienku czasowym, heartbeaty, żeby dać znać Eurece, że nasz serwis jeszcze działa. To jest opcjonalne, bo tryb self-preservation jest włączony domyślnie (nic nie trzeba robić). Problem polega na tym, że kiedy mikroserwis zarejestrowany w Eurece przestaje działać, Eureka o tym nie wie. Zakłada, że on tam jest. I przez to “ogłupia” proxy. Kiedy proxy spróbuje wysłać dane do mikroserwisu, który jest wyłączony – klops.
Głównie dlatego w tym małym monitorze wyłączyłem tryb self-preservation w Eurece. I wymusiłem przez to na mikroserwisach zgłaszanie się co jakiś czas do Eureki i raportowanie swojego stanu.

Podsumujmy:

  1. Eureka oczekuje rejestracji
  2. Eureka oczekuje heartbeatów

Zgodnie z konwencją, rejestracja jest traktowana jako “dodawanie” elementu (POST), natomiast renewal to aktualizacja stanu (PUT).
Punkt pierwszy realizujemy śląc request POST na adres http://xdevicesdev.home:8761/eureka/apps/[instance.app]
Drugi punkt realizujemy śląc request PUT na adres http://xdevicesdev.home:8761/eureka/apps/[instance.app]/[instance.id]
W body obu requestów wysyłamy – request ticket.
I praktycznie wszystko rozchodzi się o to, żeby ten request ticket jakoś stworzyć. Wersja Greenwich.SR1 spring’a akceptuje poniższą strukturę (po serializacji to jest po prostu json), którą trzeba wypełnić jakimiś wartościami:

type Instance struct {
   InstanceId       string         `json:"instanceId"`
   HostName         string         `json:"hostName"`
   App              string         `json:"app"`
   IpAddr           string         `json:"ipAddr"`
   VipAddress       string         `json:"vipAddress"`
   SecureVipAddress NullableString `json:"secureVipAddress"`
   Status           string         `json:"status"`
   Port             Port           `json:"port"`
   SecurePort       Port           `json:"securePort"`
   HomePageUrl      NullableString `json:"homePageUrl"`
   StatusPageUrl    string         `json:"statusPageUrl"`
   HealthCheckUrl   NullableString `json:"healthCheckUrl"`
   DataCenterInfo   DataCenterInfo `json:"dataCenterInfo"`
   LeaseInfo        LeaseInfo      `json:"leaseInfo"`
}

type DataCenterInfo struct {
   Class string `json:"@class"`
   Name  string `json:"name"`
}

type LeaseInfo struct {
   EvictionDurationInSecs int `json:"durationInSecs"`
}

type Port struct {
   Port    int  `json:"$"`
   Enabled bool `json:"@enabled"`
}

 

Przy czym kluczowymi elementami są zaznaczone poniżej (dla przykładu wartości z dispatchera):

Krótko o polach:

Pole Krótki opis
InstanceId Identyfikator instancji. Mega ważny element. Taki odcisk palca konkretnej instancji. Musi być unikalny. Można wrzucić tu UUID. Ale ten nie wiele nam powie, jakbyśmy chcieli zerknąć na listing serwisów zarejestrowanych w Eurece. Konwencją jest, żeby budować go z adresu IP, nazwy apki i portu, na którym serwis jest dostępny.
HostName Kolejny ciekawy atrybut. Z tego co zauważyłem jest wypełniany w serwisach springowych (tych co działają out-of-the-box) adresem IP. Drobna uwaga – nie mogą to być adres localhosta, co dodatkowo skomplikowało sprawę. Trzeba było napisał drobny tool, który przeszukiwał adresy dostępne na maszynie, na której miał działać serwis i zwracać IP, który odpowiadał temu wymaganiu.
App Dosyć łatwy do wypełnienia. Jest to po prostu nazwa serwisu.
IpAddr Jak sama nazwa wskazuje – IP, na którym hula serwis.
vipAddress Mega istotny element. Tutaj rozkładam ręce. Próbowałem różnych kombinacji: ip, uuid, zlepek różnych nazw (ip:app, ip:app:port). Sukces w działaniu (rejestracji serwisu), był jedynie gwarantowany jak vipAddress == App. Z czego to wynikało nie potrafię niestety za bardzo odpowiedzieć, ale jeśli ktoś wie, to chętnie się dowiem, bo jest to nadal dla mnie zagadka, ale nie tak istotna, żeby tracić na nią zbyt wiele czasu.
Status Ciekawy parametr. Ma kilka stanów. Można by się bawić w workflow i wysyłać podczas rejestracji status “STARTING”, potem przy wysłaniu pierwszego heartbeatu “UP” i w kolejnych następnych już “UP”. Rzecz w tym, że mikroserwisy GO, jak wszystko jest dobrze przygotowane, wstają wyjątkowo szybko (!). I nie było sensu bombardować Eureki zbędnymi komunikatami. Więc status w przypadku register ticketa zhardkodowałem na “UP”.
Port Bardzo ciekawy konstrukt. Popatrzcie na type tagi jsona.
„port”​: {
​”$”​: ​8000​, ​”@enabled”​: ​”true”
},
DataCenterInfo Struktura, która ma dwa atrybuty. I oba “muszą” być ustawione na konkretne wartości. Tego też za bardzo nie mogłem rozkminić dlaczego. O ile atrybut Class byłbym w stanie zaakceptować, że musi być konkretny, to atrybut Name wydawał mi się nieco bardziej kustomizowalny. Ale nie jest. Musi być “MyOwn”. Jak jest coś innego, np. nazwa appki, to nie przejdzie.
LeaseInfo Kolejna “ciekawa”, tajemnicza, struktura, w której jedynym istotnym elementem jest ​EvictionDurationInSec.​ Ciekawa sprawa z tym jest. W dokumentacji znaleźć można o tym niewiele. Sama struktura LeaseInfo miała wiele przeobrażeń i wyglądała inaczej we wcześniejszych wersjach (wcześniejszych do Greenwich.SR1).
Ale, z tego co zauważyłem w trakcie prób i błędów z pracą z Eureką, atrybut EvictionDurationInSec definiuje okres “lizingowania” serwisu. Jak ten okres jest stosunkowo krótki (np. 5 sekund) a na przykład mamy uruchomiony serwis w trybie debug, bo akurat coś się sypnęło i chcemy sprawdzić, w którym miejscu dokładnie coś się sypnęło, to po zdebugowaniu (które trwa zazwyczaj trochę więcej czasu, niż 5 sekund, no chyba że ktoś jest z tej samej esencji kosmicznej jak niektórzy z CPL, których podejrzewam o możliwości debugowania w głowie w trybie real time), serwis przestaje być “zarejestrowany” w Eurece.
Wygląda to tak, jakby Eureka “wypożyczała” serwis jakiemuś procesowi na dokładnie 5 sekund. Jeśli request trwa dłużej (jest trochę bardziej wymagający), to możemy spodziewać się, że serwis już się nie pojawi w Eurece. Chyba że zaimplementujemy obsługę takiego zdarzenia. Na przykład: heartbeat w przypadku nie udanego podesłania ticketa zwraca 404. Skoro Eureka wywaliła z rejestru “zlizingowany” serwis, który “nie wrócił” w umówionym czasie, to przy schedulerze wysyłającym heartbeat otrzymamy 404 w odpowiedzi z Eureki. Można to wykorzystać do re-wysłania rejestracji (POST pod inny endpoint) i kontynuowania dalej wysyłania heartbeatów.
I teraz uwaga. Używając GO jako języka dla core mikroserwisów zyskujemy tu pewien element zaskoczenia Eureki. Konstruując odpowiednio mikroserwis, możemy sprawić, że ewiktowanie naszego serwisu z rejestru będzie bardzo krótkie, lub praktycznie żadne:

  • GO ma coś, co się nazywa goroutines
  • tworząc goroutine dla schedulera, zyskujemy ciągłe
  • informowanie Eureki, że serwis gdzieś tam jest (heartbeaty), niezależnie od tego, czy coś aktualnie procesujemy, czy nie w danym konkretnym serwisie
  • tworząc goroutine dla procesowania samego requesta – zyskujemy to, że serwis jest dostępny – prawie od razu – do kolejnego request’u.

Zaznaczam, że gorutyny to nie to samo, co wątek. Debugowanie mikroserwisu zatrzymuje mikroserwis. Tu nie pomoże, że mamy gorutyny. Serwis zostanie wyrejestrowany przez Eurekę, jak będziemy debugować “nieco” dłużej.

Proces rejestracji można przedstawić schematycznie tak, w celach lepszej wizualizacji o co tam chodzi:

  1. Sytuacja startowa jest następująca:
    • po jednej stronie mamy service discovery (Eureka, która jest mikroserwisem, ale nie musi się sama w sobie rejestrować. Jest swoistego rodzaju absolutem w systemie (!). Prawdą, która jest. )
    • po drugiej mamy proxy gateway. Też mikroserwis. Springowy.
    • po środku, jak szynka w kanapce, są nasze GO-mikroserwisy.

  2. Kamera – akcja
    • serwisy zaczynają rejestrację (POST z ticketem rejestracji na adres [server]:8761/eureka/apps/[INSTANCE.ID]
    • INSTANCE.ID jest inne dla każdego z rejestrujących się serwisów

  3. Service discovery “udostępnia” meta dane o zarejestrowanych serwisach.
    • głównym klientem tych metadanych jest oczywiście gateway proxy, który wykorzystuje te informacje i “transformuje” je na bardziej przyjazne adresy.

  4. Serwisy moga od tej chwili komunikować się nie po dokładnym adresie, a przez proxy gateway:
    • jedyne co muszą znać to suffix endpointu, do którego chcą wysyłać dane
    • !!! ( oczywiście to jest alternatywa komunikacji między serwisami. Znacznie lepszym jest komunikacja przez rabbit’a, lub inną kafkę. Wtedy serwisy nie muszą znać niczego, tylko kolejkę, do której trzeba wrzucić przeprocesowany obiekt. Nie trzeba czekać na odpowiedź, można zająć się kolejnym procesowaniem, lub odpoczynkiem – przejściem w stan idle.)

  5. Wyobraźmy sobie sytuację, kiedy mamy dwa dispatchery (dwie instancje tego samego typu):
    • załóżmy na razie, że nie korzystamy z gorutyn

  6. W momencie ataku przez czujniki temperatury:
    • różne okresy uśpienia (czyli różne okresy przebudzenia)
    • duża ilość czujników

    • proxy może przy pomocy load balancera przekierowywać requesty do aktualnie aktywnej instancji danego typu (pamiętacie status request ticketa?)
    • jeśli serwis obsługuje dodatkowo wysyłanie heartbeatów w zależności co się dzieje z samym serwisem, to może w momencie rozpoczęcia procesowania requestu z czujnika, wysłać status “BUSY” do Eureki. proxy będzie wiedziało, że konkretna instancja nie obsłuży requesta, więc prześle go do pierwszego “wolnego” serwisu.
    • w przypadku mikroserwisów w GO powyższa sytuacja może zdarzyć się naprawdę rzadko (chociaż nie jest całkowicie wykluczona), jeśli odpowiednio zgramy ze sobą scheduler’a i gorutyny obsługujące procesowanie requesta.
    • można zaimplementować oba mechanizmy, co będzie rozwiązaniem idealnym. Niestety budżetowo nie byłem w stanie zweryfikować większego obciążenia aplikacji. Do testów wykorzystałem 5 prawdziwych czujników temperatury i 3 czujniki z symulatora. Bombardowanie odbywało się w różnych odstępach czasu (okresy uśpienia były odpowiednio:
      • symulator 1 – 10 sekund
      • symulator 2 – 20 sekund (miał failować na walidacji, sensor nie był zarejestrowany w systemie)
      • symulator 3 – 30 sekund
      • czujnik 1 – 10 sekund
      • czujnik 2 – 15 sekund
      • czujnik 3 – 30 sekund
      • czujnik 4 – 60 sekund
      • czujnik 5 – 20 sekund
        • Przy takiej konfiguracji, na 100 próbek z każdego czujnika (symulator też był czujnikiem), wszystkie 100 próbek dla każdego czujnika było zapisane i przeprocesowane przez dispatchera.
        • Nie uwzględniłem procesowania mikroserwisu temperaturearchive, który zbierał przeprocesowane measurements z rabbita, bo nie było to konieczne. Measurements siedziały w kolejce i temperaturearchive zbierało je z kolejki jeden po drugim i zapisywało w bazie.

 

I tak oto mamy krótką historię mixowania mikroserwisów napisanych w Javie i GO. Temat jest ciekawy i mimo swojej „egzotyczności” (mikroserwisy na Raspberry Pi), monitor spełnia swoje zadanie i ma nawet praktyczny aspekt. Nawet w przypadku takiego prostego pomiaru jakim jest temperatura.

Znalezienie optymalnej pory i miejsca do suszenia ciuchów (!) nawet w październiku wydaje się być dużo zabawniejsze, kiedy poobserwuje się nagrzewanie miejsc w odpowiednich porach dnia, z odpowiednich czujników (jakoś między 14:30 a 16 najbardziej nagrzewa się jedno miejsce na balkonie):

Ale nie ma co ściemniać, czujnik znajdował się obok schodów, po zewnętrznej stronie okna na ogród. Kiedy promienie słońca padają na szybę pod odpowiednim kątem, mogą mocno „podrasować” pomiar czujnika, nagrzewając wodoodporny box, w którym ów czujnik się znajdował. Tym tłumaczyłbym „dosyć” wysoki pomiar 23.10, sięgający – ponad 36 stopni, co raczej w październiku w naszej strefie klimatycznej normalnym nie jest.

Jasne, nie ma to jak zmysły, np. 24.10 padało. Tego czujnik temperatury nie powie, ale pomiar był już „realniejszy” – zachmurzone niebo nie przepuszczało promieni słonecznych, przez co szyba nie mogła podgrzać czujboxa.

Niestety, już 26.10, mimo, że chmur nie było, temperatura w tym miejscu nie przekroczyła już magicznej trzydziestki… Będzie coraz zimniej… 🙂

Pozdrawiam wszystkich gorąco.

Max


Maksymilian Żurawski

Software developer.

Programista Javy i baz danych. Namiętny bugfixer. Interesuje się automatyzacją wszystkiego, co możliwe i pisaniem drobnych skryptów w groovym i bashu. W wolnym czasie tata, książkozaur (horror, s-f, fantasy) i kinoman (tak samo jak książki), aktywny, ale tylko hobbystycznie, użytkownik blendera 3d.


Tags: , ,

Comments

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Trwa ładowanie