1. 概述

随着云原生应用和微服务架构的普及,对嵌入式 Servlet 容器的需求日益增长。Spring Boot 默认支持三种成熟稳定的容器:Tomcat、Jetty 和 Undertow,开发者可以轻松切换使用。

本文将通过启动时和负载下的性能指标,快速对比这三种容器的实际表现。我们不会堆砌理论,而是用数据说话,帮你避开选型时的常见“坑”。

2. 依赖配置

所有测试均基于 spring-boot-starter-web,这是 Web 应用的基础依赖。我们使用 Spring Boot 3.1.5 版本,结构清晰,开箱即用。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.5</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

✅ 默认容器,无需额外配置。spring-boot-starter-web 已自动包含 spring-boot-starter-tomcat,直接启动即可。

2.2. Jetty

要替换为 Jetty,需先排除 Tomcat 依赖,再引入 Jetty starter:

<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

配置方式与 Jetty 一致,仅替换 starter 名称:

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

⚠️ 注意:排除依赖时务必确认 artifactId 正确,否则会同时加载多个容器,引发端口冲突或线程资源浪费。

2.4. Actuator

使用 Spring Boot Actuator 暴露运行时指标,便于监控和压测。添加以下依赖:

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

启动后可通过 /actuator/metrics 等接口获取 JVM 和系统指标。

2.5. Apache Bench

使用 ab(Apache Bench)进行简单粗暴的压测。Linux 用户安装方式:

$ apt-get install apache2-utils

Windows 用户可从 Apache 官方推荐的第三方发行版下载,ab.exe 位于 bin 目录下。

3. 启动性能指标

3.1. 指标采集

我们通过监听 ApplicationReadyEvent 事件,在应用启动完成后自动采集关键指标,避免手动调用接口或使用 JMX 工具。

核心代码如下:

@Component
public class StartupEventHandler {

    private static final String[] METRICS = {
        "jvm.memory.used", 
        "jvm.classes.loaded", 
        "jvm.threads.live"
    };
    
    private static final String METRIC_MSG_FORMAT = "Startup Metric >> {}={}";
    
    private final Logger logger = LoggerFactory.getLogger(StartupEventHandler.class);
    private final MeterRegistry meterRegistry;

    public StartupEventHandler(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    @EventListener
    public void getAndLogStartupMetrics(ApplicationReadyEvent event) {
        Arrays.asList(METRICS).forEach(this::processMetric);
    }

    private void processMetric(String metricName) {
        Meter meter = meterRegistry.find(metricName).meter();
        if (meter != null) {
            Double value = meter.measure().iterator().next().getValue();
            logger.info(METRIC_MSG_FORMAT, metricName, value.longValue());
        }
    }
}

✅ 优点:无需暴露敏感接口,指标自动记录到日志,适合 CI/CD 流程。

3.2. 指标选择

我们关注三个核心指标,反映容器启动后的资源占用情况:

  • jvm.memory.used:JVM 已使用内存总量(MB)
  • jvm.classes.loaded:已加载类数量,反映初始化开销
  • jvm.threads.live:活跃线程数,体现容器线程模型差异

这些指标能快速判断容器“轻量级”程度。

4. 运行时性能指标

4.1. 压测方式

使用 Apache Bench 对 /actuator/metrics 接口施加压力,命令如下:

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

参数说明:

  • -n 10000:总请求数
  • -c 10:并发数

⚠️ 注意:实际项目应压测业务接口,此处仅为统一测试基准。

4.2. 关键指标

ab 输出中我们重点关注:

  • Requests per second:每秒处理请求数,越高越好
  • **Time per request (mean)**:单请求平均耗时(ms),越低越好

这两个指标直接反映吞吐能力和响应延迟。

5. 测试结果

以下数据基于默认配置的空项目,仅供参考。实际业务中差异可能更显著。

指标 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

结论速览:

  • 内存占用:Jetty 最小,Undertow 次之,Tomcat 略高
  • 启动线程数:Jetty 最少(17),资源更轻量
  • 吞吐性能:Undertow > Jetty > Tomcat,Undertow 领先约 7%
  • 延迟:Undertow 平均延迟最低,响应更快

⚠️ 提示:测试基于 Actuator 接口的 GET 请求,I/O 密集型场景下 Undertow 的非阻塞模型优势更明显。

6. 压测分析

  • ** workload 的影响**:本次测试为简单 GET 请求,若涉及复杂业务逻辑或数据库操作,结果可能不同。
  • 工具局限性ab 简单直接,但无法模拟复杂场景。如需更精确结果,建议使用 JMeter 或 Gatling。
  • 生产环境匹配:务必用接近生产流量的测试用例,避免“纸上谈兵”。

7. 容器选型建议

不要仅凭几个数字做决定。实际选型应综合考虑:

  • 团队熟悉度:Tomcat 文档丰富,排查问题成本低
  • 功能需求:如需 WebSocket、长连接,Jetty 和 Undertow 支持更好
  • 性能要求:高并发、低延迟场景优先考虑 Undertow
  • 生态兼容性:某些中间件对 Tomcat 适配最完善
  • 策略限制:公司技术规范可能强制使用特定容器

简单粗暴总结:

  • 求稳选 Tomcat
  • 要轻量选 Jetty
  • 要性能选 Undertow

8. 总结

本文通过实际指标对比了 Spring Boot 内置的三大 Servlet 容器。在默认配置下:

  • Undertow 启动内存适中,运行时性能最优
  • Jetty 内存和线程占用最小,适合资源受限环境
  • Tomcat 综合表现均衡,生态最成熟

最终选择应结合业务场景、团队能力和运维策略。没有最好的容器,只有最适合的方案

所有示例代码已托管至 GitHub:https://github.com/yourname/spring-boot-container-benchmark


原始标题:Comparing Embedded Servlet Containers in Spring Boot