spring webflux

Spring Boot 2 – Reaktywny Spring WebFlux

Reactive to termin, który ostatnio jest bardzo często używany w świecie IT. Oznacza on w skrócie asynchroniczne przetwarzanie z użyciem nieblokujących się serwerów przy wykorzystaniu funkcyjnych elementów języków programowania. Tak przynajmniej ja rozumiem ten termin na poziomie ogólnym 😉

Koncept programowania reaktywnego został spopularyzowany przez ReactiveX.io czyli reactive extension, które wprowadza reaktywne programowanie do języków np. poprzez RxJs w Javascripcie, RxJavę w języku Java, Rx.NET w C# i wiele innych. Jest to przetwarzanie oparte na zdarzeniach (events). I przeciwnie niż mogłoby się wydawać nie jest to nowa koncepcja…

Trochę historii

Systemy oparte o zdarzenia (eventy) były już znane w latach 90-tych, ale sama idea powstała pewnie jeszcze wcześniej (niestety nie udało mi się ustalić żadnej konkretnej daty).

netty logoNatomiast Netty, z którego między innymi może korzystać reaktywny spring powstał w okolicach roku 2003 (jak podaje oficjalna strona Netty).

The author has been writing similar frameworks since 2003 and he still finds your feed back precious!

Same idee są już dosyć stare, więc dlaczego od niedawna programowanie reaktywne staje się coraz bardziej popularne?

UWAGA: Poza Netty obsługę nieblokujących żądań oferują także Tomcat, Undertow i Jetty. Ale tylko Netty powstał jako stricte nieblokujący się serwer. W pozostałych dopisano tę funkconalność.

Jak zdefiniować programowanie reaktywne?

Paradygmat programowania reaktwnego definiowany jest często jako rozszerzenie wzorca observator. Można także dokonać porównania wzorca reaktywnego strumienia (reactive stream) z wzorcem iterator.

W iteratorach możemy wyróznić parę Iterable-iterator. Zasadniczą różnicą pomiędzy iteratorami i reaktywnym strumieniem jest to, że iterator bazuje na pobieraniu (pull), natomiast reaktywny strumień na wypychaniu (push).

Iterator to wzorzec imperatywny, w którym to developer decyduje, kiedy pobierze następny element z sekwencji używając metody next(). Natomiast w reaktywnych strumieniach odpowiednikiem powyższej pary jest publisher-subscriber, gdzie publisher powiadamia subscribera o nowym zdarzeniu (wtedy kiedy ono nadejdzie) i dlatego aspekt wypychania (push) jest tutaj kluczowy do zdefiniowania programowania reaktywnego. Wszystkie operacje, którym jest poddawany strumień są deklaratywne. Programista określa logikę przetwarzania zdarzeń zamiast określać szczegółowy przepływ sterowania.

Skąd się wzięło reactive w spring boot?

Nowy Spring Boot 2 zawiera nową wersje Frameworka Spring 5 i w wersji tej został dodany nowy moduł Spring WebFlux, który został stworzony na bazie projektu Reaktor. Projekt ten bardzo mocno korzysta z api javynp. CompletableFuture, Stream, czy Duration.

Reaktor daje nam także dwa elementy programowania reaktywnego takie jak Flux (dla obsłuwiwania n elementów) i Mono (dla 0 lub 1 elementu).

Reaktor wspiera oczywiście nieblokującą komunikację między procesami (IPC), może korzystać przy tym z wielu serwerów aplikacji, tak jak już wspomniałem wcześniej.

Daje nam to dużą elastyczność i bardzo dobrze nadaje się dla środowisk microserwisowych. Wspiera także mechanizm backpressure (o którym później).

Jakie korzyści otrzymujemy korzystając z podejścia reaktywnego?

Nieblokujący się serwer

W większości benchmarków, które widziałem, nieblokujące się serwery wypadały tylko nieznacznie lepiej niż blokujące. Było to mnie więcej 7-10% (jeśli chodzi o czas wykonania requestów), prawdopodobnie w wielu scenariuszach te różnice mogłyby być jeszcze mniejsze. Ale największą korzyścią jaką otrzymujemy jest zmniejszone użycie zasobów (chociaż to zależy też od sytuacji) i stabilna praca przy dużym obciążeniu. W skrócie: nieblokujący się serwer jest wstanie udźwignąć większą liczbę requestów nie tracąc przy tym stabilności i szybkości.

W normalnych serwerach takich jak np. Tomcat do obsługi żądań używane są wątki. Każdy request obsługuje osobny wątek. Zwykle w takim serwerze zaalokowana jest pula wątków (dla Tomcata domyślnie jest 200 wątków). Przy dużym obciążeniu, gdy wyczerpiemy pulę wątków, następne żądania będą musiały poczekać na zwolnienie jakiegoś wątku.

