Mikroserwisy – Czy na pewno ich potrzebujesz?

Mikroserwisy w ostatnich kilku latach są postrzegane zarówno przez developerów, jak i organizacje jako bardzo sexy podejście do tworzenia aplikacji. Dlaczego tak się dzieje? Mikrousługi są lekkie, stworzone przeważnie w nowoczesnych technologiach, szybkie oraz łatwe w utrzymaniu. Ale co stoi z drugiej strony? Czy są ciemne strony tego podejścia?  Książka o ekonomii „Co widać, a czego nie widać”, Frederika Bastiata, uświadomiła mi, że w IT również trzeba zaglądać w te zacienione miejsca, aby podejmować świadome decyzje.

Architektura mikroserwisowa bazuje na koncepcji wydzielania małych niezależnych części kodu z większej aplikacji. W systemach monolitycznych często takie wydzielenie kodu sprowadzane było do stworzenia nowego pakietu lub modułu w aplikacji. Taki moduł finalnie uruchamiany był razem z innymi modułami na serwerze aplikacyjnym oraz traktowany jak jedna aplikacja. Mikroserwisy, w przeciwieństwie do tego podejścia, uruchamiane są niezależnie. Każdy moduł aplikacji uruchomiony jest jako osobna aplikacja, często z własną bazą danych, na własnym serwerze. Taka architektura dostarcza nam ogromną ilość zalet, ale może również stać się dla naszego projektu ogromnym obciążeniem. Jaka zatem jest odpowiedź na pytanie zadane w tytule? Otóż… to zależy. W tym tekście opiszę, z czym wiąże się tworzenie aplikacji w architekturze mikroserwisowej.

Architektura mikroserwisowa

Zerknijmy na te mikroserwisy

Gdy chcemy stworzyć aplikację w takiej architekturze, będziemy potrzebować kilku dodatkowych narzędzi. To one uczynią naszą infrastrukturę otwartą na skalowanie. 

  • Na początek potrzebujemy service discovery. Jest to wzorzec, który mówi, o potrzebie posiadania jednego miejsca, w którym każda mikroaplikacja będzie mogła się zarejestrować, aby inne aplikacje wiedziały, pod jakim adresem się znajduje. W rozwiązaniu tego zagadnienia pomogą nam narzędzia takie jak Kubernetes, Consul, Eureka lub inne dedykowane API dostępne w rozwiązaniach chmurowych.
  • Aby móc wygodnie zarządzać konfiguracją poszczególnych usług, możemy wdrożyć config serwer. Frameworki takie jak Spring wspierają ten mechanizm. Wystarczy stworzyć nową usługę i przy pomocy odpowiedniego pliku konfiguracyjnego sprawić, że każdy mikroserwis będzie pobierał swoje konfiguracje z jednego miejsca. Pliki konfiguracyjne mogą być przechowywane jako fizyczne pliki na serwerze lub możemy skorzystać z systemu kontroli wersji GIT. 
  • API gateway to kolejny element naszej mikrousługowej układanki. Aplikację budujemy po to, aby ktoś mógł z się z nią skomunikować. Aby to zrobić, dobrą praktyką jest stworzenie jednego punktu wejścia do systemu, który przekieruje ruch w odpowiednie miejsce. API gateway zatem będzie pełnił nie tylko rolę proxy, ale może się również dobrze sprawdzić jako load-balancer.
  • W większości przypadków będziemy potrzebować systemu, który odpowiada za uwierzytelnienie ruchu przechodzącego przez nasz system. 
  • Dodatkowe sprawy, o które trzeba zadbać w takiej architekturze to: odpowiednie zunifikowane logowanie błędów, śledzenie wiadomości przepływających przez system oraz monitoring aplikacji.
Ja pokazujacy na swoją głowę

Jak widać, zanim zaczniemy prawdziwy happy-coding aplikacji, musimy sporo nagimnastykować się z konfiguracją infrastruktury. Dodatkowo, aby wdrożyć taki system na serwerze i móc go łatwo utrzymywać, potrzebujemy narzędzia do orkiestracji takiego jak np. Kubernetes, którego również trzeba dostosować do naszych potrzeb.

