我们的 Spring Boot 应用出现了奇怪的行为:
- 前端/客户端 - Angular 6
- 后端 - Spring Boot - Spring MVC - 内嵌 Tomcat - Linux
在重新启动后端之后,第一次对控制器的调用需要约 5 秒钟,随后相同的请求只需要 50 毫秒。这种情况在 90% 的情况下是可重现的,有时甚至第一次调用也很快。
我确定问题在服务器上而不是客户端上。在浏览器中,我看到 TTFB 时间(首字节时间)增加到 5 秒。后续请求只需要 10 毫秒的 TTFB。
使用服务器上的监控工具(例如应用动力学),我可以收集这样的慢速服务器调用,并在调用图中看到:
org.apache.catalina.webresources.JarWarResourceSet:getArchiveEntries:117
需要 4916 毫秒,这是我的瓶颈所在。但我不知道该如何修复。
我已经尝试过的:
- 从 hikaricp 切换到 apache tomcat jdbc 连接池
- 将 spring boot 升级到 2.0.5
- 将 java 升级到 1.8.0_181
- 属性 spring.jpa.tomcat.testOnBorrow = true
- 属性 spring.jpa.tomcat.validationQuery = select 1
所有这些都没有影响服务器延迟。
更新
时间浪费在多次扫描 war 文件上。
org.apache.catalina.webresources.CachedResource.validateResource 检查是否有 war 文件 (isPackedWarFile),此检查返回 false,即使它是一个 war 文件。出现这种问题的原因是因为我设置了 tomcat.resource.cache-tt 的高值来解决。
但现在 org.apache.catalina.webresources.Cache.getResource 有一个 noCache 方法。在此方法中,class 和 jar 文件被排除在缓存之外。这就是为什么 war 文件会再次被扫描的原因。
扫描整个 war 文件需要大约 5 秒钟。这个中断是一个 stop the world 中断。此扫描绝对是不必要的,因为 war 文件没有被展开,因此其内容不可能被更改。
更新
如果我将 war 文件放入 tomcat 安装程序中,一切都很快。嵌入式 tomcat 是问题所在。
<Host unpackWARs="true">
可以产生很大的差异。显然,直接在.war
文件内部进行资源扫描与在扩展目录中进行比较是低效的。我相信当它们本身被归档时,在WEB-INF/lib
中扫描jar文件的索引会更加耗费工作量。 - df778899