Tomcat与Jetty深度对比

1. 引言:为什么还要讨论 Tomcat 与 Jetty

在 Spring Boot 内嵌容器、云原生与虚拟线程(Virtual Threads)快速演进的今天,Servlet 容器似乎成了"默认就够用"的基础设施。但一旦遇到高并发长连接、WebSocket 推送、内存受限的边缘部署、或者需要精细控制 HTTP 解析行为的场景,选 Tomcat 还是 Jetty 就会真实地影响交付质量与运维成本。

本文基于 Apache Tomcat 与 Eclipse Jetty 的官方文档、官方 Wiki/FAQ、Release Notes,以及业界公开的工程实践与踩坑记录,系统梳理两者的设计取向、优缺点、适用场景、生态关系、资料丰富度与学习曲线,并给出新项目交付时的选型建议。

说明:本文结论基于两个项目长期公开的架构文档与社区实践总结,具体版本行为请以对应版本的官方文档为准。

2. 两者的定位与架构取向

2.1 Apache Tomcat:Servlet 规范的"参考实现"

Tomcat 由 Apache 软件基金会维护,长期作为 Jakarta Servlet、JSP、WebSocket 等规范的参考实现(Reference Implementation)之一。它的设计目标非常明确:完整、稳定、规范兼容性优先。

  • 核心组件:Server → Service → Connector + Engine → Host → Context → Wrapper,这套容器层级模型几乎成了 Java Web 容器的教科书结构。
  • 连接器:经典的阻塞式 BIO(已移除)、NIO、NIO2、APR(基于 Tomcat Native/OpenSSL)。
  • 配置方式:server.xml、context.xml、web.xml 三层配置,配合 JNDI、Realm、Valve 等扩展点。

2.2 Eclipse Jetty:可嵌入的 HTTP 服务器与 Servlet 容器

Jetty 由 Eclipse 基金会维护,定位更偏向可嵌入的、模块化的 HTTP 服务器 + Servlet 容器。它的设计哲学是"小而可组合":

  • 核心抽象是 Server → Connector → Handler,Handler 链式组合,Servlet 只是其中一种 Handler 实现。
  • 高度模块化:jetty-server、jetty-servlet、jetty-http2、jetty-websocket、jetty-alpn 等按需引入,依赖体积可控。
  • 内嵌友好:几行代码即可启动一个带 Servlet 或纯 Handler 的服务,天然适合作为库被其他框架嵌入。

一句话概括取向差异:Tomcat 是"把规范实现到极致"的容器,Jetty 是"把 HTTP 服务做成积木"的工具箱。

3. 架构与运行模型对比

维度 Tomcat Jetty
维护组织 Apache 软件基金会 Eclipse 基金会
核心模型 容器层级(Engine/Host/Context/Wrapper) Handler 链 + Connector
I/O 模型 NIO / NIO2 / APR NIO(Jetty 9+ 统一为 NIO,支持 ALPN)
线程模型 连接器线程池 + 容器线程池分离 基于 QueuedThreadPool 的统一线程池 + 可配置策略
协议支持 HTTP/1.1、HTTP/2(需配置)、AJP、WebSocket HTTP/1.1、HTTP/2、HTTP/3(Jetty 12 起)、WebSocket、ALPN
配置风格 XML 为主,编程式为辅 编程式/代码优先,XML 可选
热部署 支持(autoDeploy、reloadable) 支持(DeploymentManager、ContextProvider)
模块化 单体发行包 + 可选模块 细粒度 Maven 模块,按需组合

3.1 线程模型差异的实际影响

Tomcat 的连接器线程(Acceptor/Poller)与业务线程池(Executor)职责分离清晰,默认配置下对突发流量有较好的缓冲能力;Jetty 的 QueuedThreadPool 采用更激进的线程复用策略,在大量空闲长连接场景下内存与线程开销通常更小,这也是 Jetty 常被用于 WebSocket 推送、SSE、长轮询服务的原因之一。

3.2 协议演进节奏

