Springboot嵌入式Tomcat类加载器缓慢

Posted

技术标签:

【中文标题】Springboot嵌入式Tomcat类加载器缓慢【英文标题】:Springboot embedded Tomcat classloader slowness 【发布时间】:2017-01-07 03:10:19 【问题描述】:

我已经构建了一个使用 SpringBoot v1.3.6.RELEASE 雄猫 8.0.36 Java 1.8u101 在 CentOS 7.2 上

Web 应用程序也是调用另一个 Web 应用程序的 SOAP 客户端。(JAX-WS RI 2.2.9)如果应用程序保持空闲 15 秒,则第一个 Web 服务调用会停止近 2 秒。似乎停顿发生在 o.a.c.loader.WebappClassLoaderBase 中。

空闲 15 秒后

16:02:36.165 : 委托给父类加载器 org.springframework.boot.loader.LaunchedURLClassLoader@45283ce2

16:02:36.170 : 搜索本地存储库

16:02:36.170:findResource(META-INF/services/javax.xml.soap.MetaFactory)

16:02:38.533 : --> 找不到资源,返回 null

16:02:38.533 : --> 找不到资源,返回 null

下一个请求没有空闲时间

16:07:09.981 : 委托给父类加载器 org.springframework.boot.loader.LaunchedURLClassLoader@45283ce2

16:07:09.984 :搜索本地存储库

16:07:09.985:findResource(META-INF/services/javax.xml.soap.MetaFactory)

16:07:09.986 : --> 找不到资源,返回 null

16:07:09.986 : --> 找不到资源,返回 null

16:07:09.988 : findResources(META-INF/services

以上所有消息均由 o.a.c.loader.WebappClassLoaderBase 产生,它们显然是由来自 JAX-WS RI 的 ClientSOAPHandlerTube.processRequest 引起的。

您会注意到第一次调用需要 2 秒以上,但后续调用只需要几毫秒。 我想知道是否有人经历过这种行为?

可能的解决方案: 是否可以将springboot中tomcat使用的类加载器改成使用ParallelWebappClassLoader

或者这可能是类加载器上可重新加载标志的产物,但我不知道如何在 springboot 中更改该标志。

当使用 Jetty 作为容器运行时,不会发生这种情况。

最终解决方案:(感谢 Gergely Bacso)

    @Bean
public EmbeddedServletContainerCustomizer servletContainerCustomizer() 
    return new EmbeddedServletContainerCustomizer() 

        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) 
             if (container instanceof TomcatEmbeddedServletContainerFactory) 
                customizeTomcat((TomcatEmbeddedServletContainerFactory) container);
            
        
        private void customizeTomcat(TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory) 
            tomcatEmbeddedServletContainerFactory.addContextCustomizers(new TomcatContextCustomizer() 
                @Override
                public void customize(Context cntxt) 
                    cntxt.setReloadable(false);
                
            );
        
    ;

【问题讨论】:

看起来有一个缓存因 15 秒的空闲时间而失效,因此 findResource 调用必须再次搜索整个类路径。我不知道该缓存可能在哪里,因为不清楚是什么生成了您在上面共享的输出。重现该问题的完整示例将在这里有所帮助。我还要看看正在寻找META-INF/services/javax.xml.soap.MetaFactory 资源的任何东西。为每个请求执行查找似乎没有必要,因为结果非常不太可能发生变化。 @AndyWilkinson 是的,这正是我的想法。我查看了 WebappClassLoaderBase 并没有看到任何缓存。我也同意你的观点,每个请求的查找似乎都是浪费的。也许我会尝试 CXF 而不是 Metro,看看是否会发生同样的事情。 在迁移过程中分析了一些严重的类加载器性能问题以添加 Spring Boot 和嵌入式 Tomcat 后,我​​遇到了这个问题。 reloadable 现在似乎默认设置为 false,但是,对于遇到此问题的任何其他人,存在类似的类加载器问题,仅在将应用程序打包为 WAR 时适用:github.com/spring-projects/spring-boot/issues/16471 .那张票上的suggested solution 为我修好了。 【参考方案1】:

实际上,您的发现非常好,您已经回答了 90% 的问题。这两个事实:

    “似乎停顿发生在 o.a.c.loader.WebappClassLoaderBase” “当使用 Jetty 作为容器运行时,不会发生这种情况。”

表明这将是一个与 Tomcat 相关的问题,因为:

    o.a.c. 代表org.apache.catalina 您的代码在另一个容器上运行良好。 (码头)

您还观察到,问题是在 15 秒的空闲时间后发生的。这完美对应了Tomcat默认的checkInterval设置,即:

检查修改的类之间的秒数和 资源,如果 reloadable 已设置为 true。默认值为 15 秒。

简而言之:当前您的reloadable 标志为ON,并且Tomcat 尝试重新加载您的类,这在开发过程中很方便,但在任何其他情况下都是不可接受的。关闭它的方法不是通过 Spring-boot。

解决方案: 您需要找到您的 context.xml / server.xml ,您将在其中找到您的 Context 定义如下:

<Context ... reloadable="true">

去掉reloadable 标志,问题就解决了。该文件本身可以位于 $CATALINE_HOME/conf 的 $CATALINA_BASE/conf 中,但实际上,如果您使用某些 IDE 为您管理 Tomcat,这些位置可能会有点难以找到。

如果是带有 Spring-boot 的嵌入式 Tomcat:

manipulate Tomcat settings 可以使用的类是:EmbeddedServletContainerCustomizer

通过这个,您可以添加一个TomcatContextCustomizer (addContextCustomizers),以便您可以在上下文本身上调用setReloadable

我看不出 Spring-boot 需要这个标志的任何理由。

【讨论】:

该标志必须在springboot的嵌入式tomcat中默认设置为true。在springboot应用程序中,tomcat嵌入在应用程序jar文件中,据我所知它不使用任何上下文。 xml 文件。我将继续深入研究如何在 springboot 应用程序中更改该标志 @cyberoblivion,我看到了问题所在。我扩展了答案。 Basco,我根据您的反馈使用我提出的解决方案更新了问题!谢谢。

以上是关于Springboot嵌入式Tomcat类加载器缓慢的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot - 缓慢的请求/响应

Spring Boot如何启动嵌入式Tomcat?

Spring Boot在部署到Tomcat期间无法加载外部jar

Spring Boot如何使用内嵌式的Tomcat和Jetty?

28 | 新特性:Tomcat和Jetty如何处理Spring Boot应用?

tomcat类加载机制了解一下