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在部署到Tomcat期间无法加载外部jar
Spring Boot如何使用内嵌式的Tomcat和Jetty?