
Świat nie stoi w miejscu. Postęp stawia przed nami nowe technologiczne wyzwania. W odpowiedzi na zmieniające się wymagania, także architektura systemów informacyjnych musi ewoluować. Dziś porozmawiamy o architekturze opartej na zdarzeniach, konkurencyjności, równoległości, asynchroniczności i o tym, jak w Erlang można pokojowo z tym wszystkim współżyć.
Wprowadzenie
W zależności od rozmiarów projektowanego systemu i jego wymagań, my, programiści, wybieramy sposób wymiany informacji w systemie. W większości przypadków, aby zorganizować interakcję usług, roboczym rozwiązaniem może być schemat z brokerem, na przykład oparty na RabbitMQ lub Kafka. Jednak czasami strumień zdarzeń, SLA i poziom kontroli nad systemem są takie, że gotowe rozwiązania do komunikacji nie będą odpowiednie. Oczywiście można nieco skomplikować system, biorąc na siebie odpowiedzialność za poziom transportu i tworzenie klastra, na przykład używając ZeroMQ lub nanomsg. Ale jeśli system ma wystarczającą przepustowość i możliwości standardowego klastra Erlanga, wówczas wprowadzenie dodatkowej jednostki wymaga szczegółowego zbadania i uzasadnienia ekonomicznego.
Temat reaktywnych aplikacji rozproszonych jest dość obszerny. Aby zmieścić się w formacie artykułu, przedmiotem dzisiejszej dyskusji będą tylko homogeniczne środowiska zbudowane na bazie Erlang/Elixir. Ekosystem Erlang/OTP pozwala na wdrożenie architektury reaktywnej przy minimalnym nakładzie pracy. Jednak w każdym przypadku potrzebować będziemy warstwy wymiany wiadomości.
Podstawy teoretyczne
Projektowanie rozpoczyna się od określenia celów i ograniczeń. Głównym celem nie jest rozwój dla samego rozwoju. Musimy stworzyć bezpieczne i skalowalne narzędzie, na bazie którego można budować, a co najważniejsze – rozwijać nowoczesne aplikacje różnego poziomu: zaczynając od jednoserwerowych, obsługujących niewielką publiczność, które w przyszłości mogą przekształcić się w klastry do 50-60 węzłów, aż po federacje klastrów. W ten sposób głównym celem jest maksymalizacja zysku poprzez zmniejszenie kosztów budowy i posiadania końcowego systemu.
Wyróżnijmy 4 główne wymagania dotyczące końcowego systemu:
- Zorientacja na zdarzenia.
System zawsze jest gotowy do przepuszczania przez siebie strumienia zdarzeń i podejmowania niezbędnych działań; - Mskalowalność.
Poszczególne bloki mogą skalować się zarówno pionowo, jak i poziomo. Cały system powinien mieć zdolność do nieskończonego wzrostu poziomego; - oodporność na awarie.
Wszystkie poziomy i wszystkie usługi powinny mieć możliwość automatycznego przywracania po awariach; - Gwarantowany czas reakcji.
Czas jest cenny, a użytkownicy nie powinni czekać zbyt długo.
Pamiętasz starą bajkę o „The little engine that could”, znanej jako „Pociąg, który mógł”? Aby projektowany system skutecznie przeszedł z etapu prototypu do progresywnego rozwiązania, jego fundament musi spełniać minimalne wymagania. MOGŁO.
Do messaging jako infrastrukturalnego narzędzia i podstawy dla wszystkich usług dodawany jest jeszcze jeden punkt: wygoda użytkowania dla programistów.
orientacja na zdarzenia
Aby aplikacja mogła wyrosnąć z jednego serwera do klastra, jej architektura musi zapewniać luźną powiązanie. Temu wymaganiu odpowiada asynchroniczny model. W nim nadawca i odbiorca dbają o ładunek informacyjny wiadomości i nie martwią się o przesyłanie oraz routowanie w systemie.
Skalowalność
Skalowalność i efektywność systemu idą w parze. Komponenty aplikacji powinny umieć wykorzystywać wszystkie dostępne zasoby. Im skuteczniej możemy wykorzystywać moce wytwórcze i im optymalniejsze są nasze metody przetwarzania, tym mniej wydajemy pieniędzy na sprzęt.
W ramach jednego maszyny Erlang tworzy środowisko o wysokiej konkurencyjności. Równowagę między konkurencyjnością a równoległością można ustawić, wybierając liczbę wątków systemu operacyjnego dostępnych dla Erlang VM i liczbę planistów wykorzystujących te wątki.
Procesy Erlang nie mają wspólnego stanu i działają w trybie nieblokującym. Zapewnia to stosunkowo niską latencję i wyższą przepustowość niż tradycyjne aplikacje oparte na blokującej synchronizacji. Planista Erlang dba o sprawiedliwy podział CPU i IO, a brak blokad pozwala aplikacji odpowiadać nawet w trybie szczytowym lub w przypadku awarii.
Na poziomie klastra problem z wykorzystaniem również istnieje. Ważne jest, aby wszystkie maszyny w klastrze były równomiernie obciążone, a sieć nieprzeciążona. Wyobraźmy sobie sytuację: ruch użytkowników jest kierowany do zewnętrznych load balancerów (haproxy, nginx, itd.), które maksymalnie równomiernie rozdzielają zapytania do przetworzenia pomiędzy zestaw dostępnych backendów. W ramach infrastruktury aplikacji usługa, która realizuje wymagany interfejs, to tylko ostatnia mila, i będzie musiała zażądać od szeregu innych usług, aby odpowiedzieć na początkowe zapytanie. Wewnętrzne zapytania również wymagają routingu i równoważenia obciążenia.
Aby efektywnie zarządzać przepływem danych, messaging musi zapewnić deweloperom interfejs do zarządzania routowaniem i rozdzielaniem obciążenia. Dzięki temu deweloperzy będą mogli, korzystając ze wzorców mikroserwisowych (agregator, proxy, łańcuch, rozgałęzienie, itp.), rozwiązywać zarówno standardowe zadania, jak i te występujące rzadko.
Z perspektywy biznesowej skalowalność to jeden z narzędzi zarządzania ryzykiem. Najważniejsze to zaspokoić potrzeby klientów, optymalnie wykorzystując sprzęt:
- Przy zwiększeniu mocy sprzętu w wyniku postępu. Nie będzie on stał bezczynnie z powodu niedoskonałości oprogramowania. Erlang doskonale skaluje się w pionie i zawsze będzie w stanie wykorzystać wszystkie rdzenie CPU oraz dostępną pamięć;
- W warunkach chmurowych możemy zarządzać ilością sprzętu w zależności od bieżącego lub prognozowanego obciążenia i gwarantować SLA.
Odporność na awarie
Rozważmy dwie aksjomat: „Awaria jest niedopuszczalna” i „Awaria zawsze się zdarzy”. Dla biznesu awaria oprogramowania to utrata pieniędzy, a co gorsza – reputacji. Balansując pomiędzy możliwymi stratami a kosztami rozwoju oprogramowania odpornego na awarie, często można znaleźć kompromis.
W krótkim okresie architektura, w której zakłada się odporność na awarie, oszczędza pieniądze na zakup gotowych rozwiązań do klasteryzacji. Są one drogie i również zawierają błędy.
W długim okresie architektura odporna na awarie wielokrotnie zwraca koszty jej zastosowania na wszystkich etapach rozwoju.
Komunikacja w kodzie jest nadal w fazie rozwoju, co pozwala na szczegółowe zaplanowanie interakcji komponentów w systemie. Ułatwia to zadanie reagowania i zarządzania awariami, ponieważ wszystkie odpowiedzialne komponenty zajmują się awariami, a końcowy system wie, jak automatycznie powrócić do normalnego stanu po awarii projektowanej z myślą o tym.
Reaktywność
Bez względu na awarie, aplikacja musi odpowiadać na zapytania i spełniać SLA. Rzeczywistość jest taka, że ludzie nie chcą czekać, dlatego biznes musi się dostosować. Coraz więcej aplikacji oczekuje dużej reaktywności.
Reaktywne aplikacje działają w trybie zbliżonym do rzeczywistego czasu. Erlang VM funkcjonuje w trybie miękkiego czasu rzeczywistego. W niektórych dziedzinach, takich jak handel giełdowy, medycyna, zarządzanie sprzętem przemysłowym, ważny jest tryb twardego czasu rzeczywistego.
Reaktywne systemy poprawiają UX i są korzystne dla biznesu.
Wstępny podsumowanie
Planując ten artykuł, chciałem podzielić się doświadczeniem stworzenia brokera wymiany komunikatów i zbudowania na jego podstawie złożonych systemów. Ale część teoretyczna i motywacyjna okazała się dosyć obszerna.
W drugiej części artykułu opowiem o niuansach realizacji punktów wymiany, wzorach wymiany komunikatów i ich zastosowaniu.
W trzeciej części omówimy ogólne kwestie organizacji usług, trasowania i równoważenia obciążenia. Porozmawiamy o praktycznych aspektach skalowalności i odporności na awarie systemów.
Koniec pierwszej części.
Zdjęcie .
Źródło: habr.com