Zalety

  • Możliwość wdrażania części aplikacji. Architektura mikroserwisowa, dzięki posiadaniu niezależnie działających usług, pozwala na wdrażanie części funkcjonalności bez potrzeby zatrzymywania systemu. Dzięki temu unikamy przestojów aplikacji. W najgorszym przypadku tylko niektóre funkcje systemu przestaną na chwilę działać. Dodatkowo downtime systemu będzie niższy niż w przypadku monolitów, ponieważ nie uruchamiamy na raz całego kodu aplikacji, a jedynie małą jego część.
  • Możliwość zwiększenia wydajności. Oprócz tego, że każdą usługę możemy uruchomić na osobnych maszynach, co już jest dużym usprawnieniem, mamy możliwość skalowania części aplikacji. Gdy któryś z serwerów ma dużo większe obciążenie niż pozostałe, możemy tę samą część uruchomić więcej niż jeden raz. Równomierne obciążenie serwerów zapewni wdrożony load-balancer.
  • Łatwość zrozumienia. Dzięki zwiększonej granulacji w systemie wdrożenie nowych programistów do pracy z mikroserwisami jest dużo prostsze niż w przypadku monolitu.
  • Większa izolacja błędów. Błąd w jednym mikroserwisie przeważnie nie wpływa na działanie pozostałych serwisów.
  • Możliwość zastosowania różnych technologii. Zespoły piszące aplikację mogą tworzyć każdą mikrousługę w innym języku. Ważne jest, aby wszystkie mikroserwisy komunikowały się ze sobą w ten sam sposób.

Wady

  • Mikroserwisy wymagają dokładnego przemyślenia i starannego zaprojektowania. Podczas projektowania należy dobrze przemyśleć, jak będzie odbywać się komunikacja w systemie. Ważne, aby unikać sytuacji, gdy funkcjonalność wymagająca transakcji wykonuje się sekwencyjnie w kilku serwisach. Nie jest to proste zadanie. Gdy okaże się, że musimy zbudować taki mechanizm, trzeba dobudować dodatkowe mechanizmy takie jak Two-Phase-Commit lub implementacja wzorca Saga. To dodatkowo komplikuje implementacje.
  • Więcej usług oznacza więcej zasobów potrzebnych, aby uruchomić aplikację, a co za tym idzie, większe koszta.
  • Bardziej skomplikowana architektura w porównaniu do monolitu. Obecność wymienionych wcześniej dodatkowych narzędzi oraz mechanizmów czyni architekturę mikroserwisów bardziej skomplikowaną.
  • Autoryzacja i uwierzytelnienie w rozproszonych systemach. To zagadnienie jest całkiem proste, dopóki nasz kod i wszystkie żądania mamy zamknięte w jednej fizycznej aplikacji. W przypadku systemów rozproszonych musimy zadbać o dużo mocniejsze zabezpieczenia, ponieważ usługi będą przesyłały żądania między sobą. Dodatkowo każda usługa musi „wiedzieć”, kto wykonuje zapytanie i, czy w ogóle może je wykonać.
  • Utrudnione śledzenie błędów. Jako że każdy mikroserwis generuje swój zestaw logów, to w przypadku problemów trzeba przejrzeć więcej plików i porównać z logami z innych serwisów. Aby ten proces ułatwić, stosujemy tracing, ale nadal proces przeglądania logów bywa mozolny.
  • Globalne testowanie end-to-end jest trudne. W przypadku monolitu po prostu uruchamiamy aplikację i uruchamiamy testy. W przypadku mikroserwisów wymagane jest odpowiednie uruchomienie wielu mniejszych aplikacji, co może sprawiać problemy z ich orkiestracją.
  • Cost per line przy małym systemie jest duży. Na początku trzeba dużo rzeczy skonfigurować i napisać sporo kodu boilerplate, zanim wystartujemy z developmentem. W monolicie koszt linii kodu jest mały na początku, w miarę rozrastania się systemu koszt się zwiększą, linie kosztów przecinają się bardzo daleko.
  • Pojawiają się problemy typu shared libraries vs. copy-paste. Z jednej strony zasada DRY (Don’t repeat yourself) mówi o wydzielaniu części wspólnych, lecz doświadczenia programistów często mówią o unikaniu współdzielonych bibliotek w mikrousługach na rzecz starego dobrego copy-paste.

Zapisz się na newsletter, aby otrzymywać informacje o nowych artykułach oraz inne dodatki.

Jeśli nie mikroserwisy, to co?

