Skocz do zawartości

Programowanie reaktywne


DevStart Blogi

Recommended Posts

Zanim będę kontynuował serię o AKKA.NET, warto zapoznać się z podstawami programowania reaktywnego. Pozwoli to później zrozumieć, w jaki sposób AKKA.NET implementuje założenia programowania reaktywnego.
Dzisiaj zatem przedstawienię tzw. “The Reactive Manifesto”, którego pełną treść można znaleźć tutaj. Moim zdaniem jednak, manifest może wydawać się trochę skomplikowany i dlatego zdecydowałem się wyjaśnić to po swojemu.

Zastanówmy się po co nam kolejny “typ” programowania? Co jest złego z naszym starym, klasycznym podejściem klient-serwer? Jak nie trudno domyślić się, wymagania i oczekiwania dla dzisiejszych systemów są inne niż te 10 lat temu. Zamiast pojedynczych serwerów, mamy całe klastry. Przez ogólną dostępność komputerów (PC, Mobile itp.), przetwarzamy dane nie w gigabajtach ale w terabajtach. Model oprogramowania także zmienił się. Większość systemów stanowią usługi, a nie oprogramowanie desktopow’e, pakowane w kartony i sprzedawane w sklepach. Oczekiwania są, aby te usługi działały jak najdłużej bez żadnych usterek. Oczywiście ciężko dostarczyć oprogramowanie, które działa 100% czasu, ale możliwe jest uzyskanie niezawodności np. w 99% czasu. Dostępność różnych urządzeń dostępowych, typu tablet czy telefon komórkowy, powoduje, że użytkownicy nie chcą czekać kilku sekund na odpowiedź od serwera, a mieć rezultat w przeciągu milisekund.

Jak sama nazwa mówi, programowanie reaktywne, cechuje się, że poszczególne komponenty w odpowiedni sposób reagują (react). Na co zatem nasze systemy powinny reagować?

Zdarzenia
Przede wszystkim powinny reagować na zdarzenia. Systemy reaktywne opierają się na zdarzeniach, a nie na zapytaniach typu klient-serwer. W nServiceBus czy AKKA.NET mamy właśnie zdarzenia w postaci asynchronicznych wiadomości. Aktor wysyłając wiadomość do innego aktora, nie blokuje wykonywania procesu. Komponenty komunikujące się za pomocą zdarzeń są powiązane ze sobą w luźny sposób. Jeśli klasa A komunikuję się z klasą B, nie musimy przechowywać żadnej referencji z instancji A do B. Jedyną, luźną zależnością jest wiadomość\zdarzenie.

Dane, obciążenie
Systemy reaktywne muszą również reagować na dane, które przekazywane mogą być z różną intensywnością. Innymi słowy, system powinien być skalowalny. Powinien odpowiednio reagować na małą liczbę zapytań, jak i bardzo dużą. W przypadku niewystarczających zasobów, system powinien zareagować poprzez skalowanie. Jednym z typów skalowania (tzw. scaling-up), jest użycie więcej pamięci czy rdzeni procesora, poprzez np. wielowątkowość. Czasami jednak taka skalowalność jest niewystarczająca i trzeba szukać zasobów na zewnątrz (tzw. scaling out). Zasoby na zewnątrz to oczywiście kolejny serwer dołączony do klastra.

Ogromną rolę w reagowaniu na obciążenie odgrywają wcześniej opisane zdarzenia. Skoro dwie klasy nie są ze sobą mocno powiązane, wtedy nie współdzielą ze sobą żadnego stanu. Co za tym idzie, nic nie stoi na przeszkodzie, aby jedna z klas była wykonywana na kompletnie innym komputerze. Jeśli cały stan zawarty jest w wiadomości, nie ma dla nas różnicy czy przekażemy go w tym samym procesie np. do innego wątku,czy wyślemy przez sieć do innego klastra. Taka właściwość nazywa się “location transparency”, ponieważ raz zaimplementowana logika, może być skalowana bez żadnych zmian w kodzie. AKKA.NET posiada tą właściwość. Implementując jakiś algorytm za pomocą aktorów, można część z nich umieścić na różnych komputerach (tzw. remote actor). Lokalizacja aktorów zatem nie ma znaczenia – mogą znajdować się w tym samym procesie albo na innych komputerach, a i tak zaimplementowany algorytm będzie miał taki sam kod.

