【问题标题】:Springboot embedded Tomcat classloader slownessSpringboot嵌入式Tomcat类加载器缓慢
【发布时间】: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 为我修好了。

标签: java spring tomcat spring-boot jax-ws


【解决方案1】:

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

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

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

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

您还观察到,问题是在 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,我根据您的反馈使用我提出的解决方案更新了问题!谢谢。
猜你喜欢
  • 2023-03-25
  • 2016-11-15
  • 2016-09-01
  • 2016-12-11
  • 2012-02-21
  • 2018-02-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多