Codementor z zarówką nad głową.
  • Sharding bazy danych. W przypadku gdy system staje się mniej wydajny i szybkość zapisu w dużej mierze jest podyktowana szybkością operacji na bazie danych, to być może wystarczy skorzystać ze skalowania horyzontalnego Twojego źródła danych.
  • Skupienie się na wydajnych algorytmach – uświadamianie zespołów developerskich na temat złożoności obliczeniowej. W projektach IT często bywa tak, że odpowiedzialność za jakość jest nieco rozmyta. Programiści nie czują się w 100% właścicielami swojego kodu i po prostu budują rozwiązania wystarczające, nie skupiając się na optymalizacji algorytmów. Ciągłe uświadamianie członków zespołu oraz budzenie w nich poczucia odpowiedzialności za napisany kod może przyczynić się do znacznego zwiększenia nie tylko jakości kodu, ale i wydajności.
  • Podejście hybrydowe. Być może zamiast budowania całej aplikacji w architekturze mikroserwisowej wystarczy wydzielić z monolitu tylko niektóre kawałki aplikacji, które wymagają szybszego działania. 

Podsumowanie

Jak widać, mikroserwisy mogą wnieść wiele dobrego, ale również mogą stać się sporym obciążeniem. Jeśli nie masz zamiaru przetwarzać ogromnej ilości danych w projekcie albo zespół developerski składa się z kilku programistów, prawdopodobnie nie potrzebujesz wdrażać u siebie tak złożonych rozwiązań.

Bardzo dobrym podejściem może być zaczynanie od architektury monolitycznej. W miarę rozwoju systemu wydzielenie niektórych części systemu wyniknie naturalnie i raczej wydzielimy je słusznie i w odpowiedni sposób. Zaczynając nowy projekt, nie jesteśmy pewni, czy wydzielanie niektórych usług jest konieczne. Mikroserwisy w tym momencie mogą okazać się nie najlepszym wyborem. Aby zapewnić dobrą organizację kodu w monolicie i ułatwić późniejsze wydzielanie odrębnych usług, warto skorzystać z architektury heksagonalnej.

Przejście na architekturę mikroserwisową w istniejącej aplikacji również należy dokładnie przemyśleć. Istnieje szereg usprawnień, które powinniśmy rozważyć przed wdrożeniem tak skomplikowanej architektury.

Podsumowując, jeśli budujesz rozwiązanie dla małej firmy, start-upu lub system nie będzie ogromy, to raczej mikroserwisy nie będą dobrym rozwiązaniem. Dobrze zaplanowany modularny monolit na początek to bardzo trafny wybór.

Codementor z pieniedzmi klienta.

Ważne, aby w pierwszej kolejności dokładnie przeanalizować wymagania, biorąc pod rozważanie wiele czynników. Gdy w grę wchodzą pieniądze klienta, należy dobrze przemyśleć wszystkie aspekty, aby zaproponować rozwiązanie idealnie dopasowane do budżetu i potrzeb. Na etapie projektowania warto skorzystać ze świeżego spojrzenia architektów z innych zespołów, to pozwala na zaprojektowanie systemu w taki sposób, aby nie tylko był dobry tu i teraz, ale również służył klientowi w przyszłości z uwzględnieniem minimalnych kosztów.

Każdy z nas uwielbia nowe i ciekawe technologie, ale wynik analizy nie zawsze mówi, że to, co jest trendy, jest odpowiednim rozwiązaniem w danym przypadku. Jeśli nie chcemy być zespołem przytłoczonym przez wielką i ciężką machinę mikroserwisów, to warto na początek rozważyć inne lżejsze rozwiązania.

Jeśli artykuł Ci się podobał, zapraszam do polubienia profilu na facebooku oraz obserwowania na instagramie. Zapraszam również do grupy Wsparcie w programowaniu i do kontaku.

Sharding w bazach danych

Co to jest Sharding?

Sharding jest wzorcem architektonicznym bazy danych związanym z partycjonowaniem horyzontalnym. Praktyka ta polega na rozdystrybuowaniu wierszy w tabeli na różne tabele zwane partycjami. Każda partycja ma ten sam schemat, te same kolumny, ale kompletnie różne wiersze. Dane znajdujące się w jednej partycji są unikalne i niezależne od danych z pozostałych partycji. Każda partycja nazywana jest Shardem, co w tłumaczeniu na polski oznacza odłamek. Poniższy opisuje sposób zarządzania takimi odłamkami bazy danych.

Sharding jest dobrym przykładem implementacji Shared Nothing Architecture.

Różnice między skalowaniem wertykalnym a horyzontalnym

W powyższym akapicie wspominałem o partycjonowaniu, a teraz piszę o skalowaniu. To nie jest to samo. O partycjonowaniu napiszę niżej. Pojęcia horyzontalny i wertykalny bardzo dobrze opisują to, co zamierzamy wdrożyć. Poniższe definicje na pewno pomogą w zrozumieniu zagadnienia.