W serwerze nieblokującym jest zwykle 1-2 watki na rdzeń procesora, więc pula wątków jest zależna od dostępnego procesora. Zwykle liczba wątków nie jest większa niż liczba rdzeni procesora.

Serwer nieblokujący kolejkuje żądania, następnie pętla zdarzeń serwera przetwarza je. Wszystkie operacje wykonywane z aplikacji, takie jak: pobieranie danych z bazy, odczytywanie zawartości plików, powinny być wykonywane asynchronicznie. W innym wypadku pętla przetwarzająca zdarzenia w naszym serwerze zostanie zablokowana.

Asynchroniczne przetwarzanie zapytań

Wszystko,co jest związane z reaktywnymi stream’ami jest przetwarzane w sposób asynchroniczny, a więc na każdym poziomie naszej aplikacji musimy myśleć o tym, czy przypadkiem nie zablokujemy serwera. W związku z tym trzeba używać odpowiednich sterowników do baz danych (o ile takie istnieją). Odczytywanie plików też musi być obsługiwane w sposób asynchroniczny, w zasadzie każda operacja odczytywania zasobów powinna być wykonywana w sposób asynchroniczny. Prowadzi to czasem do komplikacji kodu.

Backpressure

Jest to mechanizm, który daje możliwość komunikacji zwrotnej pomiędzy producentem i konsumentem. Jeżeli konsument nie jest w stanie przetworzyć ilości zdarzeń produkowanych przez producenta, powiadomi go o tym, by ten mógł wysyłać taką ilość zdarzeń, którą konsument jest w stanie przetworzyć. Reaktor wykorzystuje tutaj mechanizm requestów, informując producenta, że w danej chwili może przetworzyć tylko n requestów.

Przeznaczony do środowisk microserwisowych

W architekturze microserwisów, gdzie komunikacja często odbywa się pomiędzy różnymi aplikacjami podejście asynchroniczne może okazać się kluczowe dla wydajnej komunikacji np. gdy ilość zapytań może być znaczna i gdzie jeden microserwis komunikuje się jednocześnie z wieloma innymi. Dzięki zastosowaniu nieblokujących się serwerów i nie blokujących się klientów aplikacje takie zużywają mniej zasobów i dzięki temu są też bardziej stabilne.

Krótko o Flux i Mono

Flux

flux-event-processing
Źródło: projectreactor.io

Flux<T> to jeden z dwóch specjalnych typów, które udostępnia nam Reaktor. Implementuje on interfejs Publisher<T> i reprezentuje asynchroniczną sekwencję od 0 do n wyemitowanych elementów. Posiada 3 wartości: element (jakiś obiekt), sygnał sukcesu i sygnał błędu. Te trzy wartości odpowiadają metodom onNext, onComplete lub onError.

Mono

mono-event-processing
Źródło: projectreactor.io

Mono<T> to kolejny typ, który także implementuje interfejs Publisher<T>. Mono w przeciwieństwie do Fluxa jest typem specjalnym, który zwraca od 0 do 1 elementów. Jest on takim trochę okrojonym Fluxem.

Typ Mono po emisji jedynego elementu może zakończyć się sukcesem lub błędem (onComplete lub onError). Gdy chcemy użyć puste Mono tworzymy je poprzez Mono<Void>.

Jak używać Spring WebFlux?

Spring WebFlux daje nam dwie możliwości uruchomienia endpointa http

  1. Adnotowany kontroler – jest to dobrze znany ze springa mechanizm pozwalający wystawić enpoint korzystając z @Controller lub @RestController i adnotacji @RequestMapping lub @GetMapping, @PostMapping itp.
  2. Funkcyjny endpoint – bazujący na lambdach model funkcyjny – daje to różnicę koncepcyjną, taką że aplikacja od początku do końca jest pod kontrolą mechanizmu obsługującego requesty, inaczej niż w przypadku adnotowanych kontrolerów, gdzie deklarujemy, że kontroler będzie używany do obsługi requestów.

Zaprezentuję tutaj krótki przykład z adnotowanym kontrolerem, ponieważ jest on łatwiejszy do zrozumienia.

Żeby uruchomić webfuxa musimy zacząć od zależności gradlowej (jeśli używamy gradla):

Spring boot widząc na classpath webfluxa uruchamia dla niego auto konfigurację.

Następną rzeczą jaką tworzymy jest kontroler.

  1. Klasę kontrolera oznaczamy adnotacją @RestController tak samo jak w webMvc.
  2. Metody kontrolera oznaczamy @GetMapping i @PostMapping tak samo jak w webMvc.
  3. Metoda create przyjmuje jako requestBody parametr Publishera<Person> czyli naszego producenta, który będzie emitował nam obiekty Person. Metoda zwraca Mono<Void> czyli w zasadzie nic nie zwraca – Mono może przechowywać 0 lub 1 element.
  4. list zwraca nam Flux’a z typem Person, czyli jest to sekwencja typów Person.
  5. findById zwraca mono tym razem z typem Person, czyli dostajemy jeden obiekt Person.

