Mikroserwisy – wstęp #1

Created with Sketch.

Mikroserwisy – wstęp #1

Zanim zaczniemy …

Jest to pierwszy z cyklu artykułów wprowadzających w świat architektury rozproszonej – mikro usługowej. Część zawartych tu informacji to moje spostrzeżenia i zachęcam do ich kwestionowania / mocnego przemyślenia.

Zacznijmy od małego przykładu

Załóżmy, że jesteś właścicielem sklepu internetowego. W bazie danych masz kilka tabelek/kolekcji. Jedną z nich przechowuje informacje o produkcie (tytuł, opis, jakieś atrybuty, cenę etc), inna to np zamówienia, faktury, dane użytkowników itd.  Z biegiem czasu, kolejną udaną akcją marketingową i nowym pakietem unikalnych trafionych funkcjonalności Twój sklep się rozrasta, generuje większe przychody- odnosisz sukces. Masz więcej klientów i coraz więcej produktów w asortymencie. W naturalny sposób rośnie też liczba zamówień. Niemniej zaczynasz zauważać, że w szycie zakupowy wraz ze wzrostem liczby odwiedzających Twój sklep przestaje działać wydajnie.

Rosną czasy odpowiedzi, niektórzy klienci w ogóle nie mogą wejść na stronę sklepu, inni nie mogą sfinalizować transakcji. Różne obszary sklepu szwankują, niektóre mocniej inne „w miarę” działają. Takie działanie sklepu w naturalny sposób przekłada się na szybki spadek zadowolenia kupujących i zwiększoną liczbę porzuconych transakcji – czytaj stary finansowe. Ratując sytuację kupujesz mocniejszy serwer aplikacyjny i bazodanowy (skalowanie wertykalne) i migrujesz aplikacje, co pomaga tylko na chwile… Każdego wieczora liczysz się z globalną awarią, która notabene coraz cześciej występuje w postaci komunikatu dla wszystkich klientów „500 ups coś poszło nie tak”.

Następny krok to próba „zwiększenia” liczby serwerów (skalowanie horyzontalne), zdecydowanie bardziej skuteczne rozwiązanie jednak dużo droższe i wymagające bardzo dużo pracy (m.in. rozdzielenie ruchu, współdzielenie sesji). W krótkim czasie wąskim gardlem Twojego sklepu staje się baza danych dokładasz więc warstwę pomiędzy w postaci super szybkiego cache i odkrywasz zupełnie nową klasę problemów, o których do nie dawna nie miałeś pojęcia.

Powyższy przykład, oczywiście mocno przerysowany, ilustruje jak względnie mała aplikacja przestaje działać wydajnie pod wpływem naturalnego ruchu i kliku nowych funkcjonalności.

Zacznijmy zatem od podstaw …

Czym jest aplikacja monolityczna ?

Aplikacja monolityczna, czyli duży nie podzielny system, który realizuje większość (często wszystkie) istotnych funkcjonalności biznesowych. W dużym uproszczeniu  taka aplikacja składała się zazwyczaj z kilku niepodzielnych elementów:

  • Część odpowiedzialna za komunikacje z użytkownikiem (warstwa frontend), strona kliencka odpowiedzialna za interakcje z użytkownikiem
  • Część odpowiedzialna za utrwalanie danych (warstwa persystencji) – baza danych, cache
  • Część odpowiedzialna za przetwarzanie danych, żądań użytkownika, logikę biznesową itd (warstwa back-endu)
architektura monolityczna
po przeskalowaniu *

Zalety

  • komunikacja – zazwyczaj komunikacja odbywa się pomiędzy komponentami poprzez wywołanie metod w ramach pojedynczego procesu, to sprawia że jest ona bardzo szybka i praktycznie niezawodna
  • security – dużo łatwiej zagwarantować odpowiedni poziom bezpieczeństwa, przy pojedynczej aplikacje;
  • transakcyjność (ACID)- łatwo zagwarantować transakcyjność operacji w obszarze pojedynczego systemu
  • złożoność architektury systemu – w zasadzie to jej brak

Jakie są największe wady tego typu architektury ?

Moim zdaniem największymi bolączkami aplikacji monotlitycznych(*) są : 

  • Skalowalność – można skalować tylko „cały system”, a nie wybrany jego fragment, który ma problemy wydajnościowe
  • Odporność na awarie – w przypadku błędu lub problemów infrastrukturalnych czy wydajnościowych jednej funkcjonalności nierzadko tracimy możliwość skorzystania z całego systemu. Wyobraź sobie sytuację, w której na naszym sklepie przez błąd w kodzie odpowiedzialnym np za wyświetlanie newslettera nie można sfinalizować żadnej transakcji.
  • Koszt  implementacji nowych i rozwoju istniejących funkcjonalności – w złożonym systemie ciężej przeanalizować wszystkie zależności i zlokalizować konkretne miejsce które trzeba zmodyfikować. Wymaga to większej ilość testów regresji (funkcjonalnych, bezpieczeństwa wydajnościowych), które należy wykonać. Znacząco wpływa to na ilość pracy jaką należy wykonać w celu wprowadzenia zmiany
  • Wysoki koszt utrzymania (maintenance)- uaktualnienie istotnych bibliotek, framework’u czy wersji języka implikuje zmianę „wszędzie”, w każdej nawet najmniejszej funkcjonalności. Ryzyko błędu lub konieczności przepisania dużej części systemu jest znaczące  
  • Bardziej ryzykowny proces wdrożenia (deployment) – podczas wdrażania monolitu wszystkie zmiany wdrażane są w tym samym momencie, wymaga to zaangażowania wielu zespołów (developerskich, bazodanowych infrastrukturalnych dev-ops), synchronizacji prac i szczegółowej wiedzy o funkcjonalnościach, które są wdrażane. Sam akt wdrożenia zależy oczywiście od jego implementacji, niemniej w jednej chwili aktualizujemy nasz cały system. W przypadku konieczności szybkiego wycofania wdrożenia ze względu na problem z jedną z wdrażanych funkcjonalności, wycofane zostanie całe wdrożenie.
  • Ograniczenie autonomii zespołu – cieżko pozwolić na dobór właściwej technologii (języka, bazy danych itd) w momencie gdy cały projekt powstaje w już w jakimś stosie technologicznym
  • Wdrożenie nowych członków do projektu – złożoność tego typu aplikacji jest zdecydowanie większa, duża ilość kodu, większa ilość zależności znacząco wpływa na szybkość poznania aplikacji czy zrozumienie logiki biznesowej nawet jeżeli posiadamy dobre testy czy dokumentacje co wprost przekłada się na czas jaki jest niezbędny aby nowy członek zespołu zaczął pracować efektywnie
  • i pewnie wiele innych o które mi nie przyszły do głowy

