Spring Boot 嵌入式 Tomcat 类加载器缓慢

Springboot embedded Tomcat classloader slowness

我构建了一个使用 SpringBoot v1.3.6.RELEASE Tomcat 8.0.36 Java1.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 : Delegating to parent classloader org.springframework.boot.loader.LaunchedURLClassLoader@45283ce2

16:02:36.170 : Searching local repositories

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

16:02:38.533 : --> Resource not found, returning null

16:02:38.533 : --> Resource not found, returning null

下次请求无空闲时间

16:07:09.981 : Delegating to parent classloader org.springframework.boot.loader.LaunchedURLClassLoader@45283ce2

16:07:09.984 : Searching local repositories

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

16:07:09.986 : --> Resource not found, returning null

16:07:09.986 : --> Resource not found, returning 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);
                }
            });
        }
    };
}

其实你的发现很好,你已经回答了90%的问题。这两个事实:

  1. "it appears that the stall happens in o.a.c.loader.WebappClassLoaderBase"
  2. "when run using Jetty as the container this does not occur."

显示这将是一个 Tomcat 相关的问题,因为:

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

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

The number of seconds between checks for modified classes and resources, if reloadable has been set to true. The default is 15 seconds.

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

解决方案:
你需要找到你的 context.xml / server.xml 你会发现你的 Context 定义如下:

<Context ... reloadable="true">

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

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

可用于 manipulate Tomcat settings 的 class 是:EmbeddedServletContainerCustomizer.

通过这个你可以添加一个 TomcatContextCustomizer (addContextCustomizers) 这样你就可以在上下文本身上调用 setReloadable

我看不出有任何理由 Spring-boot 需要此标志为真。