性能与容量:连接池、线程池与 GC
约 1312 字大约 4 分钟
布欧-Lewyon
2026-05-16
首页 › Spring Boot › 架构师进阶 › 性能与容量:连接池、线程池与 GC
本篇服务 L4 架构师。假设已了解 HikariCP(数据源配置)、Tomcat 线程模型(Actuator 监控)与
@Async配置(定时任务与异步)。
容量规划不是"在线上出问题了再配置",而是在已知业务吞吐量目标下,提前算清系统能承载多少请求。Spring Boot 应用主要受三个资源池约束:HTTP 线程池、数据库连接池、任务线程池。
三层线程模型
请求进入 → Tomcat Acceptor → Poller → Worker 线程池
↓
Controller(业务逻辑)
↓
JdbcTemplate(数据库连接池)
↓
@Async 任务(自定义线程池)任何一层成为瓶颈,都会导致上游线程阻塞等待——最终表现为请求超时或 Tomcat 线程耗尽。
容量计算框架
以 Web 服务为例:
目标吞吐:1000 req/s
平均处理时延(p50):50ms
→ 需要的并发线程 = 1000 req/s × 0.05 s/req = 50 线程
安全系数 1.5× → 目标 Tomcat max-threads ≈ 75
数据库调用占 40ms → 数据库并发连接 ≈ 75 × 0.8 = 60
安全系数 1.5× → 目标 HikariCP max-pool ≈ 90核心公式:并发数 = QPS × 平均处理时延
Tomcat 线程数不等于用户数——10 个 Tomcat 线程能处理 200 req/s 的请求(如果每个请求在 50ms 内完成),因为线程在等待 IO 时 CPU 可以切换处理其他请求。
各资源池配置建议
Tomcat 线程池
server:
tomcat:
threads:
max: 200
min-spare: 10
max-connections: 8192
accept-count: 100max-threads应 ≤ 数据库连接池的maximum-pool-size——否则 Tomcat 线程等连接时会阻塞- 不是越大越好:200 线程 ≈ 200 MB 栈内存 + 上下文切换开销
- Boot 4 虚拟线程默认启用时,Tomcat 的 Worker 使用虚拟线程,
max-threads配置不限制虚拟线程数
HikariCP 连接池
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
max-lifetime: 1800000
leak-detection-threshold: 60000maximum-pool-size应与 Tomcatmax-threads呈正相关- PostgreSQL 场景:每个连接约 10MB 内存,
max-pool-size=50意味着数据库侧约 500MB 内存 leak-detection-threshold是生产必配参数——连接泄漏在测试环境很难复现
@Async 线程池
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}拒绝策略选型:
| 策略 | 行为 | 适用场景 |
|---|---|---|
AbortPolicy | 抛 RejectedExecutionException(默认) | 关键任务丢数据不可接受 |
CallerRunsPolicy | 提交者线程自己执行 | 降级——用业务线程代替,会阻塞调用方 |
DiscardPolicy | 静默丢弃 | 日志、指标等可丢失 |
DiscardOldestPolicy | 丢弃队列最旧的任务 | 实时性要求高 |
架构视角:GC 与延迟
| GC 算法 | JDK 版本 | 特点 | 适用 |
|---|---|---|---|
| G1(默认) | 9+ | 停顿可控,-XX:MaxGCPauseMillis=200 | 大部分 Web 服务 |
| ZGC | 17+(实验性)→ 21+(生产) | 停顿 < 1ms,堆可达 16TB | 大堆场景(>32GB) |
| Shenandoah | 17+(实验性)→ 21+(生产) | 停顿 < 10ms,与 ZGC 选一 | 低延迟要求 |
# ZGC 生产配置(JDK 21+)
-XX:+UseZGC
-XX:MaxRAMPercentage=75.0
-XX:ConcGCThreads=2
-Xlog:gc*:file=/var/log/gc.log:time,uptime,pid,tidL4 建议:不要在生产产线启动 3 天后才看 GC 日志——在压测阶段就借助 GC 可视化工具(GCeasy、GCEasy)分析 GC 频率与停顿。GC 日志不是"出了问题才打开"的开关,生产环境始终启用 GC 日志(日志轮转已自动支持)。
架构视角:压测方法
正确的压测不是"打满请求直到报错"——而是一种系统行为建模:
- 确定目标:预期线上峰值 QPS × 1.5 安全系数
- 逐步加压:从 50 QPS 开始,每 30 秒增加 50 QPS,记录每个阶梯的 p50/p99 延迟
- 找到拐点:p99 从平稳变成线性增长的点 = 系统的真实容量
- 资源监控:同时观测 CPU、内存、GC 频率、连接池使用率、线程活跃数
QPS p50 p99 CPU% ActiveConn
50 8ms 20ms 15% 5
100 9ms 22ms 25% 10
200 10ms 25ms 45% 18
400 12ms 30ms 70% 32 ← 开始拐弯
600 20ms 80ms 85% 48 ← p99 开始劣化
800 50ms 300ms 92% 60 ← 容量上限从这个示例看,系统的安全容量在 300 QPS 左右(p99 < 50ms),极限容量约 600 QPS(p99 < 100ms)。运营时目标吞吐应 ≤ 安全容量,告警阈值设在 70%。
小结
- 容量计算:
并发数 = QPS × 平均处理时延——三层线程池联动配置。 - 资源池配置原则:Tomcat max-threads ≤ HikariCP maximum-pool-size。
- GC 选型:G1(默认)、ZGC(JDK 21+ 大堆首选)。GCD 日志始终开启。
- 压测方法论:逐步加压 + 拐点识别 + 资源联动监控。
- 架构视角:容量规划不是"一劳永逸"——每次接口逻辑变更、中间件版本升级、流量模型变化后都应有量化的复测。建议将压测基线纳入 CI/CD 门禁。
- 思考任务:对项目做一次简单的 JMeter 压测,逐步增加 QPS 直到 p99 > 200ms,记录拐点;根据拐点数据调整 Tomcat 线程池和 HikariCP 的
maximum-pool-size参数。
上一节:模块化与系统边界
下一节:事件驱动与一致性