Jetty 在 HTTP/2、HTTP/3(QUIC)等新协议上的跟进通常更早,Jetty 12 已提供 HTTP/3 支持;Tomcat 对 HTTP/2 的支持成熟稳定,但新协议引入相对保守。若项目对前沿协议有明确需求,Jetty 的迭代节奏更友好。

4. Tomcat 的优点与缺点

4.1 优点

  • 规范兼容性最强:作为 Servlet/JSP/WebSocket 规范的参考实现,行为最贴近规范文本,迁移与兼容风险低。
  • 生态与文档极其丰富:官方文档、FAQ、Wiki、迁移指南齐全;Stack Overflow、中文社区、各类教程与书籍数量远超 Jetty,遇到问题几乎都能搜到答案。
  • 运维与监控成熟:JMX 指标、Manager/Host Manager 应用、访问日志、Valve 机制完善,与各类 APM、监控体系集成经验丰富。
  • 企业级特性完整:Realm 认证、JNDI 资源、集群会话复制、AJP 与前端 Web 服务器集成等开箱可用。
  • 人才供给充足:绝大多数 Java 后端开发者默认熟悉 Tomcat,团队接手成本低。
  • Spring Boot 默认容器:默认依赖、默认配置、默认调优资料都围绕 Tomcat 展开,出问题时的"默认路径"最短。

4.2 缺点

  • 体积与启动开销偏大:完整发行包与容器层级初始化较重,冷启动与内存占用在资源受限环境下不占优势。
  • 内嵌体验一般:虽然支持编程式启动,但 API 设计以"部署 Web 应用"为中心,作为库嵌入其他进程时不如 Jetty 自然。
  • 配置偏重 XML :server.xml 的层级配置对新手不够直观,调优参数分散在连接器、Executor、Valve 多处。
  • 长连接场景资源效率一般:海量空闲连接下的线程与内存开销通常高于 Jetty。
  • 新协议跟进较保守:HTTP/3 等前沿协议支持节奏慢于 Jetty。

5. Jetty 的优点与缺点

5.1 优点

  • 极致的内嵌与模块化:按需引入模块,依赖体积小,几行代码即可启动服务,非常适合中间件、工具、测试框架、IDE 插件等"把 HTTP 服务塞进进程"的场景。
  • Handler 模型灵活:不依赖 Servlet 也能构建 HTTP 服务,路由与过滤器可用 Handler 组合实现,控制粒度细。
  • 长连接与高并发连接效率高:线程池与连接管理策略对 WebSocket、SSE、长轮询友好,连接数上限表现优秀。
  • 新协议跟进快:HTTP/2、ALPN、HTTP/3(Jetty 12)支持积极。
  • 代码优先、可编程性强:配置即代码,便于在容器化、动态编排环境中做程序化定制。
  • 与云原生/嵌入式场景契合:被大量框架与工具选作内嵌服务器(如部分构建工具、测试容器、微服务框架)。

5.2 缺点

  • 文档与社区资料相对少:官方文档质量高但偏"参考手册"风格,缺少大量面向业务的教程;中文资料明显少于 Tomcat,遇到冷门问题排查成本更高。
  • 版本演进带来迁移成本 :Jetty 9 → 10 → 11 → 12 的包名(javax.* → jakarta.*)、模块拆分与 API 调整较大,跨大版本升级需要认真评估。
  • 默认配置偏"轻":不少企业级能力(会话持久化、安全策略、监控)需要自行组合模块,开箱即用的完整度不如 Tomcat。
  • 运维经验积累较少:团队若没有 Jetty 运维经验,监控指标含义、线程池调优、连接泄漏排查的学习成本更高。
  • 人才储备较少:熟悉 Jetty 内部机制的开发者比例明显低于 Tomcat。

6. 适用场景对比

场景 推荐 理由
传统单体 Web 应用 / 企业后台 Tomcat 规范完整、JSP 支持、运维成熟、资料丰富
Spring Boot 常规业务服务 Tomcat(默认) 默认路径最短,调优资料最多
高并发 WebSocket / SSE 推送服务 Jetty 长连接资源效率高,Handler 模型灵活
中间件、SDK、测试框架内嵌 HTTP Jetty 体积小、可嵌入、模块化
资源受限的边缘/嵌入式部署 Jetty 启动快、内存占用低
需要 HTTP/3 或前沿协议 Jetty 协议跟进更快
需要 AJP 对接 Apache/Nginx 前端 Tomcat AJP 支持成熟
需要集群会话复制、JNDI、Realm Tomcat 企业级特性开箱可用
需要精细控制 HTTP 解析与路由 Jetty Handler 链控制粒度细
团队以 Java Web 传统栈为主 Tomcat 学习曲线平缓,人才易得

