大家好,我是G探险者!
在 Java 服务体系中,Tomcat 与 Jetty 是最常见的 Web 容器。很多人认为,只要加大连接数或线程数,就能提升并发能力。
但事实远比想象复杂:连接数只是冰山一角,背后还有线程模型、I/O 模型、内核资源限制等多重因素。
本文从 Tomcat 与 Jetty 的对比 入手,系统讲清楚------到底是什么在决定你的服务并发上限。
🧩 一、Tomcat 与 Jetty 的架构差异
1️⃣ Tomcat 的工作模型
Tomcat 默认采用 多线程 + 阻塞 I/O (BIO/NIO) 模型。
它通过 maxConnections
限制 TCP 连接数,通过 maxThreads
限制同时处理的请求线程。
简化模型如下:
css
[客户端连接] → [TCP连接池 (maxConnections)]
↓
[请求队列 (acceptCount)]
↓
[线程池 (maxThreads)] → [业务处理]
- 一个请求在被线程接管后,线程会一直阻塞直到请求完成。
- 若请求阻塞(如访问数据库或远程服务),线程被占用,会直接影响整体并发。
2️⃣ Jetty 的工作模型
Jetty 默认使用 非阻塞 NIO 模型 ,基于 Reactor 模式。
它的核心理念是:
用少量 Selector 线程负责所有连接的读写事件,用工作线程处理真正的业务逻辑。
模型如下:
css
[客户端连接] → [Selector线程 (少量)]
↓
[事件分发] → [工作线程池 (maxThreads)] → [业务处理]
优势:
- 一个线程可同时管理上千连接(非阻塞 I/O);
- 支持更高的空闲连接与 KeepAlive 连接数;
- 在高并发长连接场景(如 WebSocket、HTTP/2)中明显优于 Tomcat。
⚙️ 二、Tomcat 与 Jetty 的默认并发参数对比
项目 | Tomcat(Spring Boot 默认) | Jetty(Spring Boot 可选) | 说明 |
---|---|---|---|
默认线程数 | 200 (server.tomcat.max-threads ) |
200 (QueuedThreadPool.maxThreads ) |
可同时处理请求的线程数 |
默认最大连接数 | 10000 (server.tomcat.max-connections ) |
无固定值(由 Selector 与 OS 限制) | 同时打开的 TCP 连接数上限 |
默认等待队列 | 100 (server.tomcat.accept-count ) |
内部队列(动态) | 连接满后等待请求数 |
I/O 模型 | 阻塞式 NIO | 事件驱动式 NIO | 决定每个线程能管理多少连接 |
KeepAlive 管理 | 每连接占线程资源 | 非阻塞复用线程 | Jetty 更高效 |
配置灵活性 | 通过 server.tomcat.* |
通过 server.jetty.* 或 Java API |
Jetty 灵活但复杂 |
典型并发能力 | 5K~10K(视CPU与内存) | 10K~30K(视操作系统限制) | Jetty 在高连接数场景下优势明显 |
🔍 三、影响服务并发能力的核心因素
很多人只调 maxThreads
或 maxConnections
,其实那只是表象。
一个服务的真正并发能力受以下五个层面的影响:
1️⃣ 应用层:线程模型与I/O模型
模型类型 | 代表容器 | 特点 | 并发能力 |
---|---|---|---|
BIO(阻塞IO) | Tomcat早期版本 | 每连接占线程 | 低 |
NIO(非阻塞IO) | Tomcat 8+/Jetty | 一个线程可处理多个连接 | 中高 |
AIO(异步IO) | Jetty/Undertow/Netty | 完全事件驱动 | 高 |
🧠 结论 :
选择非阻塞 I/O(如 Jetty、Netty)通常能显著提升并发上限。
2️⃣ 容器层:线程池与连接池配置
以 Tomcat 为例:
ini
server.tomcat.max-connections=20000
server.tomcat.max-threads=500
server.tomcat.accept-count=1000
- maxConnections:并发TCP连接上限
- maxThreads:同时处理请求上限
- acceptCount:连接满后的排队长度
如果线程数配置过低,CPU空闲但请求积压;
配置过高,又会导致频繁上下文切换,反而变慢。
🧠 经验值:
- CPU密集型应用:
maxThreads
≈ 核数 × 2 - I/O密集型应用:
maxThreads
≈ 核数 × 5~10
3️⃣ 系统层:操作系统文件描述符与TCP栈
操作系统对连接的限制非常关键:
限制项 | Linux参数 | 说明 |
---|---|---|
最大文件描述符数 | ulimit -n |
每个连接占一个fd |
TCP backlog 队列 | /proc/sys/net/core/somaxconn |
等待接入的连接队列 |
TIME_WAIT 超时 | /proc/sys/net/ipv4/tcp_fin_timeout |
连接关闭延迟释放 |
端口复用 | /proc/sys/net/ipv4/tcp_tw_reuse |
减少端口耗尽 |
⚠️ 若 ulimit -n
太小(如 1024),即使 Tomcat 配了 20000 连接也无法生效。
4️⃣ 数据库层:连接池与事务等待
应用层的高并发最终要落到数据库连接上。
若连接池上限(如 HikariCP 的 maximumPoolSize
)太小,会出现:
"Tomcat 能接 2000 个请求,但只有 50 个能访问数据库,其他都在排队。"
🧠 调优思路:
- DB连接数 ≈ 应用活跃线程数 × 访问数据库比例
- 用缓存/异步队列减少数据库访问占比
5️⃣ 网络层:负载均衡与KeepAlive策略
- KeepAlive 过短 → 频繁重建连接,耗时;
- KeepAlive 过长 → 连接资源被空闲占用;
- 负载均衡(如 Nginx) 的超时与连接复用策略,也会直接影响并发稳定性。
🧠 建议:
- 对静态请求使用 KeepAlive;
- 对动态或长轮询请求启用超时检测与连接回收;
- 配合 Nginx 使用
keepalive_timeout 30s
、max_fails
等策略。
📈 四、Tomcat 与 Jetty 并发性能对比总结
维度 | Tomcat | Jetty |
---|---|---|
适用场景 | 标准Web应用(同步请求为主) | 高并发、长连接、WebSocket |
默认配置 | 守旧(maxThreads=200) | 灵活、事件驱动 |
可维护性 | Spring Boot默认支持最佳 | 可扩展性强(嵌入式更灵活) |
性能瓶颈 | 线程阻塞、队列等待 | 网络带宽、Selector数量 |
调优重点 | maxThreads / maxConnections | 线程池与连接限制Bean |
理论并发上限 | 1万左右 | 2万~3万以上(视系统资源) |
🧠 简单说:
- Tomcat 胜在稳定与生态;
- Jetty 胜在轻量与高并发。
🧭 五、结语:并发的核心不止"连接数"
很多人问:"Tomcat 支持 10000 连接,我能处理 10000 并发吗?"
答案是:不一定。
因为:
- 并发连接数 ≠ 并发请求数;
- 并发请求数 ≠ 并发业务处理数;
- 真正能撑起高并发的,是架构、线程模型、系统配置与资源调度的整体平衡。
🧠 最佳实践:
- 调优容器参数(连接数、线程数、队列长度);
- 优化应用逻辑(异步化、减少阻塞);
- 提升系统层资源限制(
ulimit -n
、TCP参数); - 引入缓存与异步解耦(Redis、MQ);
- 在高并发长连接场景下,考虑 Jetty 或 Netty 替代 Tomcat。
✅ 总结一句话
影响服务并发能力的核心,不只是连接数。
它取决于:
- 容器的 I/O 模型(阻塞 / 非阻塞)
- 线程池与连接池配置
- 操作系统资源限制
- 后端依赖(数据库 / MQ)性能
而 Tomcat 与 Jetty 的差异,恰好体现了"并发设计哲学"的两种思路:
- Tomcat:稳定可靠的线程池模型
- Jetty:事件驱动的高连接复用模型