# WebFlux vs. Virtual Threads: Vergleich bei 2000 Anfragen pro Sekunde
Unter hoher Last entscheidet die Wahl zwischen dem reaktiven Modell von Spring WebFlux und dem neuen Virtual-Threads-Paradigma von Project Loom über Leistung und Skalierbarkeit von Java-Anwendungen. Diese Studie präsentiert Lastergebnisse beider Ansätze bei 2000 Anfragen pro Sekunde an einem Beispiel-Mikroservices-Projekt.
Spring WebFlux-Architektur: Event Loop und nicht-blockierendes E/A
Spring WebFlux verwendet ein nicht-blockierendes E/A-Modell mit einem Event Loop. Im Gegensatz zum traditionellen Modell „eine Anfrage – ein Thread“ bewältigt WebFlux Tausende von Verbindungen mit einer geringen Anzahl an Threads. Das zentrale Prinzip: Ein Thread blockiert nicht beim Warten auf E/A, sondern registriert Interesse an einem Ereignis (z. B. „Daten am Socket eingetroffen“) und wechselt zu anderen Aufgaben.
Die zentrale Komponente ist Netty, das WebFlux standardmäßig einsetzt. Netty verwaltet den Event Loop über EventLoopGroup. Die Anzahl der Event-Loop-Threads ergibt sich aus dieser Formel:
int DEFAULT_IO_WORKER_COUNT = Integer.parseInt(System.getProperty(
ReactorNetty.IO_WORKER_COUNT,
"" + Math.max(Runtime.getRuntime().availableProcessors(), 4)));
Mit anderen Worten: Die Anzahl der Threads entspricht der Anzahl verfügbarer Prozessoren, mindestens jedoch 4. In einem containerisierten Umfeld kann dieser Wert an die zugewiesenen Ressourcen angepasst werden.
Jede Netzwerkverbindung (Channel) ist an einen spezifischen Event Loop gebunden (Channel Affinity). Das gewährleistet keine Synchronisationsoverheads und effiziente CPU-Caches. Allerdings stoppt jede blockierende Operation in einem Event Loop die Verarbeitung aller daran gebundenen Verbindungen.
Virtual Threads: So funktioniert Project Loom
Virtual Threads behalten das Modell „eine Anfrage – ein Thread“ bei, reduzieren jedoch die Overhead-Kosten drastisch. Jede Anfrage erhält einen eigenen Virtual Thread, der auf Carrier Threads (Platform Threads) montiert wird. Carrier Threads werden von einem Scheduler auf Basis von ForkJoinPool verwaltet.
Die Standardanzahl der Carrier Threads entspricht der Anzahl verfügbarer Prozessoren. So gibt es beispielsweise in einem Container mit 2 CPUs 2 Carrier Threads, die Tausende von Virtual Threads ausführen können.
Die Standardmethodik zur Scheduler-Erstellung:
private static ForkJoinPool createDefaultScheduler() {
ForkJoinWorkerThreadFactory factory = pool -> new CarrierThread(pool);
int parallelism, maxPoolSize, minRunnable;
String parallelismValue = System.getProperty("jdk.virtualThreadScheduler.parallelism");
String maxPoolSiz
Vorteile von Virtual Threads:
- Erhalt des imperativen Programmierstils
- Lesbare Stack Traces
- ThreadLocal-Unterstützung
- Vereinfachte Integration mit bestehenden blockierenden Bibliotheken
Lastergebnisse bei 2000 Anfragen pro Sekunde
Tests am Beispiel-Mikroservices-Projekt ergaben Folgendes:
- WebFlux zeigte stabile Leistung, solange keine blockierenden Operationen im Event Loop vorhanden waren. Die Integration blockierender Bibliotheken (z. B. JDBC) erforderte jedoch das Auslagern dieser Operationen in einen separaten Thread-Pool (Schedulers.boundedElastic()), was die Komplexität erhöhte.
- Virtual Threads vereinfachten Entwicklung und Debugging und lieferten hohe Leistung. Bei 2000 Anfragen pro Sekunde erreichten Virtual Threads auf Spring MVC und Tomcat die Durchsatzrate von WebFlux – mit Code, der den meisten Java-Entwicklern vertraut vorkommt.
Ein kritischer Faktor für WebFlux war die korrekte Nutzung reaktiver Chains und das Vermeiden blockierender Aufrufe im Event Loop. Bei Virtual Threads gilt: Sie lösen keine CPU-gebundenen Operationen – hohe CPU-Last kann zu Konkurrenz um Carrier Threads führen.
Wesentliche Unterschiede und Empfehlungen
Beim Vergleich von WebFlux und Virtual Threads sollten diese Aspekte berücksichtigt werden:
- Workload-Typ:
- Beide Ansätze glänzen bei E/A-gebundenen Apps (viel E/A-Wartezeit)
- Bei CPU-gebundenen Tasks können Virtual Threads durch Carrier-Thread-Konkurrenz zurückfallen
- Bibliotheks-Ökosystem:
- Wenn das Projekt auf blockierende Bibliotheken (JDBC) setzt, erleichtern Virtual Threads die Migration
- Für neue Projekte mit reaktiven Treibern (R2DBC) bleibt WebFlux eine Option
- Team-Expertise:
- Reaktives Programmieren erfordert tiefes Wissen über async-Muster
- Virtual Threads ermöglichen den vertrauten imperativen Stil
Wichtige Erkenntnisse
- Leistung bei 2000 Anfragen pro Sekunde: Beide bewältigen die Last, doch Virtual Threads vereinfachen die Entwicklung durch Beibehaltung des imperativen Stils
- Kritische Einschränkungen: Eine blockierende Operation im Event Loop von WebFlux lähmt alle Anfragen an diesem Loop; Virtual Threads beheben keine CPU-Probleme
- Ökosystem: WebFlux bedeutet den Wechsel von blockierenden zu reaktiven Bibliotheken, was teuer werden kann
- Debugging: Reaktive Apps sind aufgrund unübersichtlicher Stack Traces schwieriger zu diagnostizieren, Virtual Threads bieten vertraute Profiling-Tools
— Editorial Team
Noch keine Kommentare.