W jakich sytuacja stosować Spring WebFlux i podejście reaktwne, a w jakich nie ?

Każde narzędzie ma swoją pulę zastosowań i jeśli używamy go w odpowiednim kontekście daje nam wiele korzyści. Jeśli jednak używamy jakiegoś narzędzia w kontekście, w którym nie powinniśmy go używać, prosimy się o duże kłopoty. Także Spring WebFlux ma swoją przeznaczenie.

Żeby móc lepiej określć czy powinniśmy użyć webfluxa, rozważmy poniższe punkty:

  • Jeśli Spring MVC sprawdza się dobrze w aplikacji, której używasz – nie ma potrzeby na zmianę. Asynchroniczne programowanie jest bardziej skomplikowane niż imperatywne. Imperatywny styl programowania jest o wiele bardzie zrozumiały i naturalny dla przeciętnego programisty. Także o wiele łatwiej jest debagować kod imperatywny. Poza tym większość dostępnych bibliotek działa w sposób blokujący co utrudnia ich używanie w reaktywnym programowaniu.
  • Prosty sposób na sprawdzenie czy użycie programowania reaktywnego jest sensowne jest sprawdzenie zależności (do modułów, bibliotek). Jeśli masz zależności blokującego api (jpa, jdbc), czy modułów sieciowych, wtedy Spring MVC jest najlepszym rozwiązaniem.
  • Jeśli rozwijasz aplikację opartą na Spring MVC, która łączy się ze zdalnymi serwisami, możesz spróbować zacząć używać klasay WebClient (która jest implementacją reaktywnego klienta). Możesz zwracać reaktywne typy (Flux, Mono, lub inne) prosto z kontrolerów Spring MVC.
  • Jeśli twój team składa się z dużej liczby developerów nie mających doświadczenia w reaktywnym programowaniu to dużym wyzwaniem będzie dla nich całkowita przesiadka na nieblokujące, funkcyjne i deklaratywne programowanie. Lepszym pomysłem będzie zaczęcie od czegoś małego np. WebClient’a.

i ponadto …

  • Jeśli szukasz frameworka do tworzenia reaktywnych aplikacji, to Spring WebFlux daje Ci wszystkie funkcjonalności podobnych dostępnych frameworków, a poza tym masz możliwość wyboru serwera który będzie działał pod spodem twojej aplikacji (Netty, Tomcat, Jetty, Undertow). Masz także możliwość korzystania z funkcyjnych endpointów, czy też z adnotowanych kontrolerów. A także możliwość wyboru, którą z bibliotek reaktywnych wybierzesz – Reaktor, RxJava czy inną.
  • Jeśli szukasz frameworka, który mógłbyś wykorzystać w architekturze microserwisów, gdzie masz już aplikację oparte o Spring MVC, Spring WebFlux wydaje się naturalnym wyborem wszędzie tam, gdzie reaktywne podejście jest bardziej przydatne. Poza tym zespół, który używa już web-mvc będzie mógł o wiele łatwiej wdrożyć się w Spring WebFlux niż inny reaktywny framework.

Najważniejsze to zastanowić się nad tym jakie zyski daje nam reaktywne programowanie w naszej aplikacji. Dla większości aplikacji reaktywne programowanie nie jest dobrym wzorcem i nie jest rozsądne by nagle wszyscy przesiedli się na model reaktywny i Spring WebFlux. Jeśli nie jesteś pewien czy model reaktywnego programowania jest dla ciebie dobry, skorzystaj z tradycyjnego podejścia do tworzenia aplikacji…

Podsumowanie

Programowanie reaktywne to zupełnie inny model przetwarzania bazujący na zdarzeniach, daje nam pewnie udogodnienia, w zamian trochę komplikując kod aplikacji. Mimo, że model ten jest znany od jakiegoś czasu, to dopiero rozwój technologii umożliwił popularyzację tej koncepcji, jak również w znacznym stopniu przyczyniły się do tego elementy funkcyjne w językach obiektowych.

Nie jest to jednak uniwersalny sposób na rozwiązanie wszystkich bolączek współczesnych aplikacji. Wręcz przeciwnie, jest on przeznaczony dla niewielkiego procenta aplikacji pracujących pod dużym obciążeniem. Ale jest to ciekawy koncept który warto zgłębiać i rozwijać.

A ty co myślisz o reaktywnym programowaniu w springu ? Daj znać w komentarzach.

Źródła:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html
https://projectreactor.io/docs/core/release/reference/

 

Leave a Reply

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *