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

Posted

技术标签:

【中文标题】性能 - Spring Boot - 服务器响应时间【英文标题】:Performance - Spring Boot - Server Response Time 【发布时间】:2019-03-09 23:17:26 【问题描述】:

我的 Spring Boot 应用程序出现了一个奇怪的行为:

前端/客户端 - 角度 6 后端 - spring boot - spring MVC - 嵌入式 tomcat - Linux

重新启动后端后,对控制器的第一次调用大约需要 5 秒,以下相同的请求只需要 50 毫秒。这在 90% 的情况下是可重现的,有时甚至第一次调用也很快。

我确定,问题出在服务器上而不是客户端上。在浏览器上,我看到 TTFB 时间(到第一个字节的时间)增加到 5 秒。以下请求只需要 10ms 的 TTFB。

通过服务器上的监控工具(应用程序动态),我可以收集如此慢的服务器调用,并且在调用图上我可以看到:

org.apache.catalina.webresources.JarWarResourceSet:getArchiveEntries:117

需要 4916 毫秒。所以这是我的瓶颈,我想。但我不知道如何解决它。

我已经尝试过的:

从 hikaricp 切换到 apache tomcat jdbc 连接池 将弹簧靴从 2.0.0 升级到 2.0.5 Java 升级到 1.8.0_181 属性 spring.jpa.tomcat.testOnBorrow = true 属性 spring.jpa.tomcat.validationQuery = 选择 1

一切都不会影响服务器延迟。

更新

由于war文件被多次扫描而导致时间丢失。

org.apache.catalina.webresources.CachedResource.validateResource 正在检查我们是否有一个 war 文件 (isPackedWarFile),这个检查返回 false。即使它是一个战争文件。对于这种行为不端,我有一个解决方法。我将 tomcat.resource.cache-tt 设置为高值。

但是现在 org.apache.catalina.webresources.Cache.getResource 有一个 noCache 方法。在这种方法中,classjar 文件被排除在缓存之外。这就是再次扫描war文件的原因。

扫描整个战争文件大约需要 5 秒。而这个突破是一个停止世界的突破。而且这种扫描是完全没有必要的,因为war文件没有被炸开,所以它的内容是不能改变的。

更新

如果我将战争文件放入 tomcat 安装中,一切都会很快。嵌入式tomcat就是问题所在。

【问题讨论】:

类似地,在已安装的 Tomcat 中,我们发现<Host unpackWARs="true"> 可以产生很大的不同。与扩展目录相比,直接在.war 中进行资源扫描显然效率低下。我相信扫描WEB-INF/lib 中的jar 文件的索引,当它们本身被归档时会更有效。 Spring Boot Web 服务器的其他风格 - jetty 和 undertow 怎么样? 我需要服务器的 AJP 支持。我认为码头已经放弃了对 AJP 的支持。不确定是否有低潮。无论如何,我想使用 tomecat。 【参考方案1】:

我想您已经这样做了,但如果您还没有这样做,请查看 https://cwiki.apache.org/confluence/display/TOMCAT/HowTo+FasterStartUp 并实施那里建议的修复。

关于禁用嵌入式 tomcat 扫描,这里的 cmets 中有一个建议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(...);
    


这样,您的客户端将不再遇到 5s 延迟。我知道这是一个 hack,所以如果你找到一种更简洁的方法来代替它。

【讨论】:

希望至少黑客能帮助你:) 我现在已经放弃了作为 fat jar 的部署。当我将我的应用程序部署到 tomact 中时,它运行良好。 对于那些感兴趣的人来说,第一个网址似乎已移至cwiki.apache.org/confluence/display/TOMCAT/HowTo+FasterStartUp【参考方案2】:

我遇到了类似的 CPU 使用率高和响应延迟的问题。 org.apache.catalina.webresources.JarWarResourceSet:getArchiveEntries 在扫描战争文件时大约需要 5 秒。在扫描期间,没有任何请求得到处理。

我已将Spring boot version1.4.2.RELEASE 更新为1.5.12.RELEASE,解决了这个问题。确实,似乎问题出在嵌入式 Tomcat 上,该问题已在后续版本中得到修复。

【讨论】:

我使用的是 Spring Boot 2 (2.0.5)。此版本在 8.5.34 版中包含一个嵌入式 tomcat。而且这里的问题仍然存在。 @tomas 你介意分享一下你是否能够解决这个问题以及如何解决? 我也面临这个问题。使用 jvisual vm 和采样器在本地检查时间在 org.apache.catalina.webresources.JarWarResourceSet:getArchiveEntries 中消耗。搬到 jar 对我来说不是一个选择。任何其他想法。【参考方案3】:

使用可执行 JAR 文件(而不是 WAR)与嵌入式 Tomcat 一起运行为我解决了这个问题。这是recommended way,用于加速从可执行档案中加载资源。

    你可以有one Spring Boot project and deploy to both JAR or WAR, 我不得不从 WAR 切换到 JAR 以获得更好的性能,并且在 freemarker 中使用 JSP 标记库时遇到了问题。这是a way to solve that issue when using JSP taglibs。

【讨论】:

感谢您的回复。我很确定我当时尝试了 jar 和战争部署。两者都有这个性能问题。 我从 WAR 转到 JAR 并且不再遇到性能问题,如下所述:github.com/spring-projects/spring-boot/issues/16471【参考方案4】:

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

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

根据您的数据,我的最佳猜测是前两个步骤很慢,在数据库池预热之前,您会遇到一些非常慢的查询。 潜在的改进是:

配置一个预热期,在此期间池执行自初始化,而 Tomcat 尚未响应 在 DB 端检查在创建连接时所做的工作以及在应用端检查是否/哪些配置必须设置连接

【讨论】:

这也是我的第一想法。但是连接池不是问题。通过性能分析,我可以发现它是对 WAR 文件的扫描。 如果您确定这一点,那么我建议您研究防病毒(在打开战争和检查字节码时众所周知的猪)和容器化(有时对 I/O 有一些奇怪的副作用)。第一个(防病毒)可以通过禁用它来轻松检查。第二个(容器化)通常不太重要,因为运行容器化应用程序与非容器化应用程序存在许多差异,您应该在那里寻找特定的 *** 问题。

以上是关于性能 - Spring Boot - 服务器响应时间的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 使用来自服务器响应的数据编辑 json 异常

Flux 响应而不是 WebSocket (Spring boot)

在 Spring Boot 中使用自定义代码进行响应 [重复]

原始性能表格 - Spring Boot 2 Webflux vs. Spring Boot 1

如何为 ZuulException 自定义 Spring Boot 控制器 API 响应

Spring boot @ExceptionHandler 将响应返回为 html