Comparaison des conteneurs de servlets intégrés dans Spring Boot

1. Introduction

La popularité croissante des applications et des micro-services natifs du cloud génère une demande accrue de conteneurs de servlets intégrés. Spring Boot permet aux développeurs de créer facilement des applications ou des services à l'aide des 3 conteneurs les plus matures disponibles: Tomcat, Undertow et Jetty.

Dans ce didacticiel, nous allons montrer un moyen de comparer rapidement les implémentations de conteneurs à l'aide de métriques obtenues au démarrage et sous une certaine charge.

2. Dépendances

Notre configuration pour chaque implémentation de conteneur disponible exigera toujours que nous déclarions une dépendance sur spring-boot-starter-web dans notre pom.xml .

En général, nous voulons spécifier notre parent comme spring-boot-starter-parent , puis inclure les démarreurs que nous voulons:

 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE     org.springframework.boot spring-boot-starter   org.springframework.boot spring-boot-starter-web   

2.1. Matou

Aucune autre dépendance n'est requise lors de l'utilisation de Tomcat car elle est incluse par défaut lors de l'utilisation de spring-boot-starter-web .

2.2. Jetée

Pour utiliser Jetty, nous devons d'abord exclure spring-boot-starter-tomcat de spring-boot-starter-web .

Ensuite, nous déclarons simplement une dépendance sur spring-boot-starter-jetty :

 org.springframework.boot spring-boot-starter-web   org.springframework.boot spring-boot-starter-tomcat     org.springframework.boot spring-boot-starter-jetty  

2.3. Reflux

La configuration pour Undertow est identique à Jetty, sauf que nous utilisons spring-boot-starter-Undertow comme dépendance:

 org.springframework.boot spring-boot-starter-web   org.springframework.boot spring-boot-starter-tomcat     org.springframework.boot spring-boot-starter-undertow 

2.4. Actionneur

Nous utiliserons l'actionneur de Spring Boot comme un moyen pratique à la fois de stresser le système et de rechercher des métriques.

Consultez cet article pour plus de détails sur l'actionneur. Nous ajoutons simplement une dépendance dans notre pom pour le rendre disponible:

 org.springframework.boot spring-boot-starter-actuator 

2.5. Banc Apache

Apache Bench est un utilitaire de test de charge open source fourni avec le serveur Web Apache.

Les utilisateurs de Windows peuvent télécharger Apache auprès de l'un des fournisseurs tiers liés ici. Si Apache est déjà installé sur votre machine Windows, vous devriez pouvoir trouver ab.exe dans votre répertoire apache / bin .

Si vous êtes sur une machine Linux, ab peut être installé en utilisant apt-get avec:

$ apt-get install apache2-utils

3. Mesures de démarrage

3.1. Collection

Afin de collecter nos métriques de démarrage, nous enregistrerons un gestionnaire d'événements à déclencher sur ApplicationReadyEvent de Spring Boot .

Nous allons extraire par programme les métriques qui nous intéressent en travaillant directement avec le MeterRegistry utilisé par le composant Actuator:

@Component public class StartupEventHandler { // logger, constructor private String[] METRICS = { "jvm.memory.used", "jvm.classes.loaded", "jvm.threads.live"}; private String METRIC_MSG_FORMAT = "Startup Metric >> {}={}"; private MeterRegistry meterRegistry; @EventListener public void getAndLogStartupMetrics( ApplicationReadyEvent event) { Arrays.asList(METRICS) .forEach(this::getAndLogActuatorMetric); } private void processMetric(String metric) { Meter meter = meterRegistry.find(metric).meter(); Map stats = getSamples(meter); logger.info(METRIC_MSG_FORMAT, metric, stats.get(Statistic.VALUE).longValue()); } // other methods }

Nous évitons de devoir interroger manuellement les points de terminaison REST de l'actionneur ou d'exécuter une console JMX autonome en enregistrant des métriques intéressantes au démarrage dans notre gestionnaire d'événements.

3.2. Sélection

Actuator fournit un grand nombre de mesures prêtes à l'emploi. Nous avons sélectionné 3 métriques qui aident à obtenir un aperçu de haut niveau des principales caractéristiques d'exécution une fois que le serveur est opérationnel:

  • jvm.memory.used - la mémoire totale utilisée par la JVM depuis le démarrage
  • jvm.classes.loaded - le nombre total de classes chargées
  • jvm.threads.live - le nombre total de threads actifs. Dans notre test, cette valeur peut être considérée comme le nombre de threads «au repos»

4. Mesures d'exécution

4.1. Collection

En plus de fournir des métriques de démarrage, nous utiliserons le point de terminaison / metrics exposé par l'actionneur comme URL cible lorsque nous exécutons Apache Bench afin de mettre l'application sous charge.

Afin de tester une application réelle sous charge, nous pourrions plutôt utiliser les points de terminaison fournis par notre application.

Une fois le serveur démarré, nous aurons une invite de commande et exécuterons ab :

ab -n 10000 -c 10 //localhost:8080/actuator/metrics

Dans la commande ci-dessus, nous avons spécifié un total de 10000 requêtes utilisant 10 threads simultanés.

4.2. Sélection

Apache Bench is able to very quickly give us some useful information including connection times and the percentage of requests that are served within a certain time.

For our purposes, we focused on requests-per-second and time-per-request (mean).

5. Results

On startup, we found that the memory footprint of Tomcat, Jetty, and Undertow was comparable with Undertow requiring slightly more memory than the other two and Jetty requiring the smallest amount.

For our benchmark, we found that the performance of Tomcat, Jetty, and Undertow was comparable but that Undertow was clearly the fastest and Jetty only slightly less fast.

Metric Tomcat Jetty Undertow
jvm.memory.used (MB) 168 155 164
jvm.classes.loaded 9869 9784 9787
jvm.threads.live 25 17 19
Requests per second 1542 1627 1650
Average time per request (ms) 6.483 6.148 6.059

Note that the metrics are, naturally, representative of the bare-bones project; the metrics of your own application will most certainly be different.

6. Benchmark Discussion

Developing appropriate benchmark tests to perform thorough comparisons of server implementations can get complicated. In order to extract the most relevant information, it's critical to have a clear understanding of what's important for the use case in question.

It's important to note that the benchmark measurements collected in this example were taken using a very specific workload consisting of HTTP GET requests to an Actuator endpoint.

It's expected that different workloads would likely result in different relative measurements across container implementations. If more robust or precise measurements were required, it would be a very good idea to set up a test plan that more closely matched the production use case.

In addition, a more sophisticated benchmarking solution such as JMeter or Gatling would likely yield more valuable insights.

7. Choosing a Container

Selecting the right container implementation should likely be based on many factors that can't be neatly summarized with a handful of metrics alone. Comfort level, features, available configuration options, and policy are often equally important, if not more so.

8. Conclusion

Dans cet article, nous avons examiné les implémentations de conteneur de servlet intégré Tomcat, Jetty et Undertow. Nous avons examiné les caractéristiques d'exécution de chaque conteneur au démarrage avec les configurations par défaut en examinant les métriques exposées par le composant Actuator.

Nous avons exécuté une charge de travail artificielle sur le système en cours d'exécution, puis mesuré les performances à l'aide d'Apache Bench.

Enfin, nous avons discuté des mérites de cette stratégie et mentionné quelques points à garder à l'esprit lors de la comparaison des repères de mise en œuvre. Comme toujours, tout le code source peut être trouvé sur GitHub.