Spring Boot 中的 Tomcat、Jetty、WebLogic 与参数调优思考

欢迎你来读这篇博客,这篇博客主要是关于Spring Boot 中 Tomcat、Jetty、WebLogic 的整合方式与参数调优思考

其中包括了关于我的见解和收集的知识分享。

序言

做 Java Web 开发时,我们经常会写 Controller、Service、Mapper,却很少认真看请求到底是怎么进入应用的。

一个 HTTP 请求从客户端发出后,并不是直接进入我们的 @RestController。它会先经过网络连接、Socket 接收、Servlet 容器、Filter 链、DispatcherServlet,最后才进入业务代码。

这中间最关键的一层,就是 Web 容器。

在 Spring Boot 项目里,最常见的是内嵌 Tomcat;有些项目会切换为 Jetty;而在传统企业系统、金融系统、政企项目中,也可能会部署到 WebLogic 这种重量级应用服务器上。

这几个容器没有绝对好坏,只有适不适合当前系统:

  • Tomcat:默认选择,生态成熟,资料多,排查成本低。
  • Jetty:轻量、异步模型成熟,适合对资源占用敏感的场景。
  • WebLogic:企业级应用服务器,适合传统 Java EE、集中部署、JNDI、集群、控制台治理等场景。
  • Spring Boot 内嵌容器:更适合微服务、容器化、云原生部署。
  • 外置应用服务器:更适合遗留系统、统一运维、统一中间件治理。

一句话:Tomcat/Jetty 更像“应用自己带发动机”,WebLogic 更像“把应用放进企业级机房流水线”。

正文

chapter 1:Spring Boot 为什么默认使用内嵌 Tomcat

Spring Boot 的核心理念之一是“约定大于配置”。以前开发 Java Web 项目,经常需要:

  1. 打 WAR 包;
  2. 安装外部 Tomcat;
  3. 修改 server.xml;
  4. 部署到 webapps;
  5. 启动外部容器。

Spring Boot 把这套流程简化成:

1
java -jar app.jar

这背后的原因是:spring-boot-starter-web 默认引入了内嵌 Tomcat。应用启动时,Spring Boot 会自动创建 Servlet Web Server,并启动 Tomcat,然后把 Spring MVC、DispatcherServlet、Filter、Listener 等注册进去。

简化理解如下:

1
2
3
4
5
6
7
8
SpringApplication.run()
-> 创建 ApplicationContext
-> 创建 ServletWebServerApplicationContext
-> 查找 ServletWebServerFactory
-> 默认使用 TomcatServletWebServerFactory
-> 创建并启动 Tomcat
-> 注册 DispatcherServlet
-> 接收 HTTP 请求

所以,Spring Boot 项目本质上不是“跑在 Tomcat 里”,而是“应用启动时把 Tomcat 拉起来”。

这也是 Spring Boot 微服务化、容器化部署非常方便的原因:应用和运行时绑定在一起,部署时不再强依赖服务器上提前安装好的 Tomcat。

chapter 2:Tomcat 的请求处理流程

Tomcat 内部可以粗略拆成几层:

1
2
3
4
5
6
7
8
9
10
11
Connector
-> ProtocolHandler
-> Endpoint
-> Acceptor
-> Poller
-> Worker Thread
-> CoyoteAdapter
-> Container Pipeline
-> FilterChain
-> DispatcherServlet
-> Controller

几个核心角色可以这样理解:

1. Connector

Connector 负责网络连接。它监听端口,例如 8080,接收客户端请求。

常见配置:

1
2
server:
port: 8080

2. Acceptor

Acceptor 负责接收新连接。客户端和服务端完成 TCP 三次握手后,连接会进入队列,然后由 Acceptor 接收。

对应的关键参数是:

1
2
3
server:
tomcat:
accept-count: 200

accept-count 可以理解为:当 Tomcat 已经忙不过来时,操作系统层面还能排多少连接。

3. Poller

Poller 负责监听 Socket 事件,比如可读、可写。它并不真正处理业务,而是把就绪的连接事件交给后面的工作线程。

4. Worker Thread

Worker Thread 才是真正处理请求的线程。请求最终会进入 Servlet 容器,再进入 Spring MVC 的 DispatcherServlet,最后调用 Controller。

对应关键参数:

1
2
3
4
5
server:
tomcat:
threads:
max: 300
min-spare: 30

max 表示最大工作线程数,min-spare 表示最小空闲线程数。

不要简单理解成:max = 300 就一定能抗 300 QPS。它更接近于“最多同时有多少个请求处理线程”。如果业务大量阻塞数据库、Redis、远程 RPC,再多线程也只是把堵车从一条路搬到另一条路。

chapter 3:Tomcat 常见参数怎么调

