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%的问题。这两个事实:
- "it appears that the stall happens in o.a.c.loader.WebappClassLoaderBase"
- "when run using Jetty as the container this does not occur."
显示这将是一个 Tomcat 相关的问题,因为:
o.a.c.
代表org.apache.catalina
- 您的代码在另一个容器上运行良好。 (码头)
您还观察到,问题是在空闲时间 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 需要此标志为真。
我构建了一个使用 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%的问题。这两个事实:
- "it appears that the stall happens in o.a.c.loader.WebappClassLoaderBase"
- "when run using Jetty as the container this does not occur."
显示这将是一个 Tomcat 相关的问题,因为:
o.a.c.
代表org.apache.catalina
- 您的代码在另一个容器上运行良好。 (码头)
您还观察到,问题是在空闲时间 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 需要此标志为真。