Spring Boot REST API服务首次调用超时性能问题排查求助
环境配置
- 虚拟机:VMware(4核/8GB内存)
- 操作系统:Ubuntu Server 18.04
- JDK:OpenJDK 11.0.7,启动参数:
JAVA_OPTS='-Djava.awt.headless=true -Xms512m -Xmx2048m -XX:MaxPermSize=256m' - 数据库:本地部署MySQL 5.7.30-0ubuntu0.18.04.1(应用通过主机名连接)
- 应用栈:Spring Boot 2.1.3(内置Tomcat、Spring Data JPA、HikariCP、Hibernate,均为默认配置)
核心问题现象
- 涉及服务:仅生产Web服务(PID2677)出现问题,测试服务(PID2698)和另一Web应用(PID2769)无异常
- 触发条件:首次调用或间隔5小时以上调用时,耗时超30s导致调用方超时;后续连续调用耗时<5s
- 调用特征:每日调用量最多10次,无并发场景
已完成排查动作
- 代码逻辑检查无异常
- 确认VMware/Ubuntu无自动挂起配置
- JVM内存/GC分析未发现异常
- 尝试每10分钟本地虚拟调用预热,未解决问题
下一步排查建议
1. 先盯紧HikariCP连接池与MySQL的交互
Spring Boot 2.1.x默认用HikariCP,虽然它的默认idleTimeout是10分钟,但MySQL的wait_timeout默认是8小时(28800秒)——你的问题刚好是5小时触发,大概率是连接池没正确检测到MySQL断开的闲置连接,导致首次调用时需要重新建立连接,甚至还要清理无效连接,这过程很容易拖慢响应。
你可以这么做:
- 登录MySQL执行
show variables like '%timeout%';,重点确认wait_timeout和interactive_timeout的数值,看看是不是和5小时接近 - 在应用里开启Hikari的DEBUG日志:添加
logging.level.com.zaxxer.hikari=DEBUG到配置文件,看首次调用时的日志,有没有“获取连接超时”“重建连接”这类关键词,记录下这部分的耗时
2. 排查主机名解析的坑
虽然是本地部署MySQL,但用主机名连接就可能涉及DNS解析或hosts配置的问题。比如/etc/hosts里没配置对应主机名的话,系统会走DNS查询,哪怕是本地主机,也可能出现解析延迟(尤其是闲置很久后,DNS缓存失效的情况)。
可以试试:
- 临时把应用的MySQL连接地址改成IP,测试间隔5小时后调用是否还超时(如果生产环境允许小范围测试的话)
- 用
time nslookup <你的MySQL主机名>多次测试,间隔5小时后再测一次,看看解析耗时有没有突然飙升的情况
3. 检查TCP连接的闲置断开情况
本地TCP连接闲置过久,也可能被系统的TCP保活机制或者虚拟机网络层断开。比如Ubuntu默认的tcp_keepalive_time是7200秒(2小时),如果连接闲置超过这个时间,系统会发送保活包,要是MySQL没响应,就会断开连接。
你可以:
- 执行
sysctl net.ipv4.tcp_keepalive_time查看当前配置,看看是否和5小时有相关性 - 在问题触发前和触发后,用
ss -tulpn | grep <MySQL端口>查看PID2677对应的连接状态,是ESTABLISHED还是TIME_WAIT/CLOSE_WAIT——如果是后者,说明连接已经被断开,首次调用需要重新建立
4. 看看Tomcat线程池是否在“偷懒”
Spring Boot内置Tomcat的线程池默认minSpareThreads是10,但如果应用长期闲置,Tomcat可能会回收空闲线程。当首次调用时,需要重新创建线程、加载相关类,虽然单线程创建耗时不长,但如果叠加其他延迟(比如数据库连接重建),就可能凑够30秒的超时。
你可以:
- 开启Tomcat线程池的DEBUG日志:
logging.level.org.apache.tomcat.util.threads=DEBUG,看首次调用时是否有线程创建的记录,耗时多久 - 临时调整Tomcat配置,把
server.tomcat.min-spare-threads设置成和server.tomcat.max-threads一样(比如都设为20),避免线程回收后重新创建的开销,测试是否解决问题
5. 排查Hibernate/JPA的闲置初始化问题
如果应用用了Hibernate的二级缓存,或者EntityManagerFactory长期闲置后被Spring容器回收,首次调用时需要重新初始化缓存或实体管理器,这也可能导致延迟。
可以检查:
- Hibernate的二级缓存配置,看缓存的过期时间是否刚好是5小时左右
- 查看
spring.jpa.open-in-view是否开启,这个配置如果开启,可能会让EntityManager长期持有,但闲置过久也可能出现异常
内容的提问来源于stack exchange,提问作者Miguel Angel

