1. 简介



云原生应用程序和微服务的日益普及导致对嵌入式 servlet 容器的需求增加。Spring Boot 允许开发人员使用 3 个最成熟的可用容器轻松构建应用程序或服务:Tomcat、Undertow 和 Jetty。

在本教程中,我们将演示一种使用在启动时和某些负载下获得的指标来快速比较容器实现的方法。

2. 依赖



我们对每个可用容器实现的设置总是要求我们在 pom.xml 中声明对spring boot-starter-web的依赖。

一般来说,我们希望将我们的父级指定为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



使用 Tomcat 时不需要其他依赖项,因为使用spring-boot-starter-web时默认包含它。



2.2. Jetty



为了使用 Jetty,我们首先需要从spring-boot-starter-web中排除spring-boot-starter-tomcat

然后,我们简单地声明一个对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



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. 执行器



我们将使用 Spring Boot 的 Actuator 作为一种方便的方式来给系统施加压力并查询指标。

查看 这篇文章以了解有关执行器的详细信息。我们只需在pom中添加一个依赖项即可使其可用:



<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

 2.5. 阿帕奇长凳



Apache Bench 是与 Apache Web 服务器捆绑在一起的开源负载测试实用程序。

Windows 用户可以从此处链接的第 3 方供应商之一下载 Apache 。如果 Apache 已经安装在您的 Windows 机器上,您应该能够在apache/bin目录中找到ab.exe 。

如果您在 Linux 机器上,可以使用apt-get安装ab

$ apt-get install apache2-utils

3. 启动指标



3.1。收藏



为了收集我们的启动指标,我们将注册一个事件处理程序以在 Spring Boot 的ApplicationReadyEvent上触发。

我们将通过直接使用 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
}

通过在我们的事件处理程序中记录启动时有趣的指标,我们避免了手动查询 Actuator REST 端点或运行独立 JMX 控制台的需要。

3.2. 选择



Actuator 提供了大量开箱即用的指标。我们选择了 3 个指标,这些指标有助于在服务器启动后获得关键运行时特征的高级概述:

  • jvm.memory.used – JVM 自启动以来使用的总内存
  • jvm.classes.loaded – 加载的类总数
  • jvm.threads.live – 活动线程的总数。在我们的测试中,这个值可以被视为“静止”的线程数

4. 运行时指标



4.1。收藏



除了提供启动指标外,我们还将在运行 Apache Bench 时使用 Actuator 公开的/metrics 端点作为目标 URL,以便使应用程序处于负载状态。

为了测试负载下的真实应用程序,我们可能会改用应用程序提供的端点。

服务器启动后,我们将获得命令提示符并执行ab

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

在上面的命令中,我们使用 10 个并发线程指定了总共 10,000 个请求。

4.2. 选择



Apache Bench 能够非常快速地为我们提供一些有用的信息,包括连接时间和在特定时间内服务的请求百分比。

出于我们的目的,我们专注于每秒请求数和每次请求时间(平均值)。



5. 结果



在启动时,我们发现Tomcat、Jetty 和 Undertow 的内存占用量与 Undertow 的内存占用量相比其他两个稍多,而 Jetty 的内存占用量最少。

在我们的基准测试中,我们发现Tomcat、Jetty 和 Undertow 的性能相当,但Undertow 显然是最快的,而 Jetty 的速度稍差一些。 

公制

雄猫

码头

暗流

jvm.memory.used (MB)

168

155

164

jvm.classes.loaded

9869

9784

9787

jvm.threads.live

25

17

19

每秒请求数

1542

1627

1650

每个请求的平均时间(毫秒)

6.483

6.148

6.059

请注意,这些指标自然代表了准系统项目;您自己的应用程序的指标肯定会有所不同。

6. 基准讨论



开发适当的基准测试来对服务器实现进行全面比较可能会变得复杂。为了提取最相关的信息,清楚地了解对所讨论的用例来说什么是重要的,这一点至关重要。

请务必注意,本示例中收集的基准测量是使用非常特定的工作负载进行的,该工作负载由对 Actuator 端点的 HTTP GET 请求组成。

预计不同的工作负载可能会导致跨容器实现的不同相对测量。如果需要更稳健或更精确的测量,最好制定一个与生产用例更匹配的测试计划。

此外,更复杂的基准测试解决方案(如JMeterGatling)可能会产生更有价值的见解。

7. 选择容器



选择正确的容器实现可能应该基于许多因素,这些因素不能仅用少数指标来概括。舒适度、功能、可用配置选项和策略通常同样重要,甚至更重要。



8. 结论



在本文中,我们研究了 Tomcat、Jetty 和 Undertow 嵌入式 servlet 容器实现。我们通过查看 Actuator 组件公开的指标,在默认配置下检查了每个容器在启动时的运行时特性。

我们针对正在运行的系统执行了一个人为的工作负载,然后使用 Apache Bench 测量了性能。