性能 - Spring Boot - 服务器响应时间

15

我们的 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 方法。在此方法中,classjar 文件被排除在缓存之外。这就是为什么 war 文件会再次被扫描的原因。

扫描整个 war 文件需要大约 5 秒钟。这个中断是一个 stop the world 中断。此扫描绝对是不必要的,因为 war 文件没有被展开,因此其内容不可能被更改。

更新

如果我将 war 文件放入 tomcat 安装程序中,一切都很快。嵌入式 tomcat 是问题所在。


同样地,在已安装的Tomcat中,我们发现<Host unpackWARs="true">可以产生很大的差异。显然,直接在.war文件内部进行资源扫描与在扩展目录中进行比较是低效的。我相信当它们本身被归档时,在WEB-INF/lib中扫描jar文件的索引会更加耗费工作量。 - df778899
Spring Boot Web服务器的其他版本Jetty和Undertow怎么样? - ThomasRS
我需要服务器支持AJP。我认为Jetty已经放弃了他的AJP支持。不确定Undertow是否支持。无论如何,我想使用Tomcat。 - tomas
4个回答

4

我猜您已经看过了,如果还没有,请查看https://cwiki.apache.org/confluence/display/TOMCAT/HowTo+FasterStartUp并实施建议的修复措施。

如果要禁用嵌入式Tomcat的扫描程序,则在此处https://github.com/spring-projects/spring-boot/issues/1610的注释中有一个建议。

如果上述建议都不能帮助您解决延迟问题,则可能的解决方法是在服务器启动时进行第一次请求(并从那里触发延迟)。

@SpringBootApplication
public class Application implements CommandLineRunner {

    @Autowired
    private RestTemplate template;

    public static void main (String args[]){
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... strings) throws Exception {
        // do an initial request from here to trigger scanning the war
        template.exchange(...);
    }

}

通过这种方式,您的客户将不再遇到5秒的延迟。我知道这是一种hack方法,如果您找到更干净的方法,请使用那个方法。

希望这个黑客技巧能对你有所帮助 :) - user10367961
我已经放弃了将部署打包成fat jar的方式。当我将应用程序部署到Tomcat中时,它可以完美运行。 - tomas
似乎第一个URL已经移动到https://cwiki.apache.org/confluence/display/TOMCAT/HowTo+FasterStartUp,对于那些感兴趣的人。 - ajax992

1
我遇到了CPU使用率高和响应延迟的类似问题。扫描war文件时,org.apache.catalina.webresources.JarWarResourceSet:getArchiveEntries 耗时约5秒钟,在扫描期间未能处理任何请求。
我将Spring Boot版本1.4.2.RELEASE升级到1.5.12.RELEASE,问题得以解决。实际上,这个问题似乎是由于嵌入式Tomcat在后续版本中得到了修复。

1
我正在使用Spring Boot 2(2.0.5)。此版本包括嵌入式Tomcat版本8.5.34。但是问题仍然存在。 - tomas
@tomas 你介意分享一下你是如何解决这个问题的吗? - dev_in_prog
我也遇到了这个问题。使用jvisual vm和sampler进行本地检查,发现时间消耗在org.apache.catalina.webresources.JarWarResourceSet:getArchiveEntries中。对我来说,转移到jar不是一个选项。还有其他的想法吗? - arvin_v_s

1

谢谢您的回复。我非常确定当时尝试过JAR和WAR部署,但两者都存在性能问题。 - tomas
我从WAR转到JAR,就像在这里解释的那样,我不再有性能问题:https://github.com/spring-projects/spring-boot/issues/16471 - rochb

0

您所描述的是重启对使用大量数据库连接池的基础设施的典型影响。

  • 第一个请求:打开物理连接(100毫秒至2-3秒),进行一些初始化(取决于DB),执行SQL(每个查询都有所不同),返回到池中(<1ms)
  • 第二个请求:从池中提取(<1ms),执行SQL(每个查询都有所不同),返回到池中(<1ms)

根据您的数据,我最好的猜测是前两个步骤很慢,在DB池变暖之前,您将经历一些非常慢的查询。 可能的改进措施包括:

  • 在Tomcat尚未响应时配置自我初始化期间的预热时间
  • 检查DB端在连接创建时正在执行什么操作以及应用程序端是否需要设置连接的哪些配置

这也是我的第一反应。但连接池不是问题所在。通过性能分析,我发现它是WAR文件的扫描。 - tomas
如果你确定这个问题,我建议你考虑杀毒软件(因为它在打开war和检查字节码时会占用大量资源),以及容器化(有时会对I/O产生一些奇怪的副作用)。第一个问题(杀毒软件)可以通过禁用来轻松解决。第二个问题(容器化)通常要复杂得多,因为运行容器化应用与非容器化应用涉及到许多不同之处,因此你应该寻找具体的stackoverflow问题。 - Simone Avogadro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接