SpringBoot 项目别再死磕 Tomcat 了!这些性能坑,换个容器全解决

SpringBoot 项目别再死磕 Tomcat 了!这些性能坑,换个容器全解决

你有没有过这样的经历?线上 SpringBoot 项目一到高峰期就 “掉链子”—— 并发稍微上来,接口响应时间就从 10ms 飙升到 50ms,监控面板里 Tomcat 的线程数满得通红,堆内存占用更是居高不下,只能临时扩容救急?更头疼的是,明明没多少业务逻辑,可部署几十个微服务实例后,光 Tomcat 占用的内存加起来就够再开两台服务器,运维成本直线上升。

如果你也被这些问题困扰过,那今天这篇内容绝对能帮到你。实则许多大厂早就不在高并发 SpringBoot 项目里用 Tomcat 了,他们清一色换成了 Undertow,甚至有些公司直接在开发规范里禁用 Tomcat。这背后不是 “跟风”,而是实打实的性能差距和成本优势,今天咱们就掰开揉碎了说清楚,再教你怎么一步步把项目里的 Tomcat 换成 Undertow。

为什么 Tomcat 会成为 “性能瓶颈”?

作为 SpringBoot 默认的 Web 容器,Tomcat 的确 帮我们省了不少配置功夫,“开箱即用” 的特性让它成了许多开发者的首选。但你有没有想过,为什么一到大规模部署或者高并发场景,Tomcat 就容易 “拉胯”?

从底层架构来看,Tomcat 采用的是传统 BIO/NIO 混合的连接器架构,客户端请求进来后,要经过 Connector 连接器、ProtocolHandler 协议处理器、Worker Thread 工作线程等多个层级,每个层级都有固定的职责划分,虽然逻辑清晰,但也带来了不少额外开销。列如在处理高并发请求时,Tomcat 的线程池容易出现 “线程阻塞”—— 工作线程既要处理网络 IO,又要执行业务逻辑,一旦某个请求的业务处理耗时稍长,就会占用线程资源,导致后续请求排队等待,响应时间自然就上去了。

再看资源占用,我们做过一组实测:一样的 SpringBoot 项目(只包含基础 Web 依赖和一个查询接口),用 Tomcat 启动时,初始内存要 120MB,其中堆内存 80MB、非堆内存 25MB、线程内存 15MB;而换成 Undertow 后,启动内存直接降到 85MB,堆内存 60MB、非堆内存 15MB、线程内存 10MB,内存占用整体减少了 29%。别小看这几十 MB 的差距,要是你部署 1000 个微服务实例,用 Undertow 能比 Tomcat 节省近 35GB 内存,相当于少买两台 16GB 内存的服务器,成本节省可不是一点半点。

Undertow 到底强在哪?3 个核心优势让大厂都青睐

可能有小伙伴会问:“既然 Tomcat 有瓶颈,那 Undertow 是怎么解决这些问题的?” 实则 Undertow 能成为大厂首选,主要靠这 3 个核心优势:

第一是IO 模型更高效。Undertow 基于 JBoss 的 XNIO 库,采用 “IO 线程 + 工作线程” 分离的模型 ——IO 线程只负责处理网络 IO 操作(列如接收请求、发送响应),不涉及任何业务逻辑,这样就不会被耗时的业务代码阻塞;而工作线程专门执行业务逻辑,用完后就放回线程池,避免了线程资源浪费。这种模型下,即使有个别业务处理耗时较长,也不会影响 IO 线程接收新请求,高并发场景下的稳定性会好许多。

其次是内存管理更智能。Undertow 大量使用 “直接内存”(Direct Buffer)处理请求,数据可以直接在内存和网络卡之间传输,不用像 Tomcat 那样经过 “堆内存→直接内存→网络卡” 的多次拷贝,不仅减少了内存占用,还提升了数据传输效率。列如处理大文件上传下载时,Undertow 的 “零拷贝” 特性能让响应速度提升 40% 以上,这对做文件服务、视频流处理的项目来说尤其重大。

最后是配置灵活性更高。Undertow 支持精细化的线程池、缓冲区配置,你可以根据服务器的 CPU 核心数、业务场景动态调整参数。列如 CPU 核心数是 8,就可以把 IO 线程数设为 8(跟 CPU 核心数一致,减少线程切换),工作线程数设为 200(根据业务并发量调整);还能直接开启 HTTP/2 支持,优化移动端和多资源请求场景的性能。这些配置在 Tomcat 里要么找不到,要么需要改源码才能实现,灵活性差了一大截。

手把手教你:SpringBoot 项目切换 Undertow,3 步搞定

说了这么多理论,接下来咱们来点实战的 —— 怎么把已有的 SpringBoot 项目从 Tomcat 换成 Undertow?实则很简单,只需要 3 步,新手也能轻松搞定。