Wyjątki i błędy
Systemy reaktywne powinny również reagować w odpowiedni sposób na wszelkie błędy. AKKA.NET posiada szereg strategii obsługi wyjątków o których napiszę w przyszłych postach. System jednak nie powinien przestawać działać w momencie, gdy mało istotny błąd miał miejsce. Dokonuje się tego np. poprzez izolację. Jeśli aktor A ma jakieś błędy, można go odseparować od reszty, aby nie popsuć stanu aplikacji. Innymi słowy, system powinien wrócić do działania w momencie wystąpienia błędu (failure receovery). Jeśli np. nie można połączyć się z jakąś usługą, można spróbować ponownie za kilka sekund, zamiast całkowicie anulować operację.
Aktorzy w AKKA.NET tworzą hierarchie. Jeśli jeden z aktorów ma błędy, jego rodzic decyduje, co z błędem należy zrobić. Można np. powtórzyć operację, zrestartować aktora lub przekazać odpowiedzialność do kolejnego aktora, który zdecyduje co należy zrobić z węzłami podrzędnymi.
Jak widzimy, dobrą obsługę błędów również uzyskujemy za pomocą wspomnianych zdarzeń. Izolacja czy replikacja nie byłoby możliwa bez nich. Za pomocą zdarzeń, można komunikować się między aktorami. Jeśli błąd wystąpił w klastrze A, można spróbować w innym klastrze. Stan nie jest współdzielony, zatem odseparowanie pojedynczego węzła jest łatwe i nie powoduje proliferacji problemu do innych aktorów\węzłów.

Responsywność
Ostatnią, najłatwiejszą w zrozumieniu cechą, jest responsywność. Systemy reaktywne powinny reagować na użytkowników. Jeśli operacja jest czasochłonna, należy powiadomić o tym użytkownika. Znowu uzyskujemy to za pomocą zdarzeń. Zamiast wysłania pojedynczego zapytania i czekania aż operacja wykona się, korzystamy z asynchronicznych wiadomości, które nie blokują wykonywania.
Oczywiście responsywność jest również uzyskana za pomocą skalowalności i poprawnej obsługi błędu. Jeśli jest duża liczba zapytań, dzięki skalowalności system nie powinien być powolny. Obsługa błędów, zagwarantuje, że użytkownik zawsze jest świadom co się dzieje i nie zastanie np. pustej strony, gdy operacja nie powiodła się.

Spójrzmy zatem na słynny diagram, który można znaleźć na stronie “The Reactive Manifesto” (źródło: http://www.reactivemanifesto.org:

“Message-driven” to wspomniane zdarzenia, wiadomości.
“Responsive” to oczywiście responsywność systemu.
“Resilent” to prawidłowa obsługa błędów. Innymi słowy, system powinien być elastyczny (resilent) na błędy i dostosowywać się do sytuacji.
“Elastic” to skalowalność. System powinien być na tyle elastyczny, aby obsługiwać zarówno małą liczbę zapytań jak i bardzo dużą.

Ponadto z oznaczeń na rysunku, możemy zaobserwować:
– Dzięki zdarzeniom uzyskujemy zarówno elastyczność\skalowalność (location transparency), responsywność (nie blokujemy wywołań) oraz Resilent (izolacja błędów, replikacja w innym klastrze).
– Skalowalność (elastic) oraz obsługa błędów (resilence) są ze sobą tak naprawdę powiązane. Gdyby nie skalowalność i location transparency, nie moglibyśmy na przykład wykonać operacji w innym klastrze. Gdyby, nie prawidłowa obsługą błędów, awaria w innych punkcie systemu, zniszczyła by wszystkie węzły, niwelując korzyści ze skalowalności.
– Responsywność nie byłaby możliwa dzięki skalowalności (wydajność), jak i prawidłowej obsługi błędów (nie zostawianie użytkownika bez odpowiedzi w przypadku błędów).

Wyświetl pełny artykuł

Link do komentarza
Udostępnij na innych stronach

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gość
Odpowiedz...

×   Wkleiłeś zawartość bez formatowania.   Usuń formatowanie

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Utwórz nowe...