# WebFlux kontra wirtualne wątki: porównanie przy obciążeniu 2000 zapytań na sekundę
W warunkach wysokich obciążeń wybór między reaktywnym modelem Spring WebFlux a nową paradygmatą wirtualnych wątków Project Loom decyduje o wydajności i skalowalności aplikacji Java. W niniejszym badaniu przedstawiono wyniki testów obciążeniowych dwóch podejść przy intensywności 2000 zapytań na sekundę na przykładowym projekcie mikrousług.
Architektura Spring WebFlux: pętla zdarzeń i nieblokujące operacje wejścia-wyjścia
Spring WebFlux wykorzystuje nieblokujący model wejścia-wyjścia z pętlą zdarzeń (event loop). W przeciwieństwie do tradycyjnego modelu „jeden żądanie — jeden wątek”, WebFlux obsługuje tysiące połączeń za pomocą niewielkiej liczby wątków. Kluczowa zasada: wątek nie blokuje się podczas oczekiwania na operacje wejścia-wyjścia, lecz rejestruje zainteresowanie zdarzeniem (np. „dane dotarły do gniazda”) i przechodzi do innych zadań.
Centralnym komponentem jest Netty, który jest używany domyślnie w WebFlux. Netty zarządza pętlą zdarzeń za pośrednictwem EventLoopGroup. Liczba wątków pętli zdarzeń jest określana według wzoru:
int DEFAULT_IO_WORKER_COUNT = Integer.parseInt(System.getProperty(
ReactorNetty.IO_WORKER_COUNT,
"" + Math.max(Runtime.getRuntime().availableProcessors(), 4)));
Czyli liczba wątków odpowiada liczbie dostępnych procesorów, ale nie mniej niż 4. W środowisku kontenerowym tę wartość można dostosować do przydzielonych zasobów.
Każde połączenie sieciowe (Channel) jest przypisane do konkretnej pętli zdarzeń (channel affinity). Zapewnia to brak synchronizacji i efektywność pamięci podręcznej procesora. Jednak jakakolwiek operacja blokująca w pętli zdarzeń zatrzymuje przetwarzanie wszystkich połączeń przypisanych do tej pętli.
Wirtualne wątki: zasada działania Project Loom
Wirtualne wątki zachowują model „jeden żądanie — jeden wątek”, ale znacznie obniżają koszty. Każde żądanie otrzymuje własny wirtualny wątek, który jest montowany na wątkach nośnikowych (carrier threads, czyli wątkach platformowych). Wątki nośnikowe są zarządzane przez planista oparty na ForkJoinPool.
Liczba wątków nośnikowych domyślnie odpowiada liczbie dostępnych procesorów. Na przykład w kontenerze z 2 CPU będzie 2 wątki nośnikowe, na których może działać tysiące wirtualnych wątków.
Metoda tworzenia domyślnego planisty:
private static ForkJoinPool createDefaultScheduler() {
ForkJoinWorkerThreadFactory factory = pool -> new CarrierThread(pool);
int parallelism, maxPoolSize, minRunnable;
String parallelismValue = System.getProperty("jdk.virtualThreadScheduler.parallelism");
String maxPoolSiz
Zalety wirtualnych wątków:
- Zachowanie imperatywnego stylu programowania
- Czytelne stosy wywołań
- Obsługa ThreadLocal
- Uproszczenie pracy z istniejącymi blokującymi bibliotekami
Wyniki testów przy 2000 zapytaniach na sekundę
W trakcie testów przykładowego projektu mikrousług ujawniono następujące cechy:
- WebFlux wykazał stabilną wydajność pod warunkiem braku operacji blokujących w pętli zdarzeń. Jednak przy integracji z blokującymi bibliotekami (np. JDBC) takie operacje należało przenosić do osobnego puli wątków (Schedulers.boundedElastic()), co zwiększało złożoność.
- Wirtualne wątki umożliwiły uproszczenie rozwoju i debugowania, zachowując wysoką wydajność. Przy 2000 zapytaniach na sekundę użycie wirtualnych wątków w Spring MVC i Tomcat nie ustępowało WebFlux pod względem przepustowości, przy czym kod pozostał znajomy dla większości programistów Java.
Decydującym czynnikiem dla WebFlux okazało się prawidłowe użycie reaktywnych łańcuchów i unikanie blokujących wywołań w pętli zdarzeń. Dla wirtualnych wątków ważne jest pamiętanie, że nie rozwiązują one problemu operacji CPU-bound — przy wysokim obciążeniu procesora może dojść do rywalizacji o wątki nośnikowe.
Kluczowe różnice i rekomendacje wyboru
Przy wyborze między WebFlux a wirtualnymi wątkami uwzględnijcie następujące aspekty:
- Typ obciążenia:
- Dla aplikacji I/O-bound (dużo czekania na wejście-wyjście) oba podejścia są efektywne
- Dla zadań CPU-bound wirtualne wątki mogą ustępować z powodu rywalizacji o wątki nośnikowe
- Ekosystem bibliotek:
- Jeśli projekt intensywnie korzysta z blokujących bibliotek (JDBC), przejście na wirtualne wątki uprości migrację
- Dla nowych projektów z obsługą reaktywnych sterowników (R2DBC) WebFlux pozostaje opcją
- Kwalifikacje zespołu:
- Programowanie reaktywne wymaga głębokiego zrozumienia wzorców asynchronicznych
- Wirtualne wątki pozwalają na użycie znanego imperatywnego stylu
Co ważne
- Wydajność przy 2000 zapytaniach na sekundę: Oba podejścia radzą sobie z obciążeniem, ale wirtualne wątki upraszczają rozwój dzięki zachowaniu imperatywnego stylu
- Krytyczne ograniczenia: W WebFlux operacja blokująca w pętli zdarzeń paraliżuje wszystkie żądania w tej pętli; wirtualne wątki nie rozwiązują problemów operacji CPU-bound
- Ekosystem: Przejście na WebFlux wymaga zastąpienia blokujących bibliotek reaktywnymi odpowiednikami, co może być kosztowne
- Debugowanie: Aplikacje reaktywne są trudniejsze do diagnozowania z powodu nieczytelnych stosów wywołań, podczas gdy wirtualne wątki oferują znajome narzędzia profilowania
— Editorial Team
Brak komentarzy.