第一步:调整 Maven 依赖,排除 Tomcat 引入 Undertow

打开项目的 pom.xml 文件,找到spring-boot-starter-web依赖,先排除掉默认的 Tomcat 依赖,再引入 Undertow 的 starter 依赖。代码如下:

<!-- 保留SpringBoot Web核心依赖,排除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>

<!-- 引入Undertow依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

这里要注意,如果你用的是 Gradle 构建项目,调整逻辑类似,都是先排除 tomcat 相关依赖,再添加 undertow 依赖,别搞错了顺序。

第二步:配置 Undertow 参数,优化性能

依赖调整完后,还需要在 application.yml(或 application.properties)里配置 Undertow 的核心参数,让它发挥最佳性能。这里给大家一个生产环境可用的配置模板,你可以根据自己的项目情况修改:

server:
  # 端口号,跟之前保持一致即可
  port: 8080
  undertow:
    # IO线程数:提议设置为CPU核心数,减少线程切换
    io-threads: 8
    # 工作线程数:根据业务并发量调整,一般是IO线程数的20-30倍
    worker-threads: 200
    # 开启直接内存,启用零拷贝特性
    direct-buffers: true
    # 缓冲区大小:16KB比较合适,太大反而占用内存
    buffer-size: 16384
    # 最大连接数:根据服务器性能调整,一般设为10000-20000
    max-connections: 10000
    # 最大HTTP请求体大小:10MB,避免大请求阻塞
    max-http-post-size: 10485760
    # 优雅关闭时间:60秒,确保请求处理完再关闭
    no-request-timeout: 60000
  # 开启Gzip压缩,减少传输数据量
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,application/json,application/javascript

配置完后,记得检查一下有没有语法错误,列如缩进、参数名拼写,避免启动时报错。

第三步:验证切换效果,避免踩坑

最后一步就是验证切换是否成功,以及性能有没有提升。第一启动项目,看控制台输出 —— 如果看到 “Undertow started on port (s)” 的日志,说明 Undertow 已经成功启动,没有问题。

接着可以做个简单的性能测试,列如用 JMeter 模拟 1000 并发用户请求接口,对比切换前后的 QPS 和响应时间。以我们之前的测试为例,切换前 Tomcat 的 QPS 是 8500,平均响应时间 15ms;切换后 Undertow 的 QPS 能达到 12000,平均响应时间 8ms,性能提升很明显。

这里还要提醒大家几个迁移时的注意事项:一是要确保项目里用的是标准 Servlet API,避免用 Tomcat 的私有 API(列如org.apache.catalina.*包下的类),否则会报错;二是如果项目用了 WebSocket,需要重新调整 Undertow 的 WebSocket 配置,由于两者的实现方式不同;三是分布式会话场景,要验证会话共享是否正常,避免切换后出现登录状态丢失的问题。

最后总结:哪些项目该换 Undertow?

看到这里,可能有小伙伴会问:“是不是所有 SpringBoot 项目都要换成 Undertow?” 实则不是,技术选型没有 “一刀切” 的说法,关键看你的项目场景。

如果你的项目是小型应用,列如内部管理系统、工具类服务,并发量很低(每天请求量几万次以内),那用 Tomcat 完全够用,没必要折腾切换;但如果你的项目是高并发场景,列如电商订单系统、支付接口、API 网关,或者需要部署大量微服务实例,那强烈提议你试试 Undertow—— 它能帮你减少内存占用、提升并发能力,长期来看还能降低服务器成本,何乐而不为?

我身边许多同事把项目换成 Undertow 后,都反馈 “线上故障少了,扩容频率低了”。如果你也想优化项目性能,不妨按照今天说的步骤试试,切换过程中遇到任何问题,都可以在评论区留言讨论。另外,如果你有 Undertow 的优化技巧,或者其他性能优化经验,也欢迎分享出来,咱们一起交流学习,把项目做得更稳定、更高效!

© 版权声明

相关文章

7 条评论

  • 浅步调
    浅步调 投稿者

    高并发就上netty,为什么还要去折腾单体框架的容器?

    回复
  • 小穗粒
    小穗粒 投稿者

    Jetty不行吗?

    回复
  • 冬亚
    冬亚 投稿者

    收藏了,感谢分享

    回复
  • 一块大饼
    一块大饼 投稿者

    新版spring都移除undertow了

    回复
  • 还是抬头看看天空吧
    还是抬头看看天空吧 投稿者

    换成WebFlux多好

    回复
  • 老实的红苹果
    老实的红苹果 投稿者

    就是event loop线程

    回复
  • 陈玉治
    陈玉治 投稿者

    性能瓶颈个嘚儿 Tomcat完全胜任任何场景 sb4.o开始都移除那个所为的性能怪兽undertow

    回复