Java_Online:Spring Boot 生产环境性能调优实战
从压测发现 P95=4s 到定位瓶颈、逐步调优,记录在 2 核 4G 虚拟机上对 Spring Boot 应用进行性能优化的完整过程。
Java_Online 生产环境性能调优实战
背景
Java_Online 是我部署在 2 核 4G 虚拟机上的 Spring Boot 生产服务,使用 Nacos 作为配置中心和注册中心(Nacos 配置优先级:java-online-service.yaml > common.yaml > 本地 application.yml)。服务上线后通过 /actuator/metrics 持续监控各项指标,某天发现 P95 响应时间飙升至 4 秒,需要立即排查。
诊断过程
第一步:线程池和连接池打满
通过 Actuator 检查两个关键指标:
# Tomcat 工作线程状态
curl http://localhost:8080/actuator/metrics/tomcat.threads.busy
# HikariCP 连接池活跃数
curl http://localhost:8080/actuator/metrics/hikaricp.connections.active
发现 Tomcat 工作线程池(默认 200)和 HikariCP 连接池(默认 10)全部打满。这是一个重要的信号——每个活跃线程都占着一个数据库连接。
第二步:CPU 上下文切换异常
使用 top -H -p $(pgrep -f java_online) 查看线程级 CPU 占用,发现大量线程处于活跃状态但单个线程 CPU 不高。更关键的是 sy%(系统 CPU 占用/上下文切换)高达 39%——这在一台 2 核机器上非常异常,说明大量时间花在线程调度而不是业务处理上。
第三步:定位根因
用 jstack 导出线程快照:
jstack <pid> > thread_dump.txt
分析发现大量线程处于 RUNNABLE 状态,并非阻塞在数据库查询或 I/O 上。结合 sy% 过高可以得出结论:
瓶颈不在于 SQL 查询,不在于应用逻辑,而在于 2 核 CPU 上线程数过多导致的线程竞争和上下文切换。 即使每个请求的处理时间很短,大量线程排队等待 CPU 调度的时间远大于实际处理时间。
调优方案
Tomcat 线程池压缩
将 server.tomcat.threads.max 从 200 大幅降低到 20:
server:
tomcat:
threads:
max: 20
mbeanregistry:
enabled: true
核心思路:线程数匹配 CPU 核心数。2 核机器上 20 个工作线程已经是经验值的上限(通常建议 2 * CPU核心数 + 1 左右,但对于 I/O 密集型应用可以适当放宽)。
HikariCP 连接池调整
将连接池从 10 提升到 20:
spring:
datasource:
hikari:
maximum-pool-size: 20
因为 Tomcat 线程减少后,每个线程持有连接的时间变短,但需要保证有足够连接可用不再成为瓶颈。
结果与反思
调优后 sy% 明显下降到 15% 以下,但 P95 依然在 4 秒左右。这个结果说明:在 2 核 4G 的硬件限制下,单纯靠参数调优无法根本解决问题。20 个线程对于 2 核机器来说依然过多。
真正的结论: 对于 /courses 这类纯查询接口,性能瓶颈往往是硬件资源(CPU 核心数)。正确的方向是进一步降低线程数(如尝试 10 个)或直接扩容硬件。
技术总结
- 线程池不是越大越好——超出 CPU 核心数的线程只会增加上下文切换成本
- sy% 是 CPU 瓶颈的关键指标——超过 20% 就说明上下文切换成为瓶颈
- 连接池和线程池要联动调整——一个打满通常说明另一个也需要调整
- Nacos 配置修改后要注意——YAML 缩进错误会导致配置不生效,通过
/actuator/env可以验证配置是否加载 - G1 GC 的优化方向:增加堆内存(从默认 256MB 到 1G)可以减少 GC 频率,但这不是当时的主要矛盾
这次调优让我深刻理解了"线程数匹配 CPU 核心数"这个原则在生产环境中的真实体现。也提醒我:在调优之前,先确认瓶颈到底在哪。