目录
[第 1 层:操作系统 TCP 协议栈(内核级瓶颈)](#第 1 层:操作系统 TCP 协议栈(内核级瓶颈))
[第 2 层:JVM 与 Web 服务器连接接收(Tomcat Acceptor/Poller)](#第 2 层:JVM 与 Web 服务器连接接收(Tomcat Acceptor/Poller))
[第 3 层:Tomcat 线程池与连接队列(核心并发控制层)](#第 3 层:Tomcat 线程池与连接队列(核心并发控制层))
[第 4 层:Servlet 容器与应用逻辑(业务层瓶颈)](#第 4 层:Servlet 容器与应用逻辑(业务层瓶颈))
[第 5 层:下游依赖(隐藏的天花板)](#第 5 层:下游依赖(隐藏的天花板))
前沿
"系统的并发能力,取决于最弱的那一环。"
在高并发场景下,Spring Boot 应用的吞吐量并非由单一组件决定,而是由 操作系统、网络协议栈、Web 服务器、应用容器、业务逻辑 五层共同约束。
本文将逐层拆解,揭示每层如何影响并发,并提供可落地的优化方案。
第 1 层:操作系统 TCP 协议栈(内核级瓶颈)
作用
当客户端发起 connect()请求,数据包首先经过 Linux 内核的 TCP/IP 协议栈。
关于TCP/IP的介绍,可参考:计算机的网络体系及协议模型介绍
内核维护两个关键队列:
| 队列类型 | 作用 | 默认大小 |
|---|---|---|
| 半连接队列(SYN Queue) | 存放收到 syn 但未完成三次握手的连接 | net.ipv4.tcp_max_syn_backlog(通常 256~1024) |
| 全连接队列(Accept Queue) | 存放已完成三次握手、等待应用 accept() 的连接 | min(somaxconn,backlog) |
backlog 是应用调用 listen(fd,backlog) 时传入的值(Tomcat 默认为 accept - count + 1)
⚠️ 瓶颈表现
-
客户端报错:Connection refused 或 timeout
-
服务端无任何日志(因为请求未进入应用)
-
ss -lnt 显示 Recv-Q 持续堆积
-
/proc/net/netstat 中 ListenOverflows 计数增长
调优参数(/etc/sysctl.conf)
java
# 全连接队列最大长度(必须 ≥ Tomcat accept-count)
net.core.somaxconn = 65535
# 半连接队列长度
net.ipv4.tcp_max_syn_backlog = 65535
# 启用 SYN Cookies 防 SYN Flood(可选)
net.ipv4.tcp_syncookies = 1
生效命令
bash
sudo sysctl -p
验证命令
bash
# 查看当前监听队列状态
ss -lnt
# 输出示例:
# State Recv-Q Send-Q Local Address:Port Peer Address:Port
# LISTEN 128 128 *:8080 *:*
# Recv-Q > 0 表示有连接堆积
# Send-Q = min(somaxconn, backlog)
# 查看溢出次数
cat /proc/net/netstat | awk '/TcpExt/ {print $21}' # ListenOverflows
💡 关键原则:somaxconn 必须 ≥ Tomcat 的 accept-count,否则后者无效。
第 2 层:JVM 与 Web 服务器连接接收(Tomcat Acceptor/Poller)
如下所示:

作用
Tomcat 使用 NIO 模型处理连接:
-
Acceptor 线程:调用 ServerSocket.accept() 从内核全连接队列取连接
-
Poller 线程:将新连接注册到 Selector,监听读写事件
⚠️ 瓶颈表现
-
内核队列堆积(Recv-Q
> 0),但 Tomcat 线程池空闲 -
Acceptor 线程 CPU 占用高(罕见)
关键配置(application.properties)
bash
# Acceptor 线程数(默认 1,高并发建议 2~4)
server.tomcat.acceptor-thread-count=2
# Poller 线程数(默认 min(2, CPU 核数))
server.tomcat.poller-thread-count=4
工作流程
bash
[内核全连接队列]
↓ (Acceptor 线程调用 accept())
[Tomcat 连接对象]
↓ (Poller 线程注册到 Selector)
[等待 HTTP 请求数据]
💡 注意:Acceptor/Poller 不处理业务逻辑,只负责 I/O 事件分发,通常不是瓶颈。
第 3 层:Tomcat 线程池与连接队列(核心并发控制层)
如下所示:

作用
Tomcat 使用线程池处理 HTTP 请求:
-
工作线程(Worker Threads):执行 Servlet.service() 方法
-
连接队列(Accept Count):当所有工作线程 busy 时,新连接在此排队
⚠️ 瓶颈表现
-
所有工作线程处于 Runnable 或 Blocked 状态
-
新请求被拒绝(返回 503 或连接超时)
-
日志无异常,但压测 QPS 上不去
关键配置
java
# 最大工作线程数(默认 200)
server.tomcat.threads.max=1000
# 最小空闲线程(避免频繁创建销毁)
server.tomcat.threads.min-spare=50
# 连接队列大小(默认 100)
server.tomcat.accept-count=200
并发容量计算:
理论最大并发连接数 = max-threads + accept-count
-
例如:max
= 1000, accept-count= 200→ 最多处理 1200 个并发连接 -
超过此数,新连接被 直接拒绝
监控命令
bash
# 查看 Tomcat 线程状态
jstack <pid> | grep "http-nio" | wc -l
# 查看线程堆栈(定位 BLOCKED 原因)
jstack <pid> | grep -A 20 "BLOCKED"
调优建议
-
I/O 密集型 (如 API 网关):max-threads
= 500~2000 -
CPU 密集型 (如图像处理):max-threads
≈ CPU 核数 -
内存限制:每个线程栈默认 1MB,1000 线程 ≈ 1GB 内存
第 4 层:Servlet 容器与应用逻辑(业务层瓶颈)
如下所示:

作用
Spring Boot 的 DispatcherServlet 处理请求,调用你的 @RestController方法。
⚠️ 瓶颈表现
-
Tomcat 线程全部 Runnable,但 CPU 利用率低。
-
线程堆栈显示大量 Blocked 在 synchronized 或wait()。
-
GC 频繁(Full GC)。
常见问题与优化.
- 同步阻塞 I/O
java
// ❌ 反例:同步调用外部服务
@GetMapping("/slow")
public String callExternal() {
return restTemplate.getForObject("http://slow-service", String.class); // 阻塞线程
}
→ 解决方案:使用 CompletableFuture 或 WebFlux 异步化
- 全局锁竞争
java
// ❌ 反例:静态 synchronized 方法
public static synchronized void updateCache() { ... }
→ 解决方案:缩小锁粒度、使用 ConcurrentHashMap
- 大对象分配 & GC 压力
-
避免在循环中创建大对象
-
使用对象池(谨慎)
-
调整 JVM 参数(G1GC)
性能公式:
有效并发 = max-threads / 平均响应时间(秒)
-
RT = 100ms → 1000 线程 → QPS = 10,000
-
RT = 2s → 1000 线程 → QPS = 500
💡 优化方向:降低 RT(缓存、异步、DB 优化)
第 5 层:下游依赖(隐藏的天花板)
📌 作用
应用通常依赖数据库、缓存、消息队列等外部服务。
⚠️ 瓶颈表现
-
Tomcat 线程全部
BLOCKED在getConnection() -
数据库 CPU 100%,但应用 QPS 很低
-
Redis 连接池耗尽
关键配置示例
- HikariCP 数据库连接池
bash
# 必须 ≥ 预期并发 DB 操作数
spring.datasource.hikari.maximum-pool-size=200
- Redis 连接池(Lettuce)
java
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
var config = new GenericObjectPoolConfig();
config.setMaxTotal(200); // 连接池大小
return new LettuceConnectionFactory(redisStandaloneConfiguration,
LettucePoolingClientConfiguration.builder().poolConfig(config).build());
}
- HTTP 客户端连接池(Apache HttpClient)
java
@Bean
public CloseableHttpClient httpClient() {
return HttpClients.custom()
.setMaxConnTotal(200)
.setMaxConnPerRoute(50)
.build();
}
📊 排查命令
sql
-- MySQL 查看活跃连接
SHOW PROCESSLIST;
-- PostgreSQL
SELECT count(*) FROM pg_stat_activity;
💡 黄金法则:下游依赖的并发能力 ≥ 应用层并发能力
结论:
| 层级 | 关键参数 | 默认值 | 调优目标 | 监控命令 |
|---|---|---|---|---|
| OS TCP | net.core.somaxconn | 128/4096 | ≥ Tomcat accept-count | ss -lnt |
| Tomcat Acceptor | acceptor-thread-count | 1 | 2~4 | jstack |
| Tomcat 线程池 | threads.max | 200 | 500~2000 | Actuator metrics |
| 应用逻辑 | 响应时间(RT) | --- | 降低 RT | APM, jstack |
| 下游依赖 | DB 连接池 | 10 | ≥ 应用并发 | db show processlist |
总结:
最终并发能力 = min(各层上限); 优化必须 自底向上,先确保底层不成为瓶颈。