7. 与周边生态的关系

7.1 与 Spring 生态

  • Spring Boot 默认使用 Tomcat,spring-boot-starter-web 直接引入 tomcat-embed-core;切换到 Jetty 只需排除 Tomcat 并引入 spring-boot-starter-jetty。
  • 两者都通过 ServletWebServerFactory 抽象接入,Spring 层的差异很小,真正的差异体现在容器层行为与调优参数上。
  • Spring Boot 3 起全面转向 jakarta.* 命名空间,Tomcat 10+ 与 Jetty 11+ 才兼容,选型时需与 Spring Boot 大版本对齐。
7.1.1 依赖切换:pom.xml 中排除 Tomcat、引入 Jetty

Spring Boot 3 的 spring-boot-starter-web 默认传递引入 spring-boot-starter-tomcat,切换容器时必须显式排除,否则两个容器会同时出现在 classpath 上,Spring Boot 会按优先级选中 Tomcat,导致"改了依赖却没生效"的经典问题。

xml 复制代码
<dependencies>
    <!-- Web starter:排除默认的 Tomcat -->
    <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>

    <!-- 引入 Jetty(Spring Boot 3 对应 Jetty 11/12,jakarta.* 命名空间) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>

提示:如果项目还引入了 spring-boot-starter-websocket、spring-boot-starter-security 等间接依赖 Tomcat 的 starter,同样需要检查 mvn dependency:tree 输出,确认 tomcat-embed-* 已被彻底排除。

7.1.2 配置对照:server.tomcat.* 与 server.jetty.*

两者的调优参数语义并不一一对应,下表只列出最常调整的几组,切勿直接照搬数值:

关注点 Tomcat 配置 Jetty 配置 说明
最大工作线程数 server.tomcat.threads.max server.jetty.threads.max 语义相近,但 Jetty 线程池同时承担连接器与业务处理
最小空闲线程数 server.tomcat.threads.min-spare server.jetty.threads.min Jetty 的 min 是线程池下限,不是"预留空闲线程"
最大连接数 server.tomcat.max-connections server.jetty.max-connections(Jetty 12 起) 老版本 Jetty 需通过 Connector 编程式设置
请求头大小 server.tomcat.max-http-form-post-size / max-swallow-size server.jetty.max-http-form-post-size 表单与请求体上限,需按业务调整
空闲超时 server.tomcat.connection-timeout server.jetty.connection-idle-timeout 单位均为 Duration,注意写法如 30s
访问日志 server.tomcat.accesslog.enabled server.jetty.accesslog.enabled 两者都支持,但日志格式字段名不同
yaml 复制代码
# application.yml ------ 以 Jetty 为例
server:
  port: 8080
  jetty:
    threads:
      max: 200          # 工作线程上限
      min: 20           # 线程池下限
    max-connections: 10000
    connection-idle-timeout: 30s
    accesslog:
      enabled: true
      filename: access
      retention-period: 7

# 若切回 Tomcat,对应写法如下(注意参数名不同)
# server:
#   tomcat:
#     threads:
#       max: 200
#       min-spare: 20
#     max-connections: 10000
#     connection-timeout: 30s
#     accesslog:
#       enabled: true
#       prefix: access
#       suffix: .log
7.1.3 编程式定制:WebServerFactoryCustomizer

当 YAML 无法覆盖某些容器特有参数(例如 Jetty 的 QueuedThreadPool 名称、Tomcat 的 Connector 属性)时,可以用 WebServerFactoryCustomizer 做类型安全的编程式定制。下面的写法同时兼容两种容器,切换时无需改代码:

