第十七章. 性能调优番外篇:Web 容器选型与 JMeter 压测实战
第十七章. 性能调优番外篇:Web 容器选型与 JMeter 压测实战
Prorise第十七章. 性能调优实战:Tomcat vs Undertow 与 JMeter 压测
摘要:本章将深入探讨 RVP 5.x 为什么放弃了 Spring Boot 默认的 Tomcat 而选择了 Undertow,并揭秘如何通过代码深度集成 JDK 21 的虚拟线程。最后,我们将通过 JMeter 进行一场真实的性能压测,对比不同容器与线程模型下的吞吐量差异。
本章学习路径
- 容器选型:理解 Tomcat 与 Undertow 的架构差异及 Maven 替换策略。
- 深度调优:掌握
io-threads、worker-threads配置及虚拟线程的代码级集成。 - 压测环境:搭建测试接口,解决 JMeter 对接 RVP 鉴权(Token + ClientId)的难题。
- 巅峰对决:设计控制变量实验,对比 4 种组合下的性能表现。
17.1. 容器选型:为何 RVP 5.x 独宠 Undertow?
在 Java Web 开发领域,Tomcat 几乎是默认的代名词。但 RVP 5.x 在追求极致性能的道路上,选择了 Undertow。
17.1.1. Spring Boot 内置容器三国杀
Spring Boot 支持三种主流的嵌入式 Servlet 容器,它们的特点如下:
- Tomcat:Apache 基金会出品,老牌、稳定、生态最全。但在高并发非阻塞场景下,内存占用较高。
- Jetty:Eclipse 基金会出品,设计优秀,适合长连接(WebSocket)。
- Undertow:RedHat(JBoss)出品,基于 NIO 的高性能 Web 服务器。它的核心优势在于 轻量级 和 高吞吐量。它在处理大量并发连接时,内存占用远低于 Tomcat。
17.1.2. RVP 的切换实现:pom.xml 的排除与引入策略
要让 Spring Boot 抛弃 Tomcat 拥抱 Undertow,我们需要在 Maven 依赖中做“减法”和“加法”。
文件路径:ruoyi-common/ruoyi-common-web/pom.xml
1 | <dependencies> |
配置解析:
<exclusion>:这是 Maven 依赖管理的重要技巧。如果不写这个排除,Spring Boot 会因为 ClassPath 下同时存在两个容器而报错或行为异常。spring-boot-starter-undertow:引入这个依赖后,Spring Boot 的自动装配机制(AutoConfiguration)会自动检测并启动 UndertowWebServer。
17.2. 深度调优:Undertow 的参数配置与定制
引入依赖只是第一步,为了发挥 Undertow 的最大性能,我们需要对其核心参数进行调优,并将其与 JDK 21 的虚拟线程进行深度绑定。
17.2.1. application.yml 关键参数配置
Undertow 采用了 XNIO 框架,其线程模型主要分为两类:IO 线程和 Worker 线程。
文件路径:ruoyi-admin/src/main/resources/application.yml
1 | server: |
17.2.2. UndertowConfig:虚拟线程的深度集成
在 JDK 21 之前,我们依赖调整 server.undertow.threads.worker 的大小来应对高并发。但在 JDK 21 引入虚拟线程后,我们可以让 Undertow 使用 虚拟线程执行器 来处理请求,从而实现“从 256 到 无限”的并发能力跃升。
RVP 通过 UndertowConfig 实现了这一逻辑。
文件路径:ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java
1. 类的定义与自动装配
1 |
|
2. customize 核心方法实现
1 |
|
代码深度解析:
SpringUtils.isVirtual():这是一个工具方法,用于读取spring.threads.virtual.enabled配置。deploymentInfo.setExecutor(executor):这是最关键的一行。默认情况下,Undertow 使用一个固定大小的平台线程池(即 YAML 中的worker: 256)。当我们将执行器替换为VirtualThreadTaskExecutor后,Undertow 就不再受限于 256 个线程,而是可以为每个请求动态创建轻量级的虚拟线程。
3. SPI 注册
为了让 Spring Boot 识别这个配置类,必须在 AutoConfiguration.imports 文件中注册。
文件路径:ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1 | org.dromara.common.web.config.UndertowConfig |
17.3. 压测准备:JMeter 对接 RVP 鉴权体系
有了高性能的容器,我们需要通过压测来验证效果。由于 RVP 自带的 Demo 模块没有提供纯粹的压力测试接口,我们需要手动创建一个。
17.3.1. JMeter 基础环境
关于 JMeter 的下载、安装与基础中文配置,本文不再赘述。如果您是初次使用 JMeter,请参考以下专题文档进行环境搭建:
17.3.2. 搭建测试接口 TestJMController
我们在 ruoyi-demo 模块下创建一个简单的控制器,模拟业务场景:
- GET 请求:模拟查询,返回字符串。
- POST 请求:模拟数据写入,接收 JSON。
文件路径:ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestJMController.java
1 | package org.dromara.demo.controller; |
17.3.3. 攻克 RVP 鉴权:Token 与 ClientId
RVP 5.x 使用了 Sa-Token 并结合了 OAuth2 的设计思想,通过 Gateway 或拦截器进行鉴权。如果我们直接在 JMeter 中请求接口,会收到 401 Unauthorized 错误。
为了跑通压测,我们需要在 JMeter 中配置 HTTP Header Manager(信息头管理器),添加以下两个关键头信息:
Authorization:登录令牌。clientid:客户端标识(RVP 5.x 新增的安全校验)。
获取步骤:
- 启动 RVP 后端和前端。
- 在浏览器中登录系统。
- 按
F12打开开发者工具,点击“网络(Network)”。 - 刷新页面,随便点击一个接口(如
getInfo),在“请求头(Request Headers)”中找到这两个值。
JMeter 配置:
在 JMeter 的线程组下,右键添加 -> 配置元件 -> HTTP 信息头管理器,添加如下两条:
| 名称 | 值 (示例) |
|---|---|
Authorization | Bearer eyJhbGciOiJIUzI1NiJ9... (从浏览器复制) |
clientid | e5cd7e4891bf95d1d19206ce24a7b32e (从浏览器复制) |
17.4. 巅峰对决:Tomcat vs Undertow 性能压测实验
为了验证 Undertow + 虚拟线程的含金量,我们设计一个严格的控制变量实验,这不需要你照做
17.4.1. 实验设计
我们将构建 4 个不同版本的 JAR 包进行对比:
| 实验组 | 容器 | 线程模型 | 构建方式 |
|---|---|---|---|
| A 组 | Tomcat | 平台线程 (传统) | 启用 tomcat 依赖,YAML 关闭 virtual.enabled |
| B 组 | Tomcat | 虚拟线程 | 启用 tomcat 依赖,YAML 开启 virtual.enabled |
| C 组 | Undertow | 平台线程 (传统) | 启用 undertow 依赖,YAML 关闭 virtual.enabled |
| D 组 | Undertow | 虚拟线程 (RVP 默认) | 启用 undertow 依赖,YAML 开启 virtual.enabled |
17.4.2. 环境统一与执行
为了避免 IDEA 插件干扰,我们使用 java -jar 运行,并统一限制堆内存,模拟生产环境的资源限制。
启动命令标准:
1 | # 限制堆内存为 512M,确保在压力下能触发垃圾回收 |
JMeter 压测参数:
- 线程数(并发用户):1000
- Ramp-Up 时间:1 秒
- 循环次数:持续 60 秒
- 接口:
GET /demo/test/list
17.4.3. 战况实录
以下数据基于 i7-12700H + 32G 内存环境测试,仅供参考,实际结果取决于硬件配置。
1. 吞吐量(Throughput / TPS)对比
- Tomcat (平台线程):约 800 TPS。在高并发下,大量线程阻塞在等待切换,CPU 上下文切换消耗严重。
- Undertow (虚拟线程):约 2200 TPS。遥遥领先。得益于 NIO 的非阻塞特性加上虚拟线程的极低开销,Undertow 能够轻松榨干 CPU 性能。
2. 内存占用对比
- Tomcat:启动后空闲占用约 300MB,压测峰值瞬间飙升至 500MB(接近堆顶)。
- Undertow:启动后空闲占用约 150MB,压测峰值稳定在 250MB 左右。
17.4.4. 实验结论
- 容器差异:即使在同等线程模型下,Undertow 的吞吐量普遍比 Tomcat 高 30%~50%,且内存占用更低。
- 虚拟线程的威力:开启虚拟线程后,两者性能均有提升,但 Undertow + 虚拟线程 的组合产生了质变,是高并发场景下的绝对王者。
- RVP 的选择:RVP 5.x 默认采用 Undertow + JDK 21 虚拟线程 的架构,是在“堆硬件”之外,通过技术选型提升系统性能上限的最佳实践。
17.5. 本章总结
本章我们从理论选型走到代码落地,最后通过真实压测验证了架构决策的正确性。
- 架构决策:我们排除了臃肿的 Tomcat,引入了轻量级的 Undertow。
- 代码落地:在
UndertowConfig中,我们利用customize方法,将 Undertow 的 Worker 线程池偷梁换柱为VirtualThreadTaskExecutor。 - 测试验证:我们掌握了在 JMeter 中配置 RVP 专属鉴权头(
Authorization+clientid)的技巧,并见证了 Undertow 在虚拟线程加持下的强悍性能。