Skalowanie wertykalne (scale-up) to podejście, w którym próbujemy zwiększać wydajność poprzez zwiększanie możliwości pojedynczej maszyny. Może to być zwiększanie mocy procesora, dodanie większej ilości pamięci lub inne tego typu zabiegi.

Mówiąc o skalowaniu horyzontalnym (scale-out) mamy na myśli dodawanie większej ilości równoległych maszyn czy budowanie klastra. Na każdej maszynie będzie uruchomiony takie samo oprogramowanie. W tym przypadku potrzebujemy dodatkowego mechanizmu, jakim jest load balancer, który zapewni, że każde żądanie trafi w odpowiednie miejsce.

Partycjonowanie wertykalne vs. horyzontalne

Mówiąc o partycjonowaniu mamy na myśli dane, a dokładniej sposób ich ułożenie w sensowny sposób. Z partycjonowaniem na pewno mieliście do czynienia w przypadku podziału dysków w systemie operacyjnym na mniejsze dyski.

Partycjonowanie wertykalne w bazach danych polega na tworzeniu tabel z mniejszą liczbą kolumn i używaniu innych tabel do zapisywania pozostałych danych. Ogólnie takie partycjonowanie polega na rozdzielaniu danych, które mógłby znajdować się w jednej tabeli na kilka innych. Dobrym motywem do wydzielenia może być większa częstotliwość korzystania (odczyt lub zapis) z niektórych danych. Dla przykładu możemy mieć tabelę z imionami i nazwiskami osób (częste wyszukiwania po tych danych) oraz drugą tabelę z ich numerami telefonów, adresem oraz innymi danymi.

Partycjonowanie horyzontalne polega na zapisywaniu różnych wierszy w różnych tabelach tego samego typu. Za przykład posłuży baza danych firm. Schemat bazy danych będzie wyglądał tak samo dla każdej instancji. Różnica będzie w podziale danych. W przypadku firm możemy przyjąć kryterium liczby pracowników np. mniej niż 100 pracowników, 100 – 10000 pracowników oraz powyżej 10000. Na tej podstawie możemy stworzyć 3 bazy danych z takim samym schematem, ale różnymi danymi. Takie partycjonowanie może być przydatne w systemach multitenant, gdzie dodatkową zaletą shardingu będzie brak konieczności tworzenia dodatkowych zabezpieczeń, aby uniknąć wyciekom danych między tenantami.

Zalety Shardingu

  • Mechanizm shardingu pozwala nam na skalowanie horyzontalne,
  • Zwiększenie czasu odpowiedzi zapytań – dzięki podziałowi danych zapytania nie muszą przeglądać wszystkich wierszy w tabeli,
  • Zmniejszenie ryzyka kompletnej awarii systemu – nawet w przypadku gdy któraś z maszyn przestanie działać, pozostałe nadal będą uruchomione. Może nie jest to idealne rozwiązanie, ale lepiej gdy aplikacja działa przynajmniej dla części użytkowników, niż miałaby wcale nie działać.
  • Może redukować koszty. Dużo implementacji tej architektury opiera się o niskokosztowe darmowe bazy danych, które nie wymagają drogiego hardware, aby działać wydajnie.

Wady Shardingu

  • Zaprojektowanie systemu w taki sposób, aby wykorzystać mechanizm shardingu, jest skomplikowane. Nieprawidłowa implementacja może spowodować niespójność, a nawet utratę danych
  • Bazy danych mogą okazać się nieprawidłowo zbalansowane. To oznacza, że niektóre shardy mogą być bardziej eksploatowane niż inne. Mamy wtedy do czynienia z hotspotem.
  • Raz podzielona baza danych może być trudna do przywrócenia gdy jednak zdecydujemy się korzystać z jednej instancji.
  • Nie każda baza danych natywnie wspiera sharding.

Co to hotspot?

Hotspot to shard, który jest używany dużo częściej niż inne. Załóżmy, że w bazie danych mamy tabelę użytkowników i zdecydowaliśmy, że podział będzie odbywał się na podstawie nazwisk. Tak więc mamy dwie grupy: A-M oraz N-Z. Jakimś dziwnym sposobem 3/4 użytkowników ma nazwisko zaczynające się od litery z drugiej grupy. W tym przypadku hotspotem będzie ta instancja, która obsługuję użytkowników N-Z i nie jest to pożądane zjawisko.