java 复制代码
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ContainerCustomizerConfig {

    @Bean
    public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> containerCustomizer() {
        return factory -> {
            factory.setPort(8080);
            factory.setContextPath("/api");

            if (factory instanceof JettyServletWebServerFactory jetty) {
                // Jetty:定制 QueuedThreadPool
                jetty.addServerCustomizers(server -> {
                    org.eclipse.jetty.util.thread.QueuedThreadPool pool =
                            (org.eclipse.jetty.util.thread.QueuedThreadPool) server.getThreadPool();
                    pool.setName("jetty-worker");
                    pool.setMaxThreads(200);
                    pool.setMinThreads(20);
                    pool.setIdleTimeout(60_000);
                });
            } else if (factory instanceof TomcatServletWebServerFactory tomcat) {
                // Tomcat:定制 Connector
                tomcat.addConnectorCustomizers(connector -> {
                    connector.setProperty("maxThreads", "200");
                    connector.setProperty("acceptCount", "100");
                    connector.setProperty("connectionTimeout", "30000");
                });
            }
        };
    }
}

注意:addServerCustomizers / addConnectorCustomizers 属于容器特有 API,务必放在 instanceof 分支内,避免切换容器后编译或运行期报错。

7.1.4 切换后必须重点回归验证的 3 个行为差异点
  1. 线程池与并发行为 :Tomcat 的 maxThreads 与 Jetty 的 QueuedThreadPool.maxThreads 语义不同,Jetty 的线程池同时服务连接器与业务逻辑。切换后需压测确认:高并发下是否出现请求排队、线程数是否被正确限制、ThreadPoolExecutor 相关监控指标是否仍可采集。
  2. 会话(Session)与 Cookie 行为 :两者的 Session ID 生成策略、Cookie 属性(SameSite、HttpOnly、Secure)默认值存在差异,集群会话复制方案也不通用。切换后需验证登录态保持、多实例会话共享、以及前端跨域场景下的 Cookie 传递是否正常。
  3. HTTP 解析与错误处理细节 :请求头大小上限、URL 编码规则、非法请求的响应码(如 400/431)、multipart 上传的临时文件策略在两个容器中并不完全一致。切换后需回归大文件上传、超长 Header、特殊字符路径、以及全局异常处理器对容器级错误的兜底逻辑。

7.2 与反向代理和网关

  • Tomcat 的 AJP 连接器在与 Apache HTTP Server 集成时成熟稳定;Jetty 更常以纯 HTTP/1.1 或 HTTP/2 方式置于 Nginx、Envoy、HAProxy 之后。
  • 在 Service Mesh 场景下,两者通常都退化为"纯 HTTP 服务端",此时 Jetty 的轻量优势更明显。

7.3 与可观测性体系

  • Tomcat 的 JMX MBean 体系成熟,Micrometer 等指标库对其线程池、连接数、请求处理指标支持完善。
  • Jetty 同样提供 JMX 与统计 Handler,但指标命名与含义需要团队自行熟悉,接入成本略高。

7.4 与云原生部署

  • 容器化部署下,Jetty 更小的镜像体积与更快的启动速度对弹性伸缩更友好。
  • Tomcat 在 Kubernetes 中的运维经验、探针配置、JVM 参数模板更丰富,社区案例更多。

8. 官方文档与业界资料的丰富程度

维度 Tomcat Jetty
官方文档 完整,含配置参考、FAQ、Wiki、迁移指南 完整但偏参考手册,教程性内容较少
官方示例 较多,覆盖集群、安全、连接器 有,但更偏模块化示例
社区问答 Stack Overflow 与中文社区海量 数量明显较少,冷门问题需读源码
书籍与课程 大量中文书籍与课程 较少,多为英文资料
踩坑记录 极其丰富 相对零散
版本迁移资料 有官方迁移指南 有,但跨大版本变化大,需仔细阅读

结论:Tomcat 在"资料密度"上明显占优,Jetty 在"官方文档准确性"上不落下风,但需要读者具备更强的自主排查能力。

