
Spring Cloud 接口无报错却变慢先看线程栈和连接池接口没有报错但延迟升高时扩容 JVM 往往只是把排队藏得更久。先抓线程栈核对连接池等待、锁竞争和 GC再沿调用链看下游。本文把证据顺序写成可执行的排查路径。第一步检查 Tomcat/Netty 堆栈与 Worker 线程池状态当 Spring Boot 微服务处理请求变慢时首先要查看当前处理线程是否都被卡在了某个阻塞调用点上。利用jstack输出线程 Dump或者通过 Spring Boot Actuator 的/actuator/metrics/tomcat.threads.busy接口获取实时指标。如果发现 busy 线程数达到了max-threads设置的上限默认通常为 200通过堆栈信息查找这些线程停留在哪里tomcat-handler-45 #88 daemon prio5 os_prio0 tid0x00007f91a0028000 nid0x1a3b waiting on condition java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for 0x000000076a08f120 (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:172) at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:162) at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128)如果是上述堆栈说明 Tomcat 线程并没有在执行复杂的业务计算而是全都在等待获取数据库连接HikariPool.getConnection。此时问题根源并不在 Tomcat而在数据库连接池与慢 SQL。第二步检查 HikariCP 连接池与下游 HTTP 连接池配置对于基于 Spring Cloud 构筑的微服务连接池配置不合理是导致卡顿的重灾区。常见的典型配置误区是把 HikariCP 的maximum-pool-size设得明显例如设为 100以为连接越多越快。但实际上CPU 核心数有限过多的数据库连接反而会导致数据库服务器侧发生剧烈的上下文切换与磁盘 IO 锁争抢。针对 8 核 CPU 的微服务节点合理的 HikariCP 与 OpenFeign 连接池参数优化配置如下spring: datasource: hikari: # 连接池大小公式: (Core CPU * 2) 磁盘有效并发 maximum-pool-size: 20 minimum-idle: 10 connection-timeout: 3000 # 3秒获取不到连接立刻报错防止无限卡死 idle-timeout: 600000 max-lifetime: 1800000 feign: httpclient: enabled: true max-connections: 500 # 全局最大连接数 max-connections-per-route: 50 # 单个微服务路由的最大连接数 client: config: default: connectTimeout: 1000 # 建立连接超时 1s readTimeout: 3000 # 读取响应超时 3s特别注意max-connections-per-route参数。如果不做专门设置默认值往往非常小某些 HTTP Client 默认单个 Route 只给 2 或 5 个连接导致上百个线程在争抢这几个 HTTP 连接造成严重的队头阻塞卡顿。第三步检查 JVM SafePoint 停顿与 GC 垃圾回收日志如果线程堆栈和连接池看起来都很正常但请求仍周期性整体停顿就需要把 SafePoint安全点日志与 GC 日志放到同一时间轴上排查。有些时候虽然 GC 发生的次数不多但是因为代码中存在大循环没有被 JIT 编译为 Counted LoopJVM 在进入 SafePoint 时需要等待循环结束从而导致了超长的 STWStop-The-World停顿。需要在 JVM 启动参数中加上日志监控# JDK 11 推荐的 SafePoint 与 GC 日志配置 -Xlog:gc*,safepointinfo:file/tmp/gc-safepoint.log:time,uptime,pid:filecount5,filesize50M分析/tmp/gc-safepoint.log中的关键行[2026-08-09T00:12:45.1230800] Total time spent in GC pauses: 0.045 seconds [2026-08-09T00:12:45.1680800] Leaving safepoint region [2026-08-09T00:12:45.1690800] Reached safepoint: Application time: 12.456 seconds, Time to stop: 0.852 seconds示例日志里的Time to stop: 0.852 seconds表示线程到达安全点前的等待时间而不是 GC 本身的耗时。若自己的日志也在这一列出现高值再检查大循环、JNI 等阻碍线程进入安全点的代码调整-XX:UseCountedLoopSafepoints前应先在同版本 JVM 上验证吞吐与停顿变化。调优前后的性能指标对比用同一请求集分别测试当前配置与候选配置并把线程、连接池和 SafePoint 指标对齐到请求时间线监控指标采集方法当前配置候选配置P99 响应延时同一脚本保存原始样本统计分位值统计分位值容量边界逐级加压且错误率不越界记录实际 QPS记录实际 QPSTomcat 活跃线程Prometheus 同一时间窗记录峰值与队列记录峰值与队列SafePoint 停顿JVM 日志与请求 Trace 对齐统计分位值统计分位值总结卡顿排障的避坑清单第一不要依赖 Spring Default 配置直接上生产。默认的 Ribbon/Feign 超时设置、默认的 Tomcat 线程池队列长度都是为了兼容小项目设计的在高并发场景下容易引发雪崩。第二先看监控指标再看线程堆栈最后看日志。微服务卡顿时业务日志往往是静默的只有线程堆栈jstack和指标Prometheus Metrics才能反应系统的真实生存状态。