Tomcat vs Jetty vs Netty – Różnice w Obsłudze Żądań i Zarządzaniu Wątkami

bykowski.pl 23 godzin temu

Wydajność Twojej aplikacji nie zależy wyłącznie od kodu – najważniejszy jest sposób, w jaki kontener webowy zarządza żądaniami i wątkami.

Przybliżę Ci różnice między Tomcatem, Jetty i Netty – trzema popularnymi serwerami, które znacząco różnią się podejściem do obsługi ruchu.

Tomcat – klasyczny model blokujący

Tomcat to najbardziej rozpowszechniony serwer aplikacyjny w ekosystemie Java.

Domyślnie: Tomcat konfiguruje pulę 200 wątków roboczych (maxThreads=200). Każde przychodzące żądanie HTTP przypisuje do wolnego wątku.

Parametry zarządzania wątkami:

  • maxThreads – maksymalna liczba wątków, które mogą równocześnie obsługiwać przychodzące żądania HTTP. Gdy liczba żądań przekracza tę wartość, kolejne trafiają do kolejki oczekujących.
    Domyślnie: 200.
  • minSpareThreads – minimalna liczba wątków utrzymywanych w stanie gotowości, choćby gdy są bezczynne. Dzięki temu serwer może gwałtownie zareagować na nowe żądania bez konieczności tworzenia wątków „na żądanie”. Pozostałe wątki pozostają w puli w stanie bezczynności (aby nie zużywać niepotrzebnie zasobów), gotowe do ponownego użycia.
    Domyślnie: 10.
  • acceptCount – maksymalna liczba żądań, które mogą oczekiwać w kolejce, gdy wszystkie maxThreads są zajęte. jeżeli limit ten zostanie przekroczony (np. 200 aktywnych wątków + 100 w kolejce), każde kolejne żądanie (np. 301.) zostaje odrzucone, co skutkuje błędem lub timeoutem.
    Domyślnie: 100.

Jak działa request flow:

  1. Klient wysyła request (np. GET /users/1).
  2. Gniazdo (socket) akceptowane jest przez AcceptorThread.
  3. Trafia do Executor, który pobiera wątek z puli.
  4. Wątek obsługuje request: np. odczytuje dane z DB, renderuje widok.
  5. Odpowiedź wysyłana, wątek wraca do puli.

jeżeli wszystkie 200 wątków są zajęte – kolejne requesty czekają w kolejce (acceptCount=100), a potem… są odrzucane.

Jetty – lekki i elastyczny serwer

Jetty również operuje na modelu wątków per request, ale z bardziej elastyczną konfiguracją i mniejszym narzutem. Jest częściej używany tam, gdzie liczy się niska latencja i możliwość dostosowania.

Jetty pozwala też na konfigurację hybrydową – częściowo nieblokującą, dzięki adnotacji @WebServlet(asyncSupported = true) oraz metodzie startAsync().

Nie jest to jednak pełnoprawny reactive stack – przez cały czas sporo zależy od architektury Twojej aplikacji.

Jak działa request flow w Jetty (blokująco):

  1. Request trafia do QueuedThreadPool.
  2. Wątek obsługuje całość, analogicznie do Tomcat.
  3. Możliwość manualnego „zawieszenia” przetwarzania i zwolnienia wątku (startAsync()).

Z asynchronicznym API:

  • Wątek może wrócić do puli zanim response zostanie wygenerowany.
  • Odpowiedź może być przekazana np. przez callback lub event.

Jetty lepiej znosi bursty i scenariusze WebSocketowe, choć przez cały czas to nie w pełni reaktywny model.

Netty – w pełni asynchroniczny, event-driven

Netty działa na zupełnie innym poziomie. Netty nie oferuje gotowego serwera HTTP jak Tomcat czy Jetty – zamiast tego udostępnia niskopoziomowy model, który pozwala zbudować serwer HTTP z pełną kontrolą nad ruchem i zdarzeniami sieciowymi.

Netty działa w modelu Event Loop. Domyślnie ma:

  • 1–2 wątki akceptujące – boss group (domyślnie 1),
  • liczba rdzeni CPU × 2 wątków przetwarzających (worker group).

Jak dział request flow:

  1. Połączenie akceptowane przez bossGroup.
  2. Trafia do workerGroup, gdzie zarządzane jest przez selektory.
  3. Wszystko przetwarzane asynchronicznie, przez zdarzenia (channelRead, write, flush, itd.) a dane przekazywane są w postaci obiektów Netty (np. ByteBuf, FullHttpRequest).
  4. Callbacki reagują na zakończenie operacji (np. dostęp do bazy przez CompletableFuture lub Mono).

Jeden wątek Netty może obsłużyć tysiące połączeń, bo nie blokuje IO.

Porównanie: obsługa wątków i przetwarzanie

CechaTomcatJettyNetty
ModelBlokujący Blokujący / HybrydowyW pełni nieblokujący
Wątek per requestTakTak (z opcją async)Nie – Event Loop
Domyślna pula wątków200 (maxThreads)250 (QueuedThreadPool)2 × liczba dostępnych rdzeni CPU + 1 bossGroup
SkalowalnośćOgraniczona pulą wątkówŚrednia, ale lepsza od TomcatWysoka
KonfiguracjaProstaElastycznaZłożona, niskopoziomowa (Spring Boot zapewnia konfiguracje za nas)
Idealny use caseKlasyczne aplikacje weboweLightweight REST, WebSocketReactive, event-driven apps

Kiedy wybrać który?

Tomcat i Jetty to świetne rozwiązania dla klasycznych aplikacji – łatwe w konfiguracji, sprawdzone. Ale w sytuacji, gdy liczba requestów rośnie wykładniczo, wątek per request przestaje być opłacalny.

Netty oferuje zupełnie inny poziom: w pełni reaktywna obsługa, która minimalizuje zużycie zasobów. Wymaga jednak więcej wysiłku – zarówno od strony konfiguracyjnej jak i zasad implementacji rozwiązania, które na nim działa.

Tomcat – dla większości klasycznych aplikacji Spring MVC, kiedy nie przewidujesz gigantycznego ruchu.

Jetty – gdy chcesz więcej kontroli i elastyczności przy mniejszym narzucie, np. w microservices.

Netty – jeżeli tworzysz system wymagający maksymalnej wydajności i kontroli nad ruchem – API gateway, serwer HTTP/2, websockety, broker wiadomości.

Co z wirtualnymi wątkami?

Od JDK 21 wchodzą w grę wirtualne wątki (Project Loom) – przełom, który może zmienić podejście do serwerów opartych na wątkach per request. Umożliwiają one obsługę milionów requestów bez zmiany architektury na reaktywną.

Ważne – wirtualne wątki zmieniają model wykonania, ale nie rozwiązują wszystkiego – przez cały czas wymagają przemyślanej współpracy z blokującymi zasobami (np. I/O, bazy danych).

Zobacz, jak wirtualne wątki przyspieszają aplikacje – pełne szkolenie!

Sprawdź mój materiał na YouTube, w którym krok po kroku pokazuję, jak uruchomić wirtualne wątki w Spring Boot. Live coding, praktyczne przykłady i porównanie wydajności – wszystko, czego potrzebujesz, aby wynieść swoje API na wyższy poziom!

https://youtube.com/embed/wQ9vwUaoq68
Idź do oryginalnego materiału