Structure and features of distributed systems

16 marca 2023
Jakub Rojek Jakub Rojek
Zdjęcie autorstwa D koi z Unsplash (https://unsplash.com/photos/5nI9N2wNcBU)
Kategorie: Wdrożenia, Administracja, Podstawy IT

Bądźmy szczerzy - przeciętnego telewidza nie interesuje, jak dokładnie działa telewizor. On po prostu ma umożliwiać oglądanie programu, pozwalać przełączać kanały, ustawiać głośność i mieć inne potrzebne funkcje. To samo dotyczy użytkowników radia, telefonu, a nawet samochodów, choć w tym przypadku pewna wiedza zawsze się przydaje. A co z odbiorcami aplikacji, np. internetowych? Naprawdę mały odsetek osób jest zainteresowany tym, jak to wszystko działa od środka i np. dlaczego chwilowo nie można otworzyć Facebooka lub strona nie prezentuje od razu wszystkich informacji. Dlatego największy sukces odnoszą te systemy, które nie tylko zapewniają duże możliwości, ale też są proste w obsłudze i dodatkowo intuicyjne - trafiają wówczas do każdego, niezależnie od jego biegłości technicznej.

Oczywiście, nie każde rozwiązanie takie może być - choćby programy instalowane na komputerze wymagają czasem pewnych czynności w konkretnych sytuacjach i to także od użytkowników, którzy normalnie takimi kwestiami się nie zajmują. Są to jednak rzadkie incydenty. Wracając do aplikacji webowych, wiele osób kończy swoje dociekania na tym, że oni mają przeglądarkę, a gdzieś tam istnieje jakiś serwer. A może serwery?

Nie bez powodu użyłem liczby mnogiej - dzisiaj opowiemy sobie o systemach informatycznych, które działają na wielu serwerach i to w różnym charakterze. Zazwyczaj mówi się o takich systemach, że są rozproszone, przy czym nie chodzi tylko o taką "rozproszoność", o której uczą się studenci informatyki, a więc związaną głównie z obliczeniami. Mam nadzieję, że dzięki temu artykułowi może nie zmieni się dramatycznie Wasze postrzeganie aplikacji, ale będziecie świadomi tego, w jaki sposób skonstruowane są duże serwisy i że zarządzanie nimi nie jest taką prostą sprawą.

System scentralizowany a system rozproszony

Na początku przywołajmy przeciwstawne pojęcia, jakimi są właśnie system scentralizowany oraz rozproszony. Patrząc na nazwę nietrudno się domyślić, co oznaczają te terminy, ale aby tradycji stało się zadość, to zrobimy to po kolei.

Zazwyczaj wyobrażamy sobie systemy informatyczne w taki sposób, że istnieją klienty (tak, nie bez powodu "klienty" zamiast "klienci" - są to użytkownicy, roboty lub inne aplikacje korzystające z API), którzy mają zainstalowane programy lub korzystają z przeglądarki internetowej. Ich aplikacje klienckie (bo tak nazywają się te interfejsy, ew. "końcówki") muszą skądś pobierać i dokądś zapisywać dane, zwłaszcza jeśli współdzielą je z innymi osobami. Kontaktują się zatem z aplikacją serwerową, która działa na jakimś serwerze, a który może komunikować się ze swoją bazą danych i innymi komponentami. To właśnie system scentralizowany - mamy jedno miejsce, które odpowiada za obsługę żądań użytkowników i z którym kontaktują się aplikacje klienckie. W ten sposób skonstruowana jest znakomita większość stron internetowych i aplikacji, gdyż jest to naturalna i najprostsza metoda, wystarczająca w większości sytuacji.

Niestety, nie we wszystkich - istnieją systemy wyjątkowo obciążone, które przy jednym serwerze nie dałyby rady obsłużyć wszystkich żądań od użytkowników. W takim wypadku często myśli się o zwiększeniu mocy tego serwera, czyli tzw. skalowaniu pionowym, co może pomóc, ale na dłuższą metę nie pozwoli na rozwiązanie wszystkich problemów i obsługę coraz większej rzeszy odbiorców. Dodatkowo i co najważniejsze, awaria tej jednej maszyny oznacza awarię całego systemu - nie ma bowiem alternatywnego serwera, który mógłby przejąć obowiązki. Jeśli mówimy o bardzo skomplikowanych obliczeniach, to wykonywanie ich na pojedynczym serwerze może trwać naprawdę bardzo długo. Dlatego niektóre systemy zostały skonstruowane w sposób rozproszony.

Oznacza to, że funkcjonalność aplikacji serwerowych może być podzielona na mniejsze części i zostać umieszczona na różnych serwerach, które komunikują się ze sobą lub bezpośrednio łączą się z nimi aplikacje klienckie. Jest to podstawa działania mikroserwisów. Dodatkowo, w ten sposób niektóre serwisy można powielać, gdyż np. okazują się krytyczne dla całej infrastruktury - jest to możliwe, jeśli aplikacja jest gotowa do tzw. skalowania poziomego. Można również w ten sposób zreplikować całą aplikację, zapewniając jej kopię i tym samym pozbywając się tzw. centralnego punktu awarii.

Dodatkowo, systemami rozproszonymi niekoniecznie muszą być zestawy usług sieciowych - mogą być też to węzły (punkty) obliczeniowe, które przetwarzają pewną porcję danych i których wyniki są potem synchronizowane przez nadzorcę. Ma to miejsce zazwyczaj w zastosowanych naukowych.

Przykłady systemów rozproszonych

Tak naprawdę wszyscy znamy systemy, które działają w sposób rozproszony - wspomniany już Facebook, Twitter, Allegro czy serwisy specjalistyczne wykorzystywane przez tysiące osób nie pociągnęłyby długo, gdyby zostały skonstruowane jako system scentralizowany. Programiści znają też inne, dużo bliższe im przykłady środowisk, które działają w ten sposób - są to systemy kontroli wersji, które pozwalają obsługiwać repozytoria kodu. Dawniej bardzo popularne było korzystanie z SVN (Subversion), który był zainstalowany na wybranym serwerze i to z nim każdorazowo łączył się każdy programista, chcąc pobrać lub zapisać swoje zmiany, aby widzieli je inni członkowie zespołu. Było to proste, ale dostarczało wielu problemów, w związku z czym obecnie najbardziej powszechnym systemem tego typu jest Git, działający w sposób rozproszony i pod pewnymi aspektami zabezpieczający kod nie tylko na serwerze, ale też u każdego programisty z osobna.

Diagram przedstawiający system master-slave, w którym jeden nadzorca zleca podległym maszynom paczkę danych do obliczenia

Warto przypomnieć, że istnieje wiele odmian systemów rozproszonych. Pierwszą z nich są takie, które działają w strukturze master-slave, a więc istnieje jeden nadzorca (master), który zleca zadania innym węzłom obliczeniowym, a następnie zbiera wyniki i je synchronizuje. W takim układzie mamy zdecydowanie wyróżniający się serwer (lub serwery), który jest odpowiedzialny za komunikację z innymi maszynami w celu wspólnego wykonania jednego zadania. Ponieważ przypomina to dyrygowanie tymi podrzędnymi węzłami, taką maszyną nadzorczą nazywa się czasem orkiestratorem. Niektóre osoby mogły spotkać się z tym pojęciem np. przy okazji pracy z kontenerami, a konkretnie z Kubernetesem lub Docker Swarmem.

Diagram przedstawiający system z udziałem mikroserwisów

Bardzo blisko tego podejścia jest też organizacja serwerów w postaci mikroserwisów. Wówczas każda maszyna (lub jedna zawierająca kilka usług) zapewnia charakterystyczną funkcjonalność dla swojej tzw. domeny. W tym układzie może istnieć jeden orkiestrator, który pełni rolę pośrednika i komunikuje się z tymi usługami, ale może być to też zorganizowane w formie choreografii (tak, to pojęcie związane jest nie tylko z tańcem), w której aplikacja kliencka kontaktuje się z konkretnym serwerem, a ten w razie potrzeby pobiera informacje od innych.

Diagram przedstawiający wiele serwerów wybieranych przez load balancera

Istnieją też systemy rozproszone, w których każdy węzeł jest równorzędny. Może to oznaczać skopiowanie systemu na wielu maszynach (tzw. replikacja) oraz posiadanie jednej maszyny, której zadaniem jest jedynie wskazywanie wolnego serwera i tym samym wyrównywanie obciążenia (tzw. load balancing). Podobnie jak wszystkie koncepcje, które są tutaj opisywane, tak naprawdę wszystko może być mieszane - load balancer może wybierać tylko część serwerów albo być przeznaczony do obsługi konkretnej grupy mikroserwisów. Same serwery nie muszą być równe pod kątem parametrów i np. wybór jednego z nich będzie częstszy (ponieważ obsłuży większą liczbę żądań), a inne będą tylko pełniły funkcję wspomagającą lub awaryjną.

Diagram przedstawiający system federacyjny

Natomiast istnieją też struktury, w których serwery są całkowicie autonomiczne, każdy z nich zawiera część danych i mogą działać zarówno same, jak i nawiązując kontakt z innymi, podobnie skonfigurowanymi maszynami. Ten model to np. system federacyjny, który stanowi podstawę funkcjonowania Fediverse i takich aplikacji jak Mastodon, Pleroma, Pixelfed czy Kbin. Serwery w takim układzie stanowią osobne "wysepki", które pozwalają wymieniać ze sobą informacje, ale równie dobrze mogą działać autonomicznie - wówczas kolokwialnie mówimy o nich, że są "zdefederowane".

Dlaczego systemy rozproszone są dobre?

Przede wszystkim, architektura rozproszona zapewnia większą odporność na błędy - jeśli jeden węzeł ulegnie awarii, to jest kilka innych, które potrafią go zastąpić. A nawet jeśli każdy węzeł zawierał inną część funkcjonalną, to nie oznacza, że cała aplikacja przestaje działać, tylko jej konkretny fragment. Przykładowo, w systemie do zarządzania dokumentami możemy nadal podpisywać umowy, ale nie podejrzymy danych spółek. Z pewnością kojarzycie też sytuację, w której bank ogłasza przerwę (zwykle nocną lub poranną) w funkcjonowaniu, ale tylko dla konkretnego rodzaju użytkowników (np. klientów indywidualnych). W ten sposób można ograniczyć dostępność tylko fragmentu całego środowiska. Czasami wyłączenie części węzłów odbije się na wydajności, ale nadal nie unieruchomi całego systemu. Choć jeśli ktoś jest zdolny lub architektura została źle zaprojektowana albo wykonania to i zatrzymanie całego systemu jest wykonalne.

Druga sprawa to rozszerzalność, znana bardziej pod nazwą skalowalności. Jeśli w środowisku scentralizowanym serwer działa zbyt wolno lub ma zbyt małą przepustowość, to można go rozbudować, ale ma to swoje ograniczenia i wymaga wyłączenia maszyny na pewien czas. Z kolei w przypadku architektury rozproszonej, w przypadku konieczności zapewnienia obsługi większej liczby użytkowników (np. w grze online) można po prostu dostawić kolejne serwery i zazwyczaj nie wymaga to wyłączania całego systemu. Oczywiście, to również ma swoje limity, ale na pewno jest łatwiejsze do przeprowadzenia niż np. zamontowanie lepszego procesora.

Trzecia sprawa to wydajność i zwiększenie zasobów, co wiąże się trochę ze skalowalnością, ale zależy też od tego, jaki charakter ma system informatyczny, który rozpatrujemy. Na pewno w przypadku obliczeń przeprowadzanych w sposób rozproszony głównym argumentem za zastosowaniem tego modelu architektonicznego jest właśnie możliwość równoległego obliczania części danych na różnych maszynach. Jeśli chodzi o inne zasoby, to wiele serwerów pozwala osiągnąć większą łączną przestrzeń dyskową, choć tutaj też trzeba pamiętać o tym, żeby panować nad dystrybucją plików i ich nie "zgubić".

Czwarta sprawa to łatwiejsze aktualizacje "w locie". Zastanawialiście się kiedyś, w jaki sposób niektórzy użytkownicy mogą widzieć dwie różne wersje tego samego systemu, nowszą i starszą? Albo dlaczego niektórzy użytkownicy odczuwają problemy, których Wy nie doświadczacie? Właśnie dzięki temu, że modernizacje wprowadza się nie na wszystkie serwery po kolei, tylko najpierw na kilka z nich - jeśli użytkownik akurat będzie miał szczęście zostać skierowany do tego serwera, to zobaczy zmiany. W ten sposób firmy IT mogą mieć większą kontrolę nad aktualizacją (widzą, czy są jakieś problemy i w razie konieczności mogą zatrzymać dalsze wdrożenia) oraz przeprowadzać ją przy działającym systemie, nie blokując wykonywania akcji przez użytkowników.

Piąta sprawa to większa odporność nie tylko na błędy, ale też działania niepożądane. Oczywiście, nie wszystkie akcje złoczyńców są trudniejsze dzięki takiej architekturze, ale warto przywołać choćby sytuacje z atakami DDoS (ang. Distributed Denial of Service), które polegają na wysyłaniu gigantycznej liczby żądań do serwera w celu obciążenia go i w konsekwencji wyłączenia z użycia. W przypadku systemów scentralizowanych dopuszczenie do takiego ataku oznacza całkowite zatrzymanie usługi i przeczekanie, aż ofensywa minie. Natomiast systemy rozproszone (a tym bardziej federacyjne) są na to naturalnie odporniejsze, ponieważ utrata jednego węzła nie oznacza upadku całego środowiska. Przykładem jest próba ataku DDoS na jedną z głównych instancji Mastodona, a mianowicie mastodon.social. W tym przypadku pomogły też inne narzędzia, natomiast nawet mimo dużych problemów tej maszyny, reszta ekosystemu działała bez zarzutu.

Trzeba pamiętać, że każdy z tych argumentów jest piekielnie istotny i wielowymiarowy. Przykładowo, skalowalność pozwala nie tylko zwiększyć zasoby i przepustowość, ale też lepiej wyrównywać obciążenie i polepszać wydajność każdego komponentu z osobna. Pomaga też ukryć część infrastruktury przed światem zewnętrznym, jeśli jest taka potrzeba. Nie bez powodu rozwiązania chmurowe stały się tak popularne, choć trzeba jasno powiedzieć, że nie w każdym przypadku są one odpowiednim rozwiązaniem. Tak samo zresztą, jak architektura rozproszona.

Dlaczego systemy rozproszone są złe?

A właściwie nie "złe", tylko nie zawsze odpowiednie. Tworzenie architektury to zawsze sztuka znalezienia dobrych kompromisów i systemy rozproszone nie są tutaj wyjątkiem. Przede wszystkim ich utrzymanie jest trudniejsze, gdyż same z siebie są środowiskami bardziej złożonymi. Łatwo jest stworzyć system scentralizowany i go wdrożyć, gdyż wszystko zależy tylko od niego. Natomiast utworzenie struktury mikroserwisowej, łączenie węzłów, zapewnienie ich synchronizacji, bezpieczeństwa i prawidłowej obsługi systemy wyjątkowych, a także samo rozdzielenie np. baz danych to robota dla wieloosobowego zespołu, który jest wspierany przez specjalistyczny dział obsługi infrastruktury. Nie jest to schemat, który może być zrealizowany w każdym zespole. Natomiast dobrą informacją jest to, że przy odrobinie wyobraźni można przygotować system tak, aby w przyszłości mógł ulec rozproszeniu, ale w aktualnym momencie jeszcze tego nie wprowadzać.

Drugą wadą, związaną z poprzednim punktem, jest cena. Tworzenie tak rozległego oprogramowania samo w sobie jest drogie, ale jeśli dodamy do tego zespół obsługujący infrastrukturę oraz sam koszt serwerów, to budżet zaczyna się znacząco rozszerzać. Tym niemniej, nie jest to wada w przypadku, kiedy mówimy o systemie ogólnego przeznaczenia, który szybko zwróci przeznaczone na niego środki. Tak, jak napisałem wyżej - nie trzeba od razu uruchamiać 10 maszyn i łączyć je ze sobą - można zacząć nawet od jednego lub dwóch serwerów i przygotować oprogramowanie tak, aby w przyszłości mogło zostać wyskalowane poziomo. Bo pamiętajmy, że zwiększanie możliwości jednej maszyny też potrafi zauważalnie uszczuplić portfel.

Podsumowanie

Systemy rozproszone są bardziej powszechne niż nam się wydaje. W przypadku dużych aplikacji mówimy często raczej o zestawie aplikacji, a mówiąc "serwer" zazwyczaj w myślach powinniśmy podstawiać "farma serwerów" lub "chmura". Oczywiście, nadal ogromna rzesza stron działa na serwerach w tradycyjny sposób, gdyż też nie zawsze jest potrzebne rozwijanie w celu rozproszenia. Dlatego warto znać tę koncepcję i wiedzieć, jakie są jej zalety oraz wady, aby umieć dobrać odpowiednie rozwiązanie do danej sytuacji.

Pozdrawiam i dziękuję - Jakub Rojek.

Piszemy nie tylko artykuły na blogu, ale też aplikacje i dokumentację dla naszych klientów. Zobacz, z kim do tej pory współpracowaliśmy.

O autorze

Jakub Rojek

Główny programista i współwłaściciel Wilda Software, z wieloletnim doświadczeniem w tworzeniu i rozwoju oprogramowania, ale także w pisaniu tekstów na różnorakich blogach. Zaprawiony w boju analityk i architekt systemów IT. Jednocześnie absolwent Politechniki Poznańskiej i okazjonalny prowadzący zajęcia na tej uczelni. W wolnych chwilach oddaje się graniu w gry wideo (głównie w karcianki), czytaniu książek, oglądaniu futbolu amerykańskiego i e-sportu, odkrywaniu cięższej muzyki oraz wytykaniu innym błędów językowych.

Jakub Rojek