Vergleich eingebetteter Servlet-Container im Spring Boot

1. Einleitung

Die zunehmende Beliebtheit von Cloud-nativen Anwendungen und Mikrodiensten führt zu einer erhöhten Nachfrage nach eingebetteten Servlet-Containern. Mit Spring Boot können Entwickler auf einfache Weise Anwendungen oder Dienste mit den drei ausgereiftesten verfügbaren Containern erstellen: Tomcat, Undertow und Jetty.

In diesem Tutorial zeigen wir Ihnen, wie Sie Containerimplementierungen mithilfe von Metriken, die beim Start und unter Last ermittelt wurden, schnell vergleichen können.

2. Abhängigkeiten

Unser Setup für jede verfügbare Container-Implementierung erfordert immer, dass wir in unserer pom.xml eine Abhängigkeit von Spring-Boot-Starter-Web deklarieren .

Im Allgemeinen möchten wir unser Elternteil als Spring-Boot-Starter-Elternteil angeben und dann die gewünschten Starter einschließen:

 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. Kater

Bei Verwendung von Tomcat sind keine weiteren Abhängigkeiten erforderlich, da diese bei Verwendung von Spring-Boot-Starter-Web standardmäßig enthalten sind .

2.2. Steg

Um Jetty nutzen zu können, müssen wir zunächst den Spring-Boot-Starter-Tomcat vom Spring-Boot-Starter-Web ausschließen .

Dann deklarieren wir einfach eine Abhängigkeit vom Spring-Boot-Starter-Steg :

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

2.3. Sog

Die Einrichtung für Undertow ist identisch mit Jetty, außer dass wir Spring-Boot-Starter-Sog als Abhängigkeit verwenden:

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

2.4. Aktuator

Wir werden den Aktuator von Spring Boot verwenden, um das System zu belasten und Metriken abzufragen.

In diesem Artikel finden Sie Details zum Aktuator. Wir fügen einfach eine Abhängigkeit in unseren POM ein , um ihn verfügbar zu machen:

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

2.5. Apache Bank

Apache Bench ist ein Open Source-Dienstprogramm zum Testen der Last, das im Lieferumfang des Apache-Webservers enthalten ist.

Windows-Benutzer können Apache von einem der hier verlinkten Drittanbieter herunterladen. Wenn Apache bereits auf Ihrem Windows-Computer installiert ist, sollten Sie ab.exe in Ihrem Apache / bin- Verzeichnis finden können.

Wenn Sie sich auf einem Linux-Computer befinden, kann ab mit apt-get installiert werden mit:

$ apt-get install apache2-utils

3. Startmetriken

3.1. Sammlung

Um unsere Startmetriken zu erfassen, registrieren wir einen Ereignishandler, der auf ApplicationReadyEvent von Spring Boot ausgelöst wird .

Wir extrahieren programmgesteuert die Metriken, an denen wir interessiert sind, indem wir direkt mit der von der Actuator-Komponente verwendeten MeterRegistry arbeiten :

@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 }

Wir vermeiden die Notwendigkeit, Actuator-REST-Endpunkte manuell abzufragen oder eine eigenständige JMX-Konsole auszuführen, indem wir interessante Metriken beim Start in unserem Ereignishandler protokollieren.

3.2. Auswahl

Es gibt eine große Anzahl von Metriken, die Actuator sofort bereitstellt. Wir haben drei Metriken ausgewählt, die Ihnen helfen, einen allgemeinen Überblick über die wichtigsten Laufzeitmerkmale zu erhalten, sobald der Server hochgefahren ist:

  • jvm.memory.used - der Gesamtspeicher, der von der JVM seit dem Start verwendet wird
  • jvm.classes.loaded - Die Gesamtzahl der geladenen Klassen
  • jvm.threads.live - die Gesamtzahl der aktiven Threads. In unserem Test kann dieser Wert als die Anzahl der Threads "in Ruhe" angesehen werden.

4. Laufzeitmetriken

4.1. Sammlung

Zusätzlich zur Bereitstellung von Startmetriken verwenden wir den vom Actuator bereitgestellten Endpunkt /metrics als Ziel-URL, wenn wir Apache Bench ausführen, um die Anwendung unter Last zu setzen.

Um eine reale Anwendung unter Last zu testen, können wir stattdessen Endpunkte verwenden, die von unserer Anwendung bereitgestellt werden.

Sobald der Server gestartet wurde, erhalten wir eine Eingabeaufforderung und führen ab :

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

Im obigen Befehl haben wir insgesamt 10.000 Anforderungen mit 10 gleichzeitigen Threads angegeben.

4.2. Auswahl

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

In diesem Artikel haben wir uns die eingebetteten Servlet-Container-Implementierungen von Tomcat, Jetty und Undertow angesehen. Wir haben die Laufzeitmerkmale jedes Containers beim Start mit den Standardkonfigurationen untersucht, indem wir uns die von der Actuator-Komponente bereitgestellten Metriken angesehen haben.

Wir haben eine erfundene Arbeitslast für das laufende System ausgeführt und dann die Leistung mit Apache Bench gemessen.

Zuletzt haben wir die Vorzüge dieser Strategie erörtert und einige Dinge erwähnt, die beim Vergleich der Implementierungsbenchmarks zu beachten sind. Wie immer ist der gesamte Quellcode auf GitHub zu finden.