* celowo nie wspominam o jakości kodu, architekturze aplikacji itd. ponieważ niezależnie czy to bedą mikro usługi czy aplikacja monolityczna, kod może być napisany lepiej lub gorzej, być silnie powiązany i niespójny lub nie, może posiadać lub nie dobre testy itd.

Mikro usługi (micro service)

Mikro usługi (micro service) to nic innego jak styl architektoniczny, w którym potrzeba biznesowa realizowana jest w przy pomocy małych wyspecjalizowanych, niezależnych, realizujących konkretne zadania systemów komunikujących się ze sobą sieciowo. 

Czyli parafrazując zamiast mieć jeden system do wszystkiego lepiej mieć kilka wyspecjalizowanych.

Jakie są zalety takiej architektury? 

Moim zdaniem, do głównych zalet takiej architektury bez wątpienia możemy zaliczyć:

  • Skalowalność (scalability) – łatwo możemy skalować dokładnie te części systemu które odpowiadają za funkcjonalności mocniej obciążone,
  • Stabilność / niezawodność– w architekturze mikro usługowej minimalizowane jest ryzyko wystąpienia błędów uniemożliwiających działanie całej aplikacji na rzecz nie działania poszczególnych funkcjonalności tj. w przypadku gdy awarii ulega jedna funkcjonalność (np. wysyłka notyfikacji) nie ma to wpływu na cały system
  • Wydajność (efficient) – dzięki łatwej skalowalności i możliwości doboru właściwych technologii do problemu możemy osiągnąć niezbędny poziom wydajności
  • Szybkość dostarczania nowych funkcjonalności w ramach części systemu (time to market)- dzięki ściśle określonej odpowiedzialności złożoność aplikacji jest mniejsza
  • Jakość – w mniejszej aplikacji łatwiej osiągnąć wysoką jej jakość czyli zaczynając od spójności, czytelności, wydajności, „czystości” kodu aż po dokumentacje, logi i monitoring aplikacji
  • Testowalność – testy małej aplikacji o ściśle określonej odpowiedzialności są zdecydowanie szybsze i efektywniejsze a niżeli całego systemu, łatwiej i szybciej można zweryfikować czy wprowadzone zmiany nie spowodowały niepożądanego efektu
  • Optymalne wykorzystanie technologii – podczas pisania wyspecjalizowanego serwisu znając wymagania, łatwo możemy dobrać odpowiednie technologie
  • Niski koszt refactoringu funkcjonalności – w przypadku gorzej napisanego serwisu, popełniania poważnego błędu u podstaw (np zły dobór technologii) nie ma on „znaczącego” wpływu na inne obszary aplikacji, małym kosztem możemy wymieć / przepisać i wymienić gorszy „klocek”

Czy mikro serwisy maja wady ?

Było by zbyt prosto gdyby istniały rozwiązania pozbawione wad, pasujące wszędzie i do każdego problemu. Taki „złoty młotek”, którym możemy naprawić wszystko jednym magicznym uderzeniem. Zatem i architektura mikro-usługowa takowe posiada:

  • ecosystem – stworzenie środowiska uruchomieniowego nie jest proste ani tanie. Wymaga dużo specjalistycznej wiedzy, dużej ilości systemów wspierających nasz system, komunikację i jego stabilność (więcej o tym w następnym poście)
  • eventual consistency – w przypadku gdy mamy rozproszone systemy z niezależnymi bazami danych, komunikacje sieciową (sync i async) problem niespójności (operowania na danych najbardziej aktualnych) może zawsze wystąpić
  • trudność w projektowaniu usług, ich odpowiedzialności i granic – zwłaszcza na początku prac nad nowym systemem bardzo często nie wiemy jakie dokładnie funkcjonalności i granice odpowiedzialności komponentów powstaną. Bardzo często ciężko tez stwierdzić, która część systemu będzie bardziej (i jak bardzo) „zasobożerna”
  • transakcyjnośćzagwarantowanie transakcyjności w systemie rozproszonym jest sporym wyzwaniem, są oczywiście know how, ale żadne z nich nie jest tak łatwe jak np @Transactional nad publiczną metodą
  • debugowanie – wiąże się często z analizą przepływu requestu przez wiele systemów
  • wdrażanie funkcjonalności przekrojowych w systemie, może wymagać dużego nakładu na synchronizację wielu zespołów