Zapisz się na newsletter, aby otrzymywać informacje o nowych artykułach oraz inne dodatki.

Architektura

Key Based Sharding

Inna nazwa dla tego podejścia to hash based sharding. Polega ono na użyciu jakiegoś klucza (ID, IP, kod pocztowy, kod państwa itp) z nowo generowanej wartości i użycia go jako wsad do funkcji generującej hash. Na podstawie tej wygenerowanej wartości podejmowana jest decyzja, który shard ma obsługiwać te dane.

Key Based Sharding
Key Based Sharding

Próby dodania nowej maszyny mogą być nieco skomplikowane. W takim przypadku potrzebujemy wygenerować nowy klucz dla nowego sharda. Może się również okazać, że będziemy musieli przegenerować część lub nawet wszystkie klucze dla wartości z pozostałych shardów oraz odpowiednio te wartości przemigrować.

Zaletą tego podejścia jest równomierne rozłożenie elementów między maszynami, aby uniknąć hotspotów.

Range Based Sharding

To podejście cechuje się podziałem bazującym na pewnych zakresach. Może to być np. cena czy rozmiar.

Range based sharding
Range Based Sharding

Zaletą tego podejścia jest łatwość implementacji. Jeśli z góry wiemy, że mamy ustalenie gdzie trafiają które dane to wystarczy napisać odpowiedni kod, który sprawdzi ten warunek.

Wadą jest możliwość wystąpienia hotspotów. Nie jesteśmy w stanie zapewnić, że nasze zakresy będą równomiernie rozłożone między shardami.

Directory Based Sharding

Aby zaimplementować tę architekturę, najpierw trzeba utworzyć specjalną tabelę, która będzie zawierać klucze. Klucze w tabeli mówią nam, który shard zawiera dane, które nas interesują.

To podejście jest podobne do range based sharding. Różnica jest taka, że zamiast nie musimy ustalać za każdym razem, gdzie trafią dane, tabela z kluczami po prostu nam to “powie”.

Directory Based Sharding
Directory Based Sharding

Dużą zaletą tej architektury jest jej elastyczność. W przeciwieństwie do dwóch wyżej opisanych podejść możemy zastosować dowolny algorytm rozmieszczenia danych. Zwiększenie ilości maszyn również będzie łatwiejsze.

Minusem jest fakt, że przy każdym żądaniu musimy wykonać zapytanie do dodatkowej tabeli. Jeśli okaże się, że została ona nieoptymalnie zbudowana, to przy większych ilościach danych może spowalniać działanie.

Co zoptymalizować, zanim zdecydujesz się na sharding?

Zanim zdecydujesz się wdrożyć sharding w swoim systemie, rozważ poniższe rozwiązania.

  • Zdalna baza danych – jeśli Twój system bazy danych jest zainstalowany na tym samym serwerze co aplikacja, możesz zwiększyć wydajność przez odciążenie maszyny i przeniesienie bazy na zupełnie inną. To nie jest skomplikowana operacja, ale powinna zwiększyć wydajność.
  • Implementacja pamięci podręcznej – implementacja cacheingu może być dobrym pomysłem gdy problemem w Twoim systemie są wolne odczyty.
  • Stworzenie repliki – chodzi po prostu o skopiowanie bazy danych tak, aby równocześnie działało więcej instancji. W tym przypadku mamy różne podejścia: master-slave, jedna instancja do zapisu a druga do odczytu itp.
  • Skalowanie wertykalne – być może dołożenie większej ilości zasobów sprzętowych do maszyny może spowodować zwiększenie wydajności Twojego systemu.

Podsumowanie

Sharding może wprowadzać większą złożoność do systemu oraz tworzyć potencjalne miejsca wytwarzania się błędów. Należy pamiętać, że sharding nie jest natywnie wspierany w każdym systemie baz danych. Mimo to jest to dobry sposób na zwiększenie wydajności.

Warto wspomnieć o architekturze mikroserwisów. Moim zdaniem, zanim zdecydujemy się na zbudowanie architektury mikrousług, warto zastanowić się, czy sharding nie rozwiąże problemów, z którymi będziemy się zderzać.

P.S. Być może zastanawiasz się, czemu na obrazku wyróżniającym dodałem jakiś budynek. Ta wieża wygląda jak odłamek czegoś większego i nazywa się The Shard.

Dziękuję za przeczytanie mojego artykułu. Zapraszam do poczytania innych wpisów oraz do kontaktu bezpośredniego lub na grupie na facebooku.