Spring Webflux Reaktywny Spring

Spring Webflux – Reaktywny Spring

Spring Webflux to moduł frameworka Spring, który pozwala korzystać z reaktywnego programowania w Springu. Koncept programowania reaktywnego został spopularyzowany przez ReactiveX.io (Reactive Extension), które wprowadza reaktywne programowanie do wielu języków poprzez biblioteki. Np. poprzez RxJs w Javascripcie, RxJava 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.

Zacznijmy od tego co oznacza reactive. Jest to termin, który ostatnio jest często używany w IT. Oznacza on w skrócie asynchroniczne przetwarzanie w sposób nieblokujący (np. z użyciem nieblokujących się serwerów). Dodatkowo przy wykorzystaniu funkcyjnych elementów języków programowania. Tak przynajmniej ja rozumiem ten termin ?

 

Trochę historii

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

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

Netty logo

 

Cytat ze strony Netty: „The author has been writing similar frameworks since 2003 and he still finds your feed back precious!”

 

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ę funkcjonalność.

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

 

Jak definiować programowanie reaktywne?

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

W iteratorach możemy wyróżnić 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 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.

 

Spring Webflux w Spring Boot

Spring Boot od wersji 2 zawiera nową wersje Spring Framework 5. W wersji tej został dodany nowy moduł Spring WebFlux, który został stworzony na bazie projektu Reaktor. Projekt ten bardzo mocno korzysta z api Javy 8, z takich elementów jak: CompletableFuture, Stream, czy Duration.

Reaktor daje nam także dwa elementy programowania reaktywnego takie jak Flux (dla obsługiwania 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.

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

 

Jakie korzyści daje nam Spring Webflux

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 za alokowana 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 w 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 strumieniami 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 mikroserwisowych

W architekturze mikroserwisó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 mikroserwis 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

Procesowanie zdarzeń dla Flux w Spring Webflux

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

Mono

Procesowanie zdarzeń dla Mono w Spring Webflux

To kolejny typ(Mono<T>), 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ć endpoint korzystając z adnotacji @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ć Weblfuxa musimy zacząć od zależności gradlowej (jeśli używamy Gradle):

compile('org.springframework.boot:spring-boot-starter-webflux')

Spring Boot „widząc” na swoim classpath Webfluxa, uruchamia dla niego auto konfigurację.

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

@RestController //1
public class PersonController {

	private final PersonRepository repository;

	public PersonController(PersonRepository repository) {
		this.repository = repository;
	}

	@PostMapping("/person") //2
	Mono<Void> create(@RequestBody Publisher<Person> personStream) {//3
		return this.repository.save(personStream).then();
	}

	@GetMapping("/person") //2
	Flux<Person> list() {//4
		return this.repository.findAll();
	}

	@GetMapping("/person/{id}")//2
	Mono<Person> findById(@PathVariable String id) {//5
		return this.repository.findOne(id);
	}
}
  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 Publisher<Person>, czyli naszego producenta, który będzie emitował obiekty Person. Metoda zwraca Mono<Void> czyli w zasadzie nic nie zwraca – Mono może przechowywać 0 lub 1 element.
  4. list zwraca 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.

 

Gdzie stosować Spring WebFlux i podejście reaktywne?

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ślić 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 debugować 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. 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 mikroserwisów, gdzie masz już aplikację oparte o Spring MVC, Spring WebFlux wydaje się naturalnym wyborem. Poza tym zespół, który używa już WebMvc 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. 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ć.

Przeczytaj także: Spring Webflux przykładowa aplikacja

 

PS. Artykuł został przeniesiony z mojego starego bloga, którego już dawno nie rozwijam. W ten sposób chciałem ocalić go od zapomnienia 😉

 

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

 

Mateusz Dąbrowski

Cześć jestem Mateusz, zajmuję się programowaniem już ponad 12 lat z czego ponad 8 programuję w Javie. Zapraszam Cię do lektury mojego bloga. Możesz przeczytać więcej o mnie >>TUTAJ<<

8 thoughts to “Spring Webflux – Reaktywny Spring”

  1. Świetny artykuł. Naprawde napisany po ludzku i dzięki niemu lepiej zrozumiałem programowanie raktywne. W końcu ktoś wyjaśnił mi różnicę gdy go nie użyjemy i gdy go użyjemy. Czy nam coś da i ile nam da. Fajnie tak bardziej szczegółowo.
    Fajnie gdyby Pan robił właśnie takie artykuły bardziej na tematy średnio-zaawansowane i zaawansowane.

    1. Dzięki za komentarz Artur. Cieszę się, że pomogłem 😉

  2. Świetny artykuł. Podoba mi się przedstawienie szerszego kontekstu (tj. historii, kiedy stosować a kiedy nie). Dzięki takiemu ujęciu mamy pełniejszy obraz technologii. Jest to miła różnica w porównaniu z wieloma innymi blogami IT. Fajnie się to czyta, tak trzymaj 🙂 Może jeszcze dodałby jakiś moduł like-ów pod postami bo wiem z doswiadczenia ze ludziom często nie chce sie pisac komentarzy, a like daje się dużo szybciej. Pozdrawiam 🙂

  3. Świetny artykuł – idealnie wprowadza w zagadnienie WebFlux i daje taki dobry overview. W kontekście podsumowania zastanawiam się jeszcze – jeśli architektura mikroserwisów zdaje się być coraz bardziej popularna i szerzej stosowana, to czy może to też wpłynąć na to, że programowanie reaktywne znajdzie zastosowanie w odczuwalnie większym stopniu? Czy jednak wspomniany model imperatywny będzie mógł nadal utrzymywać znaczną liczbę zastosowań ?

    Wiem, że nie jesteś wyrocznią Mateusz – interesuje mnie za to Twoje zdanie w tym temacie 😉

    Pozdrawiam,
    Bartek

    1. Myślę, że tradycyjny model będzie jednak dominował jeszcze przez wiele lat. Głównie dlatego, że jest prostszy (tworzenie i utrzymanie aplikacji) i dlatego, że jest wystarczający. Programowanie reaktywne nie daje aż takich korzyści, że nagle wszyscy zaczną migrować swoje projekty do nowego modelu. Tak jak napisałem w podsumowaniu: „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ć.”

      Tutaj masz opisane jak wyglądało przejście na Webfluxa w Allegro (do poczytania) https://blog.allegro.tech/2019/07/migrating-microservice-to-spring-webflux.html

      Ale pamietaj, to że jakaś duża firma przechodzi na Webfluxa to nie znaczy, że wszyscy tak robią 😉

  4. Dzięki za kolejny dobry artykuł. Po szkoleniu z programowania reaktywnego w mojej firmie wszyscy się tym podjarali, już w każdych następnych rozwiązaniach stosowaliby programowanie reaktywne. Zastanawiam się gdzie jest granica zysku w stosunku do kosztów w stosowaniu stosu reaktywnego.

Komentarze są zamknięte.