1. Introduction
1.绪论
The rising popularity of cloud-native applications and micro-services generate an increased demand for embedded servlet containers. Spring Boot allows developers to easily build applications or services using the 3 most mature containers available: Tomcat, Undertow, and Jetty.
云原生应用和微服务的不断普及,产生了对嵌入式Servlet容器的更多需求。Spring Boot允许开发人员使用现有的3个最成熟的容器,轻松构建应用程序或服务。Tomcat、Undertow和Jetty。
In this tutorial, we’ll demonstrate a way to quickly compare container implementations using metrics obtained at startup and under some load.
在本教程中,我们将展示一种方法,使用在启动时和一些负载下获得的指标来快速比较容器的实现。
2. Dependencies
2.依赖性
Our setup for each available container implementation will always require that we declare a dependency on spring-boot-starter-web in our pom.xml.
我们对每个可用的容器实现的设置将始终要求我们在pom.xml中声明对spring-boot-starter-web的依赖。
In general, we want to specify our parent as spring-boot-starter-parent, and then include the starters we want:
一般来说,我们要把我们的父本指定为spring-boot-starter-parent,然后包括我们想要的启动器。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2.1. Tomcat
2.1. Tomcat
No further dependencies are required when using Tomcat because it is included by default when using spring-boot-starter-web.
使用Tomcat时不需要进一步的依赖,因为在使用spring-boot-starter-web时,它被默认包含在其中。
2.2. Jetty
2.2.
In order to use Jetty, we first need to exclude spring-boot-starter-tomcat from spring-boot-starter-web.
为了使用Jetty,我们首先需要将spring-boot-starter-tomcat从spring-boot-starter-web中排除。
Then, we simply declare a dependency on spring-boot-starter-jetty:
然后,我们简单地声明对spring-boot-starter-jetty的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
2.3. Undertow
2.3.暗流涌动
Setting up for Undertow is identical to Jetty, except that we use spring-boot-starter-undertow as our dependency:
Undertow的设置与Jetty相同,只是我们使用spring-boot-starter-undertow作为我们的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
2.4. Actuator
2.4. 执行器
We’ll use Spring Boot’s Actuator as a convenient way to both stress the system and query for metrics.
我们将使用Spring Boot的Actuator作为一种方便的方式,既能给系统施加压力,又能查询指标。
Check out this article for details on Actuator. We simply add a dependency in our pom to make it available:
请查看这篇文章,了解有关Actuator的详细信息。我们只需在我们的pom中添加一个依赖项,使其可用。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.5. Apache Bench
2.5.阿帕奇工作台
Apache Bench is an open source load testing utility that comes bundled with the Apache web server.
Apache Bench是一个开源的负载测试工具,与Apache网络服务器捆绑在一起。
Windows users can download Apache from one of the 3rd party vendors linked here. If Apache is already installed on your Windows machine, you should be able to find ab.exe in your apache/bin directory.
Windows用户可以从链接此处的第三方供应商之一下载Apache。如果Apache已经安装在您的Windows机器上,您应该能够在您的apache/bin目录中找到ab.exe。
If you are on a Linux machine, ab can be installed using apt-get with:
如果你在Linux机器上,ab可以用apt-get来安装。
$ apt-get install apache2-utils
3. Startup Metrics
3.初始化指标
3.1. Collection
3.1.采集
In order to collect our startup metrics, we’ll register an event handler to fire on Spring Boot’s ApplicationReadyEvent.
为了收集我们的启动指标,我们将注册一个事件处理程序,在Spring Boot的ApplicationReadyEvent上启动。
We’ll programmatically extract the metrics we’re interested in by directly working with the MeterRegistry used by the Actuator component:
我们将通过直接使用Actuator组件使用的MeterRegistry,以编程方式提取我们感兴趣的指标。
@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<Statistic, Double> stats = getSamples(meter);
logger.info(METRIC_MSG_FORMAT, metric, stats.get(Statistic.VALUE).longValue());
}
// other methods
}
We avoid the need to manually query Actuator REST endpoints or to run a standalone JMX console by logging interesting metrics on startup within our event handler.
我们通过在事件处理程序中记录启动时的有趣指标,避免了手动查询Actuator REST端点或运行一个独立的JMX控制台。
3.2. Selection
3.2.选择
There are a large number of metrics that Actuator provides out of the box. We selected 3 metrics that help to get a high-level overview of key runtime characteristics once the server is up:
Actuator提供了大量的开箱即用的指标。我们选择了3个指标,这些指标有助于在服务器启动后对关键的运行时特性有一个高层次的概述。
- jvm.memory.used – the total memory used by the JVM since startup
- jvm.classes.loaded – the total number of classes loaded
- jvm.threads.live – the total number of active threads. In our test, this value can be viewed as the thread count “at rest”
4. Runtime Metrics
4.运行时指标
4.1. Collection
4.1.采集
In addition to providing startup metrics, we’ll use the /metrics endpoint exposed by the Actuator as the target URL when we run Apache Bench in order to put the application under load.
除了提供启动指标外,当我们运行Apache Bench时,我们将使用Actuator暴露的/metrics端点作为目标URL,以使应用程序处于负载状态。
In order to test a real application under load, we might instead use endpoints provided by our application.
为了在负载下测试一个真正的应用程序,我们可能会使用我们的应用程序提供的端点。
Once the server has started, we’ll get a command prompt and execute ab:
一旦服务器启动,我们将得到一个命令提示符并执行ab。
ab -n 10000 -c 10 http://localhost:8080/actuator/metrics
In the command above, we’ve specified a total of 10,000 requests using 10 concurrent threads.
在上面的命令中,我们指定了使用10个并发线程的总共10,000个请求。
4.2. Selection
4.2.选择
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.
Apache Bench能够非常迅速地给我们提供一些有用的信息,包括连接时间和在一定时间内得到服务的请求的百分比。
For our purposes, we focused on requests-per-second and time-per-request (mean).
为了我们的目的,我们专注于每秒请求数和每次请求时间(平均值)。
5. Results
5.结果
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.
在启动时,我们发现Tomcat、Jetty和Undertow的内存占用相当,Undertow需要的内存略多于其他两个,Jetty需要的内存最少。
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.
在我们的基准测试中,我们发现Tomcat、Jetty和Undertow的性能相当,但Undertow显然是最快的,Jetty只是稍逊一筹。
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
6.基准讨论
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.
值得注意的是,在这个例子中收集的基准测量值是使用一个非常具体的工作负载,包括对一个执行器端点的HTTP GET请求。
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.
此外,更复杂的基准测试解决方案,如JMeter或Gatling,可能会产生更有价值的洞察力。
7. Choosing a Container
7.选择容器
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
8.结语
In this article, we looked at the Tomcat, Jetty, and Undertow embedded servlet container implementations. We examined the runtime characteristics of each container at startup with the default configurations by looking at metrics exposed by the Actuator component.
在这篇文章中,我们研究了Tomcat、Jetty和Undertow等嵌入式servlet容器的实现。我们通过查看Actuator组件所暴露的指标,检查了每个容器在默认配置下的启动时的运行特性。
We executed a contrived workload against the running system and then measured performance using Apache Bench.
我们对运行中的系统执行了一个假想的工作负载,然后用Apache Bench测量性能。
Lastly, we discussed the merits of this strategy and mentioned a few things to keep in mind when comparing implementation benchmarks. As always, all source code can be found over on GitHub.
最后,我们讨论了这一策略的优点,并提到了在比较实施基准时需要注意的几件事。一如既往,所有的源代码都可以在GitHub上找到over。