Tomcat 调优不要上来就背参数,先看几个关键点:

1
2
3
4
5
6
7
8
并发连接数
请求处理线程数
等待队列
连接超时
Keep-Alive
请求体/请求头大小
监控指标
下游连接池

一个相对稳妥的 Spring Boot Tomcat 配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
server:
port: 8080

tomcat:
threads:
max: 300
min-spare: 30

max-connections: 8192
accept-count: 200

connection-timeout: 20s
keep-alive-timeout: 30s
max-keep-alive-requests: 100

max-http-form-post-size: 2MB
max-swallow-size: 2MB
processor-cache: 300

mbeanregistry:
enabled: true

shutdown: graceful

spring:
lifecycle:
timeout-per-shutdown-phase: 30s

1. server.tomcat.threads.max

最大工作线程数。

经验判断:

  • CPU 密集型业务:不要开太大,线程多了只会增加上下文切换。
  • IO 密集型业务:可以适当增大,但必须看数据库连接池、Redis 连接池、RPC 线程池是否跟得上。
  • 线上建议结合压测和监控调整,不要拍脑袋。

坏例子:

1
2
3
4
server:
tomcat:
threads:
max: 1000

如果数据库连接池只有 50,Tomcat 开 1000 个线程并不会让系统起飞,只会让更多请求排队等数据库。线程池不是许愿池,不能往里丢硬币。

2. server.tomcat.max-connections

最大连接数。

它控制 Tomcat 同时接受和处理的连接数量。连接数和线程数不是一回事。一个连接不一定马上占用工作线程,尤其在 Keep-Alive 场景下,连接可能处于空闲等待状态。

3. server.tomcat.accept-count

当连接数达到上限、工作线程也繁忙时,新的连接会进入等待队列。accept-count 就是这个队列的长度。

如果这个值太小,高峰期容易拒绝连接;如果太大,请求可能在队列里等很久,最终超时,用户体验一样差。

我的建议是:队列不要无限大,系统扛不住时要尽早失败,而不是让用户一直转圈。

4. connection-timeoutkeep-alive-timeout

这两个参数经常被忽略。

connection-timeout 是连接建立后,Tomcat 等待请求行的时间。

keep-alive-timeout 是一个请求处理完后,连接保持多久等待下一个请求。

如果你的服务前面有 Nginx、网关、SLB,需要让各层超时配置有梯度。一般建议:

1
客户端超时 > 网关超时 > 应用超时 > 数据库/RPC超时

否则会出现一种很恶心的情况:客户端已经断了,后端还在辛苦干活,像公司群里凌晨两点还在“收到”的打工人。

5. mbeanregistry.enabled

如果使用 Spring Boot Actuator、Micrometer、Prometheus 监控 Tomcat 指标,建议开启:

1
2
3
4
server:
tomcat:
mbeanregistry:
enabled: true

这样更容易观察:

  • 当前线程数;
  • 繁忙线程数;
  • 当前连接数;
  • 请求耗时;
  • 错误数量;
  • GC 和内存压力。

chapter 4:Jetty 和 Spring Boot 的整合

Jetty 也是常见的 Servlet 容器。相比 Tomcat,它更强调轻量、嵌入式、异步处理能力。很多网关、长连接、嵌入式服务场景会考虑 Jetty。

在 Spring Boot 中切换 Jetty,主要是排除 Tomcat,引入 Jetty starter。

Maven 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>

Jetty 配置示例:

1
2
3
4
5
6
7
8
9
10
server:
port: 8080

jetty:
threads:
max: 200
min: 8
idle-timeout: 60s
max-connections: -1
connection-idle-timeout: 30s

Jetty 的线程模型和 Tomcat 不完全一样。尤其在异步请求、HTTP/2、多路复用场景下,不能用“一请求一线程”的简单模型去理解。

所以 Jetty 调优时,不能只盯着 threads.max。还要看:

  • 是否大量异步请求;
  • 是否使用 HTTP/2;
  • 是否存在长连接;
  • 线程池是否被内部组件占用;
  • 队列是否堆积;
  • 下游资源是否才是真瓶颈。

我的理解是:Tomcat 的调优更容易按“连接数 + 工作线程 + 队列”去理解;Jetty 的调优要更关注异步模型、线程池饥饿和整体事件流。

chapter 5:WebLogic 和 Spring Boot 的整合

WebLogic 和 Tomcat、Jetty 的定位不太一样。

Tomcat、Jetty 更偏 Servlet 容器;WebLogic 是完整的企业级应用服务器,支持更多 Java EE/Jakarta EE 能力,例如:

  • 管理控制台;
  • 数据源;
  • JNDI;
  • JMS;
  • 集群;
  • 部署计划;
  • 安全域;
  • Work Manager;
  • 企业级监控和治理。

