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 项目,经常需要:
- 打 WAR 包;
- 安装外部 Tomcat;
- 修改 server.xml;
- 部署到 webapps;
- 启动外部容器。
Spring Boot 把这套流程简化成:
1 | |
这背后的原因是:spring-boot-starter-web 默认引入了内嵌 Tomcat。应用启动时,Spring Boot 会自动创建 Servlet Web Server,并启动 Tomcat,然后把 Spring MVC、DispatcherServlet、Filter、Listener 等注册进去。
简化理解如下:
1 | |
所以,Spring Boot 项目本质上不是“跑在 Tomcat 里”,而是“应用启动时把 Tomcat 拉起来”。
这也是 Spring Boot 微服务化、容器化部署非常方便的原因:应用和运行时绑定在一起,部署时不再强依赖服务器上提前安装好的 Tomcat。
chapter 2:Tomcat 的请求处理流程
Tomcat 内部可以粗略拆成几层:
1 | |
几个核心角色可以这样理解:
1. Connector
Connector 负责网络连接。它监听端口,例如 8080,接收客户端请求。
常见配置:
1 | |
2. Acceptor
Acceptor 负责接收新连接。客户端和服务端完成 TCP 三次握手后,连接会进入队列,然后由 Acceptor 接收。
对应的关键参数是:
1 | |
accept-count 可以理解为:当 Tomcat 已经忙不过来时,操作系统层面还能排多少连接。
3. Poller
Poller 负责监听 Socket 事件,比如可读、可写。它并不真正处理业务,而是把就绪的连接事件交给后面的工作线程。
4. Worker Thread
Worker Thread 才是真正处理请求的线程。请求最终会进入 Servlet 容器,再进入 Spring MVC 的 DispatcherServlet,最后调用 Controller。
对应关键参数:
1 | |
max 表示最大工作线程数,min-spare 表示最小空闲线程数。
不要简单理解成:max = 300 就一定能抗 300 QPS。它更接近于“最多同时有多少个请求处理线程”。如果业务大量阻塞数据库、Redis、远程 RPC,再多线程也只是把堵车从一条路搬到另一条路。
chapter 3:Tomcat 常见参数怎么调
Tomcat 调优不要上来就背参数,先看几个关键点:
1 | |
一个相对稳妥的 Spring Boot Tomcat 配置示例:
1 | |
1. server.tomcat.threads.max
最大工作线程数。
经验判断:
- CPU 密集型业务:不要开太大,线程多了只会增加上下文切换。
- IO 密集型业务:可以适当增大,但必须看数据库连接池、Redis 连接池、RPC 线程池是否跟得上。
- 线上建议结合压测和监控调整,不要拍脑袋。
坏例子:
1 | |
如果数据库连接池只有 50,Tomcat 开 1000 个线程并不会让系统起飞,只会让更多请求排队等数据库。线程池不是许愿池,不能往里丢硬币。
2. server.tomcat.max-connections
最大连接数。
它控制 Tomcat 同时接受和处理的连接数量。连接数和线程数不是一回事。一个连接不一定马上占用工作线程,尤其在 Keep-Alive 场景下,连接可能处于空闲等待状态。
3. server.tomcat.accept-count
当连接数达到上限、工作线程也繁忙时,新的连接会进入等待队列。accept-count 就是这个队列的长度。
如果这个值太小,高峰期容易拒绝连接;如果太大,请求可能在队列里等很久,最终超时,用户体验一样差。
我的建议是:队列不要无限大,系统扛不住时要尽早失败,而不是让用户一直转圈。
4. connection-timeout 和 keep-alive-timeout
这两个参数经常被忽略。
connection-timeout 是连接建立后,Tomcat 等待请求行的时间。
keep-alive-timeout 是一个请求处理完后,连接保持多久等待下一个请求。
如果你的服务前面有 Nginx、网关、SLB,需要让各层超时配置有梯度。一般建议:
1 | |
否则会出现一种很恶心的情况:客户端已经断了,后端还在辛苦干活,像公司群里凌晨两点还在“收到”的打工人。
5. mbeanregistry.enabled
如果使用 Spring Boot Actuator、Micrometer、Prometheus 监控 Tomcat 指标,建议开启:
1 | |
这样更容易观察:
- 当前线程数;
- 繁忙线程数;
- 当前连接数;
- 请求耗时;
- 错误数量;
- GC 和内存压力。
chapter 4:Jetty 和 Spring Boot 的整合
Jetty 也是常见的 Servlet 容器。相比 Tomcat,它更强调轻量、嵌入式、异步处理能力。很多网关、长连接、嵌入式服务场景会考虑 Jetty。
在 Spring Boot 中切换 Jetty,主要是排除 Tomcat,引入 Jetty starter。
Maven 示例:
1 | |
Jetty 配置示例:
1 | |
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 | |
2. 修改启动类
1 | |
3. 将内嵌 Tomcat 设置为 provided
1 | |
因为真正运行时由 WebLogic 提供 Servlet 容器能力,WAR 包里不应该再强行塞一个内嵌 Tomcat,否则容易类冲突。
4. 增加 weblogic.xml
常见路径:
1 | |
示例:
1 | |
这个配置主要用于避免 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 | |
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 | |
如果只是把 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 | |
远程调用也要配置超时,例如 HTTP Client、Feign、Dubbo、gRPC。
第二步:再看连接池
Web 线程数必须和数据库连接池、Redis 连接池、RPC 并发能力匹配。
比如:
1 | |
这种情况下,系统真实吞吐可能被数据库连接池限制在 50 附近。剩下的线程不是在创造价值,而是在排队。
第三步:控制队列
队列不是越大越好。
队列太大,会让请求“假装还活着”。用户看起来没报错,但一直转圈,最后还是失败。
更好的方式是:
- 内部队列适中;
- 下游有限流;
- 网关有熔断;
- 系统扛不住时快速失败;
- 给调用方明确的 429 或 503。
第四步:打开监控
Spring Boot Actuator + Micrometer + Prometheus + Grafana 是非常常见的组合。
Tomcat 项目建议开启:
1 | |
然后重点看:
1 | |
第五步:用压测说话
没有压测的调优,就是玄学开光。
可以使用:
- JMeter;
- Gatling;
- wrk;
- ab;
- k6。
压测时不要只看平均响应时间,平均值很容易骗人。真正需要重点关注的是 P95、P99,以及错误率。
chapter 9:我的结论
Tomcat、Jetty、WebLogic 的区别,本质上不是“谁性能更强”,而是运行模式不同。
Tomcat 更像标准答案:稳定、成熟、资料多,Spring Boot 默认选它是合理的。
Jetty 更像轻量选手:适合嵌入式、异步、长连接、资源敏感场景,但调优时要理解它的线程模型,不要拿 Tomcat 的经验生搬硬套。
WebLogic 更像企业级平台:功能强、治理能力强,但重量也大。它适合历史包袱比较重、企业规范比较强、部署体系已经绑定的系统。
对于大多数新 Spring Boot 项目,我的建议是:
1 | |
对于参数调优,我的建议是:
1 | |
真正稳定的系统,不是参数看起来很猛,而是在流量打上来时,每一层都知道自己该接多少、排多少、拒多少、等多久。
这才是工程里的体面。
参考资料
- 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 架构及参数优化
启示录
富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。