9. 开发者学习曲线

  • 入门阶段:两者都只需理解 Servlet 生命周期即可跑通 Hello World,差异不大。
  • 进阶阶段:Tomcat 的学习路径是"配置项 + 容器层级 + 运维工具",知识点多但资料全;Jetty 的学习路径是"Handler 链 + 模块组合 + 线程池策略",概念更抽象但体系更简洁。
  • 精通阶段 :Tomcat 需要理解连接器内部实现、Valve 管道、类加载机制;Jetty 需要理解 Handler 组合语义、Connector 与 ALPN 协商、QueuedThreadPool 调度策略。
  • 团队视角:Tomcat 的"平均上手成本"更低,因为默认配置与默认资料都在帮你;Jetty 的"精通成本"不低,但一旦掌握,定制能力更强。

10. 新项目交付时的选型建议

10.1 决策流程

#mermaid-svg-39adSuKcQBUl0DbP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-39adSuKcQBUl0DbP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-39adSuKcQBUl0DbP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-39adSuKcQBUl0DbP .error-icon{fill:#552222;}#mermaid-svg-39adSuKcQBUl0DbP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-39adSuKcQBUl0DbP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-39adSuKcQBUl0DbP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-39adSuKcQBUl0DbP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-39adSuKcQBUl0DbP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-39adSuKcQBUl0DbP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-39adSuKcQBUl0DbP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-39adSuKcQBUl0DbP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-39adSuKcQBUl0DbP .marker.cross{stroke:#333333;}#mermaid-svg-39adSuKcQBUl0DbP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-39adSuKcQBUl0DbP p{margin:0;}#mermaid-svg-39adSuKcQBUl0DbP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-39adSuKcQBUl0DbP .cluster-label text{fill:#333;}#mermaid-svg-39adSuKcQBUl0DbP .cluster-label span{color:#333;}#mermaid-svg-39adSuKcQBUl0DbP .cluster-label span p{background-color:transparent;}#mermaid-svg-39adSuKcQBUl0DbP .label text,#mermaid-svg-39adSuKcQBUl0DbP span{fill:#333;color:#333;}#mermaid-svg-39adSuKcQBUl0DbP .node rect,#mermaid-svg-39adSuKcQBUl0DbP .node circle,#mermaid-svg-39adSuKcQBUl0DbP .node ellipse,#mermaid-svg-39adSuKcQBUl0DbP .node polygon,#mermaid-svg-39adSuKcQBUl0DbP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-39adSuKcQBUl0DbP .rough-node .label text,#mermaid-svg-39adSuKcQBUl0DbP .node .label text,#mermaid-svg-39adSuKcQBUl0DbP .image-shape .label,#mermaid-svg-39adSuKcQBUl0DbP .icon-shape .label{text-anchor:middle;}#mermaid-svg-39adSuKcQBUl0DbP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-39adSuKcQBUl0DbP .rough-node .label,#mermaid-svg-39adSuKcQBUl0DbP .node .label,#mermaid-svg-39adSuKcQBUl0DbP .image-shape .label,#mermaid-svg-39adSuKcQBUl0DbP .icon-shape .label{text-align:center;}#mermaid-svg-39adSuKcQBUl0DbP .node.clickable{cursor:pointer;}#mermaid-svg-39adSuKcQBUl0DbP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-39adSuKcQBUl0DbP .arrowheadPath{fill:#333333;}#mermaid-svg-39adSuKcQBUl0DbP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-39adSuKcQBUl0DbP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-39adSuKcQBUl0DbP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-39adSuKcQBUl0DbP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-39adSuKcQBUl0DbP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-39adSuKcQBUl0DbP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-39adSuKcQBUl0DbP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-39adSuKcQBUl0DbP .cluster text{fill:#333;}#mermaid-svg-39adSuKcQBUl0DbP .cluster span{color:#333;}#mermaid-svg-39adSuKcQBUl0DbP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-39adSuKcQBUl0DbP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-39adSuKcQBUl0DbP rect.text{fill:none;stroke-width:0;}#mermaid-svg-39adSuKcQBUl0DbP .icon-shape,#mermaid-svg-39adSuKcQBUl0DbP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-39adSuKcQBUl0DbP .icon-shape p,#mermaid-svg-39adSuKcQBUl0DbP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-39adSuKcQBUl0DbP .icon-shape .label rect,#mermaid-svg-39adSuKcQBUl0DbP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-39adSuKcQBUl0DbP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-39adSuKcQBUl0DbP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-39adSuKcQBUl0DbP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
Tomcat 经验为主
Jetty 经验为主
均无
新项目启动
是否需要内嵌 HTTP 服务或极小体积?
优先 Jetty
是否有高并发长连接/WebSocket 需求?
是否需要 JSP/AJP/集群会话等企业级特性?
优先 Tomcat
团队是否已有成熟容器运维经验?
默认 Tomcat,降低交付风险