如果是新项目、微服务、容器化部署,一般不建议优先选择 WebLogic。不是说它不好,而是它太重。就像你只是想买杯奶茶,结果开了一辆装甲车去排队,气势是有了,停车费也上来了。

但在传统企业系统里,WebLogic 仍然很常见,尤其是金融、政企、保险、运营商等场景。

Spring Boot 部署到 WebLogic,通常不是打可执行 JAR,而是打 WAR 包。

核心步骤如下。

1. 修改打包方式

1
<packaging>war</packaging>

2. 修改启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.WebApplicationInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer implements WebApplicationInitializer {

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

3. 将内嵌 Tomcat 设置为 provided

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>

因为真正运行时由 WebLogic 提供 Servlet 容器能力,WAR 包里不应该再强行塞一个内嵌 Tomcat,否则容易类冲突。

4. 增加 weblogic.xml

常见路径:

1
src/main/webapp/WEB-INF/weblogic.xml

示例:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app
xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<wls:container-descriptor>
<wls:prefer-application-packages>
<wls:package-name>org.slf4j</wls:package-name>
</wls:prefer-application-packages>
</wls:container-descriptor>

</wls:weblogic-web-app>

这个配置主要用于避免 WebLogic 自带依赖和应用内部依赖冲突。

5. 注意 Servlet API 版本

这是 WebLogic 部署 Spring Boot 时最容易踩坑的地方。

Spring Boot 2.x 主要还是 javax.servlet 体系;Spring Boot 3.x 开始全面进入 jakarta.servlet 体系。

如果你的 WebLogic 版本比较老,只支持旧的 Java EE / javax 体系,那么贸然把 Spring Boot 3.x 的应用打成 WAR 部署上去,很可能会因为 Servlet 版本、包名变化、web-fragment 校验失败等问题启动失败。

所以部署前一定要确认:

1
2
3
4
5
6
Spring Boot 版本
Servlet API 版本
WebLogic 版本
JDK 版本
应用依赖是否包含内嵌容器
日志实现是否冲突

chapter 6:WebLogic 的调优思路

WebLogic 的调优和 Tomcat 不一样。

Tomcat 常见思路是调 Connector、线程、连接数、队列;WebLogic 更强调 Work Manager、数据源、线程自调度、请求优先级、容量约束。

常见调优点:

1. Work Manager

WebLogic 使用 Work Manager 管理应用工作负载。可以通过它配置:

  • fair-share:公平份额;
  • response-time:响应时间目标;
  • min-threads-constraint:最小线程约束;
  • max-threads-constraint:最大线程约束;
  • capacity:容量约束;
  • stuck-thread 处理策略。

例如某个接口严重依赖数据库,而数据库连接池最大只有 50,那么你可以通过最大线程约束限制这类请求,不要让几百个线程一起涌进数据库。

2. 数据源连接池

WebLogic 的数据源是重点调优对象。

需要关注:

  • Initial Capacity;
  • Maximum Capacity;
  • Statement Cache;
  • Test Connections On Reserve;
  • 连接泄漏检测;
  • 等待连接的线程数;
  • 数据库慢 SQL;
  • 数据库本身最大连接数。

比较推荐的思路是:连接池大小不要只看应用,要和数据库资源一起评估。

如果数据库最多稳定支撑 100 个活跃连接,你在 WebLogic 上配 500 个连接,只是把数据库送进 ICU。

3. Stuck Thread

WebLogic 会检测长时间不返回的线程,称为 Stuck Thread。

出现 Stuck Thread 时,不要第一反应就调大阈值。正确思路是先看线程栈:

1
2
3
4
5
线程卡在数据库?
线程卡在外部 HTTP 调用?
线程卡在锁等待?
线程卡在文件 IO?
线程卡在远程服务超时?

如果只是把 Stuck Thread 时间从 600 秒调到 1200 秒,本质上可能只是把报警铃音量调小,火还在烧。

chapter 7:Tomcat、Jetty、WebLogic 怎么选

我的建议如下:

场景 推荐选择
普通 Spring Boot Web 项目 Tomcat
微服务、容器化、K8s Tomcat 或 Jetty
对资源占用敏感 Jetty
大量异步、长连接、HTTP/2 Jetty 可重点评估
传统 WAR 部署 外置 Tomcat 或 WebLogic
政企/金融已有 WebLogic 基础设施 WebLogic
新项目追求轻量和交付效率 Spring Boot 可执行 JAR
老系统迁移 先兼容 WAR,再逐步迁移 JAR/容器化

更直白一点:

  • 新项目:优先 Spring Boot 内嵌容器。
  • 老项目:先兼容现状,再考虑迁移。
  • 微服务:不要轻易引入 WebLogic 这种重量级运行时。
  • 企业遗留系统:不要为了“技术新潮”强行推翻 WebLogic,迁移成本可能比系统本身还刺激。

chapter 8:参数调优的完整方法论

调优不是把参数调大。

真正的调优应该是:

1
压测 -> 观测 -> 找瓶颈 -> 小步调整 -> 再压测 -> 固化配置

重点观察:

  • QPS;
  • 平均响应时间;
  • P95/P99 响应时间;
  • Tomcat busy threads;
  • 当前连接数;
  • JVM GC;
  • 堆内存;
  • CPU 使用率;
  • 数据库连接池 active/idle/wait;
  • 慢 SQL;
  • Redis/RPC 超时;
  • 网关 4xx/5xx;
  • 应用日志中的耗时分布。

我个人比较推荐的调优顺序:

第一步:先设超时

没有超时的系统不叫高可用,叫“命运共同体”。

1
2
3
4
5
6
7
8
9
server:
tomcat:
connection-timeout: 20s
keep-alive-timeout: 30s

spring:
datasource:
hikari:
connection-timeout: 3000

远程调用也要配置超时,例如 HTTP Client、Feign、Dubbo、gRPC。

第二步:再看连接池

Web 线程数必须和数据库连接池、Redis 连接池、RPC 并发能力匹配。

比如:

1
2
3
Tomcat maxThreads = 300
Hikari maximumPoolSize = 50
下游 RPC 最大并发 = 100

这种情况下,系统真实吞吐可能被数据库连接池限制在 50 附近。剩下的线程不是在创造价值,而是在排队。

第三步:控制队列

队列不是越大越好。

队列太大,会让请求“假装还活着”。用户看起来没报错,但一直转圈,最后还是失败。

更好的方式是:

  • 内部队列适中;
  • 下游有限流;
  • 网关有熔断;
  • 系统扛不住时快速失败;
  • 给调用方明确的 429 或 503。

第四步:打开监控

Spring Boot Actuator + Micrometer + Prometheus + Grafana 是非常常见的组合。

Tomcat 项目建议开启:

1
2
3
4
5
6
7
8
9
10
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,threaddump

server:
tomcat:
mbeanregistry:
enabled: true

然后重点看:

1
2
3
4
5
6
7
8
tomcat.threads.busy
tomcat.threads.current
tomcat.connections.current
http.server.requests
jvm.memory.used
jvm.gc.pause
hikaricp.connections.active
hikaricp.connections.pending

第五步:用压测说话

没有压测的调优,就是玄学开光。

可以使用:

  • JMeter;
  • Gatling;
  • wrk;
  • ab;
  • k6。

压测时不要只看平均响应时间,平均值很容易骗人。真正需要重点关注的是 P95、P99,以及错误率。

chapter 9:我的结论

Tomcat、Jetty、WebLogic 的区别,本质上不是“谁性能更强”,而是运行模式不同。

Tomcat 更像标准答案:稳定、成熟、资料多,Spring Boot 默认选它是合理的。

Jetty 更像轻量选手:适合嵌入式、异步、长连接、资源敏感场景,但调优时要理解它的线程模型,不要拿 Tomcat 的经验生搬硬套。

WebLogic 更像企业级平台:功能强、治理能力强,但重量也大。它适合历史包袱比较重、企业规范比较强、部署体系已经绑定的系统。

对于大多数新 Spring Boot 项目,我的建议是:

1
2
3
优先使用内嵌 Tomcat。
有明确资源/异步/长连接需求,再评估 Jetty。
只有在企业运行环境要求下,再考虑 WebLogic。

对于参数调优,我的建议是:

1
2
3
4
5
不要迷信大参数。
不要只调 Tomcat。
不要忽略数据库连接池。
不要没有监控就上线。
不要没有压测就说优化完成。

真正稳定的系统,不是参数看起来很猛,而是在流量打上来时,每一层都知道自己该接多少、排多少、拒多少、等多久。

这才是工程里的体面。

参考资料

  • Spring Boot Reference Documentation:Servlet Web Applications
  • Spring Boot Reference Documentation:Traditional Deployment
  • Spring Boot Common Application Properties
  • Apache Tomcat Configuration Reference:HTTP Connector
  • Eclipse Jetty Documentation:Threading Architecture
  • Oracle WebLogic Server Documentation:Tuning WebLogic Server
  • Oracle WebLogic Server Documentation:Using Work Managers
  • Oracle WebLogic Server Documentation:Tuning Data Sources
  • Jason207010:Spring Boot Tomcat 架构及参数优化

启示录

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


Spring Boot 中的 Tomcat、Jetty、WebLogic 与参数调优思考
https://allendericdalexander.github.io/2026/06/02/springboot_tomcat/
作者
AtLuoFu
发布于
2026年6月2日
许可协议