WebFlux contre les Virtual Threads : Comparaison à 2000 requêtes par seconde
Sous de fortes charges, le choix entre le modèle réactif de Spring WebFlux et le nouveau paradigme des virtual threads de Project Loom détermine les performances et la scalabilité des applications Java. Cette étude présente les résultats de tests de charge pour les deux approches à 2000 requêtes par seconde sur un projet d'exemple de microservices.
Architecture de Spring WebFlux : Boucle d'événements et E/S non bloquante
Spring WebFlux utilise un modèle d'E/S non bloquante avec une boucle d'événements. Contrairement au modèle traditionnel « une requête — un thread », WebFlux gère des milliers de connexions à l'aide d'un petit nombre de threads. Le principe clé : un thread ne se bloque pas en attendant une E/S ; il s'inscrit plutôt pour un événement (par ex., « données arrivées sur le socket ») et passe à d'autres tâches.
Le composant central est Netty, utilisé par défaut par WebFlux. Netty gère la boucle d'événements via EventLoopGroup. Le nombre de threads de boucle d'événements est déterminé par cette formule :
int DEFAULT_IO_WORKER_COUNT = Integer.parseInt(System.getProperty(
ReactorNetty.IO_WORKER_COUNT,
"" + Math.max(Runtime.getRuntime().availableProcessors(), 4)));
En d'autres termes, le nombre de threads est égal au nombre de processeurs disponibles, mais pas moins de 4. Dans un environnement conteneurisé, cette valeur peut être ajustée pour correspondre aux ressources allouées.
Chaque connexion réseau (Channel) est liée à une boucle d'événements spécifique (channel affinity). Cela garantit l'absence de surcharge de synchronisation et une efficacité du cache CPU. Toutefois, toute opération bloquante dans une boucle d'événements arrête le traitement pour toutes les connexions attachées à cette boucle.
Virtual Threads : Comment fonctionne Project Loom
Les virtual threads conservent le modèle « une requête — un thread » mais réduisent drastiquement les coûts en surcharge. Chaque requête obtient son propre virtual thread, qui s'exécute sur des carrier threads (platform threads). Les carrier threads sont gérés par un scheduler basé sur ForkJoinPool.
Le nombre par défaut de carrier threads est égal au nombre de processeurs disponibles. Par exemple, dans un conteneur avec 2 CPU, il y aura 2 carrier threads capables d'exécuter des milliers de virtual threads.
La méthode de création par défaut du scheduler :
private static ForkJoinPool createDefaultScheduler() {
ForkJoinWorkerThreadFactory factory = pool -> new CarrierThread(pool);
int parallelism, maxPoolSize, minRunnable;
String parallelismValue = System.getProperty("jdk.virtualThreadScheduler.parallelism");
String maxPoolSiz
Avantages des virtual threads :
- Préserve le style de programmation impérative
- Traces de pile lisibles
- Support de ThreadLocal
- Simplifie l'intégration avec les bibliothèques bloquantes existantes
Résultats des tests de charge à 2000 requêtes par seconde
Les tests sur le projet d'exemple de microservices ont révélé ce qui suit :
- WebFlux a montré des performances stables tant qu'il n'y avait pas d'opérations bloquantes dans la boucle d'événements. Cependant, l'intégration de bibliothèques bloquantes (par ex., JDBC) nécessitait de décharger ces opérations vers un pool de threads séparé (Schedulers.boundedElastic()), ce qui ajoutait de la complexité.
- Les virtual threads ont simplifié le développement et le débogage tout en offrant de hautes performances. À 2000 requêtes par seconde, les virtual threads sur Spring MVC et Tomcat ont égalé le débit de WebFlux, avec un code familier pour la plupart des développeurs Java.
Un facteur critique pour WebFlux était l'utilisation correcte des chaînes réactives et l'évitement des appels bloquants dans la boucle d'événements. Pour les virtual threads, notez qu'ils ne résolvent pas les opérations liées au CPU — une charge CPU élevée peut entraîner une contention pour les carrier threads.
Différences critiques et recommandations
Lors du choix entre WebFlux et les virtual threads, tenez compte de ces aspects :
- Type de charge de travail :
- Les deux approches excellent pour les applications liées à l'E/S (attente E/S lourde)
- Pour les tâches liées au CPU, les virtual threads peuvent être en retrait en raison de la contention des carrier threads
- Écosystème de bibliothèques :
- Si votre projet repose sur des bibliothèques bloquantes (JDBC), les virtual threads facilitent la migration
- Pour les nouveaux projets supportant des drivers réactifs (R2DBC), WebFlux reste viable
- Expertise de l'équipe :
- La programmation réactive exige une connaissance approfondie des patterns asynchrones
- Les virtual threads permettent un style impératif familier
Enseignements clés
- Performance à 2000 requêtes par seconde : Les deux gèrent la charge, mais les virtual threads simplifient le développement en restant dans le style impératif
- Limitations critiques : Une opération bloquante dans la boucle d'événements de WebFlux paralyse toutes les requêtes sur cette boucle ; les virtual threads ne corrigent pas les problèmes liés au CPU
- Écosystème : Adopter WebFlux signifie remplacer les bibliothèques bloquantes par des réactives, ce qui peut être coûteux
- Débogage : Les applications réactives sont plus difficiles à diagnostiquer en raison de traces de pile confuses, tandis que les virtual threads offrent des outils de profilage familiers
— Editorial Team
Aucun commentaire pour le moment.