10.2 具体建议

  1. 默认选 Tomcat:除非有明确理由,Spring Boot 项目保持默认容器是最稳妥的交付选择,调优资料、监控接入、故障排查路径都最短。
  2. 以下情况优先 Jetty:需要把 HTTP 服务嵌入到库或工具中;WebSocket/SSE 连接数极大;镜像体积与启动速度是硬指标;需要 HTTP/3 等前沿协议。
  3. 不要为了"性能"盲目切换:在常规业务负载下,两者的吞吐差异往往小于业务代码与数据库带来的差异,切换前应做压测验证。
  4. 关注命名空间对齐:Spring Boot 3 / Jakarta EE 9+ 场景下,Tomcat 需 10+、Jetty 需 11+,版本错配是最常见的踩坑点。
  5. 把容器当作可替换组件 :通过 Spring 的 ServletWebServerFactory 抽象与统一的可观测性埋点,让容器切换成本可控,避免业务代码直接依赖容器 API。
  6. 团队能力优先于技术参数:容器是长期运维对象,团队熟悉度往往比纸面性能更重要。

10.3 常见经验教训

  • 线程池参数照搬会出事 :Tomcat 的 maxThreads 与 Jetty 的 QueuedThreadPool.maxThreads 语义不同,直接照搬数值容易导致排队或线程爆炸。
  • 长连接要单独评估:WebSocket 场景下连接数与线程数的关系在两个容器中差异明显,需按实际连接规模压测。
  • 升级大版本前先读迁移指南 :Jetty 的模块拆分与包名变化、Tomcat 的 javax → jakarta 迁移,都是高频踩坑点。
  • 监控指标要提前对齐:不同容器的指标命名不同,接入 APM 前先确认关键指标(活跃连接、排队请求、线程池饱和度)是否可采集。

11. 总结

Tomcat 与 Jetty 并非"谁替代谁"的关系,而是两种设计取向的代表:

  • Tomcat:规范完整、生态繁荣、运维成熟,是绝大多数 Java Web 项目的稳妥默认选择。
  • Jetty:轻量、可嵌入、长连接友好、协议跟进快,是嵌入式与高并发连接场景的优选。

选型的核心不是比较纸面性能,而是回答三个问题:我的部署形态是什么、我的连接模型是什么、我的团队最熟悉什么。 想清楚这三点,答案通常就很清晰了。

相关推荐
乌暮2 小时前
Spring AOP 与 Spring 事务:用大白话彻底理解原理与实践
java·数据库·spring boot·后端·sql·学习·spring
YDS8292 小时前
Small Spring IOC篇:容器事件及其监听器
java·spring
金銀銅鐵2 小时前
带您看懂字节码指令(三):一行加法里有什么
java·jvm
文丑颜不良啊2 小时前
LeetCode2921~2940题目解析
java·leetcode
北漂人Java3 小时前
基于 Spring AI Alibaba WorkFlow 构建 Java 生产级 AI Agent
java·人工智能·spring
用户112936112194 小时前
编译全绿,上线就崩:MyBatis 五层方法名大小写埋下的坑
java
文丑颜不良啊4 小时前
LeetCode2901~2920题目解析
java·leetcode
码云社区4 小时前
能碳管理系统开发要关注什么?能源数据、统计口径与AI分析的衔接
java·人工智能·ai·能源·能源管理系统·能碳管理系统·能碳管理系统源码
Flynn Liang5 小时前
Agentic 工作流:子代理、Supervisor 与人工介入
java·langchain·多智能体·supervisor·agentic·langchain4j