文章目录
-
- Tomcat和Undertow比对
- 配置差异
- [HttpHandler 案例](#HttpHandler 案例)
Tomcat和Undertow比对
| 维度 | Tomcat | Undertow | 备注 |
|---|---|---|---|
| 核心定位 | 传统 Servlet 容器,事实标准 | 轻量级、高性能 Web 服务器 + Servlet 容器 | 都可做 Spring Boot 内嵌容器 |
| 模型 | 基于 Connector + ProtocolHandler 的线程模型 | 事件驱动 + 非阻塞 IO(XNIO) | Undertow 在高并发连接下更占优势 |
| Servlet 支持 | 完整 Servlet/JSP 支持,生态成熟 | 支持 Servlet 规范,但自身更偏 Handler 管线 | Spring 体系下一般不再推荐使用 JSP |
| 协议 | HTTP/1.1,支持 HTTP/2(8.5+ 配置或代理层) | HTTP/1.1,支持 HTTP/2(取决于具体版本与配置) | 实际多通过 Nginx 等代理终止 HTTP/2 |
| WebSocket | org.apache.tomcat.websocket 实现 | 内置 WebSocket 支持,事件驱动 | 两者都能满足常规 WebSocket 场景 |
| AJP | 原生支持 AJP | 不强调 AJP | 现代架构中 AJP 使用逐渐减少 |
| 性能(吞吐) | 已足够强,大多数业务场景差异不明显 | 理论上延迟更低、吞吐略高 | 压测场景下 Undertow 通常略优 |
| 连接/线程模型 | 典型:一个请求绑定一个工作线程 | 非阻塞 IO,线程数可相对更少 | 极端高并发长连接下 Undertow 更有优势 |
| 内存占用 | 相对略高 | 相对略低 | 实际差异视场景与调优而定 |
| 配置复杂度 | Spring Boot 下极简(server.tomcat.*) | Boot 2.x 有 server.undertow.*;Boot 3.x 需自实现配置绑定 | Boot 3 以后 Undertow 维护成本更高 |
| 生态与资料 | 非常丰富,Spring 官方默认 | 文档和案例相对较少 | 团队学习与排障成本 Tomcat 明显更低 |
| Spring Boot 集成 | 默认内嵌容器,开箱即用 | Boot 2.x 有官方 starter;Boot 3.x 官方 starter 已移除,需自集成或用三方 starter | 升级/统一标准时推荐 Tomcat |
| 适用场景 | 绝大部分企业级 Web/REST 系统 | 对极致性能、高并发连接、轻量部署有强需求且团队可接受维护成本的少数服务 | 适合作为"特种兵"容器而非常规默认 |
简单说明:
Undertow优点:
- Undertow对大并发长连接(大量 keep-alive、WebSocket)更友好。在"简单业务逻辑 + 高并发压测"场景下,QPS 和 P99 延迟通常略优于 Tomcat。
- io-threads + worker-threads 模型,更接近 Netty 的风格;利用 CPU 更均衡,不那么依赖"一个请求绑一个线程"的模式。
- 不仅能用 Servlet,也可以用 Undertow 自己的 HttpHandler 管线;可以做一些容器的定制化开发。
Undertow缺点:
- Spring Boot 3 开始,官方移除了 Undertow starter,意味着额外的维护和测试成本
- 绝大部分中间件、监控探针、APM、诊断文档,都优先支持 Tomcat 场景。Undertow 虽然支持 Servlet,但在这些生态上的优先级明显低。
- "最大连接数 / 队列"等参数不那么直观。Tomcat 有 max-connections / accept-count,很好理解。Undertow 没有 1:1 的"最大连接数"配置,需要你解释:连接处理能力由 io-threads + worker-threads + OS backlog + 网关综合决定
配置差异
tocmat
java
server:
#tomcat内核参数调优
tomcat:
#等待队列长度,默认100
accept-count: 1000
#最大链接数
max-connections: 10000
uri-encoding: UTF-8
threads:
#最大工作线程数,默认200
max: 800
#最小工作线程数,默认10
min-spare: 100
Undertow
java
server:
# Undertow 内核参数调优
undertow:
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
# 不要设置过大,如果过大,启动项目会报错:打开文件数过多
io-threads: 16
# 工作线程数:对应 Tomcat 的 threads.max(负责真正业务处理),默认值是IO线程数*8
worker-threads: 800
# 使用直接内存缓冲区,提高 IO 性能
direct-buffers: true
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分,不要设置太大,以免影响其他应用,合适即可
buffer-size: 16384
# 每个区分配的buffer数量 , 所以pool的大小是buffer-size * buffers-per-region
buffers-per-region: 1024
# HTTP POST 最大体积(防止恶意大包)
max-http-post-size: 20MB
#用于解码 URL 的字符集,默认UTF-8
url-charset: UTF-8
#默认不需要配置
threads:
#为工作线程创建的 I O 线程数。默认值源自可用处理器的数量=16
:io:
工作线程数。默认是I O线程数的8倍:16*8=128
worker:
最大同时连接数不是直接配置,通过 buffer-size和 buffers-per-region综合计算,也不能配置最小连接数
HttpHandler 案例
java
public class UndertowHttpHandlerDemo {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public static void main(String[] args) {
Undertow server = Undertow.builder()
.addHttpListener(8080, "0.0.0.0")
// 使用 path() 做简单路由分发
.setHandler(
path()
.addExactPath("/hello", new HelloHandler())
.addExactPath("/echo", new EchoHandler())
// POST /json,使用 BlockingHandler 方便读请求体
.addExactPath("/json", new BlockingHandler(new JsonHandler()))
)
.build();
server.start();
System.out.println("Undertow started on http://localhost:8080");
}
/**
* 示例 1:最简单的 HttpHandler,返回纯文本
*/
public static class HelloHandler implements HttpHandler {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
// 设置响应头
exchange.getResponseHeaders()
.put(Headers.CONTENT_TYPE, "text/plain; charset=UTF-8");
// 写响应
exchange.getResponseSender().send("Hello from Undertow HttpHandler!");
}
}
}