6

环境。

操作系统:macOS Mojave 版本 10.14.5(centOS 也有同样的问题)

Springboot:2.1.6.RELEASE(嵌入式tomcat 9.0.21),war

我是 Spring Boot 的新手,我认为这对构建我的项目很有帮助。现在我已经完成了我的工作,但一个奇怪的现象困扰着我。我的项目响应第一个请求大约需要 5 分钟,它需要 5 分钟而不是 5 秒,第一次之后的请求看起来很正常。它非常慢,所以我需要你的帮助。

在我的帮助下jstack,大部分时间都花在了做下面的事情上,同样做解包战争。

"http-nio-15281-exec-5" #105 daemon prio=5 os_prio=31 tid=0x00007f988eaff800 nid=0x13b03 runnable [0x0000700013218000]
   java.lang.Thread.State: RUNNABLE
    at java.util.zip.Inflater.inflateBytes(Native Method)
    at java.util.zip.Inflater.inflate(Inflater.java:259)
    - locked <0x00000007bac79ab0> (a java.util.zip.ZStreamRef)
    at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:152)
    at java.util.zip.ZipInputStream.read(ZipInputStream.java:194)
    at java.util.jar.JarInputStream.read(JarInputStream.java:207)
    at java.util.zip.ZipInputStream.closeEntry(ZipInputStream.java:140)
    at java.util.zip.ZipInputStream.getNextEntry(ZipInputStream.java:118)
    at java.util.jar.JarInputStream.getNextEntry(JarInputStream.java:142)
    at java.util.jar.JarInputStream.getNextJarEntry(JarInputStream.java:179)
    at org.apache.catalina.webresources.JarWarResourceSet.getArchiveEntries(JarWarResourceSet.java:117)
    - locked <0x00000007810e7770> (a java.lang.Object)
    at org.apache.catalina.webresources.AbstractArchiveResourceSet.getResource(AbstractArchiveResourceSet.java:253)
    at org.apache.catalina.webresources.StandardRoot.getResourceInternal(StandardRoot.java:282)
    at org.apache.catalina.webresources.Cache.getResource(Cache.java:62)
    at org.apache.catalina.webresources.StandardRoot.getResource(StandardRoot.java:217)
    at org.apache.catalina.webresources.StandardRoot.getClassLoaderResource(StandardRoot.java:226)
    at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:2303)
    at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:865)
    at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader.findClassIgnoringNotFound(TomcatEmbeddedWebappClassLoader.java:119)
    at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader.doLoadClass(TomcatEmbeddedWebappClassLoader.java:84)
    at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader.loadClass(TomcatEmbeddedWebappClassLoader.java:66)
    - locked <0x00000007af22a990> (a java.lang.Object)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1188)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at com.sun.beans.finder.ClassFinder.findClass(ClassFinder.java:67)
    at com.sun.beans.finder.ClassFinder.findClass(ClassFinder.java:110)
    at java.beans.Introspector.findCustomizerClass(Introspector.java:1301)
    at java.beans.Introspector.getTargetBeanDescriptor(Introspector.java:1295)
    at java.beans.Introspector.getBeanInfo(Introspector.java:425)
    at java.beans.Introspector.getBeanInfo(Introspector.java:262)
    at java.beans.Introspector.getBeanInfo(Introspector.java:204)
    at org.springframework.beans.CachedIntrospectionResults.getBeanInfo(CachedIntrospectionResults.java:248)
    at org.springframework.beans.CachedIntrospectionResults.<init>(CachedIntrospectionResults.java:273)
    at org.springframework.beans.CachedIntrospectionResults.forClass(CachedIntrospectionResults.java:177)
    at org.springframework.beans.BeanWrapperImpl.getCachedIntrospectionResults(BeanWrapperImpl.java:174)
    at org.springframework.beans.BeanWrapperImpl.getLocalPropertyHandler(BeanWrapperImpl.java:230)
    at org.springframework.beans.BeanWrapperImpl.getLocalPropertyHandler(BeanWrapperImpl.java:63)
    at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:620)
    at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:612)
    at org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper.getPropertyValue(DirectFieldAccessFallbackBeanWrapper.java:52)
    at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.getId(JpaMetamodelEntityInformation.java:154)
    at org.springframework.data.repository.core.support.AbstractEntityInformation.isNew(AbstractEntityInformation.java:42)
    at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.isNew(JpaMetamodelEntityInformation.java:233)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:506)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:521)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359)
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor$$Lambda$641/1539038539.get(Unknown Source)
    at org.springframework.data.repository.util.QueryExecutionConverters$$Lambda$640/28145535.apply(Unknown Source)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.transaction.interceptor.TransactionInterceptor$$Lambda$636/1377160602.proceedWithInvocation(Unknown Source)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:295)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:138)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy125.saveAndFlush(Unknown Source)
4

2 回答 2

10

我们面临着完全相同的问题,我花了好几天的时间来解决这个问题,但设法用这个配置块解决了它:

@Configuration
public class EmbeddedTomcatConfiguration {

  @Bean
  TomcatServletWebServerFactory tomcatFactory() {
    return new TomcatServletWebServerFactory() {

      @Override
      protected void postProcessContext(Context context) {
        context.setResources(new ExtractingRoot());
      }
    };
  }

  @Bean
  public WebServerFactoryCustomizer<TomcatServletWebServerFactory> servletContainerCustomizer() {
    return new WebServerFactoryCustomizer<TomcatServletWebServerFactory>() {

      @Override
      public void customize(TomcatServletWebServerFactory container) {
        container.addContextCustomizers(
            new TomcatContextCustomizer() {
              @Override
              public void customize(Context cntxt) {
                cntxt.setReloadable(false);
              }
            });
      }
    };
  }
}

这基本上做了两件事......

  1. 它要求 Tomcat 不要丢弃加载的类并进行刷新。
  2. 它告诉 Tomcat 在启动时提取包(这需要更多磁盘空间),而不是扫描未提取的包。
于 2020-07-06T15:25:50.153 回答
4

原来这里发生了一些事情,我在 github 上做了一个要点和一个 repo 来分享加快 Spring(特别是 JPA 存储库)启动时间的解决方案。

TLDR:

解决方案 1:添加配置以关闭嵌入式 Tomcat 类重新加载https://gist.github.com/SimoneGianni/74b3b7e5f5986a72a95e705cc6abe6dc。这将防止问题每 15 分钟发生一次,并加快启动速度。

解决方案 2:为每个实体实现 BeanInfo,或使用此https://github.com/SimoneGianni/auto-bean-info自动生成它们。

完整说明:

当 Spring 启动时,和/或第一次使用您的 JPA 存储库,和/或第一次使用特定存储库时(取决于缓存、加载组件的顺序等),Spring 和 Hibernate 将检查您的实体。

为此,他们将使用 java Introspector。这没什么不好,它是任何必须处理 java bean 的人使用的标准库类。

回到 Swing 炙手可热的时代,Java bean 是在 Angular 之前 20 年组成模块化 UI 的方式,为此,一个 bean 可以伴随着许多其他类,如 BeanInfo 和 Descriptor,以指定它应该如何由 IDE 使用和配置。

这些附带的类被用来更好地描述属性、事件等。当时还没有注释。

现在,假设您有一个名为SomethingCool.java 的bean,您可以提供SomethingCoolBeanInfo.java 和/或SomethingCoolDescriptor.java。模式是<bean class name>+(BeanInfo|Descriptor)

Introspector 获取一个 bean,首先.. 搜索相应的 BeanInfo 并最终搜索 Descriptor。因此,这些是 Class.forName 查找。

至少在 java 1.8(还没有检查过更新的版本)中,这种查找是以相当复杂的方式完成的,在不同的类加载器上尝试了多次。

同样,到此为止,没什么不好的,只是搜索两个类。

然后,它到达 Tomcat Embedded Classloader。它有两个问题。

  1. 它很慢,因为它必须在 jars 中搜索 jars,这本身很慢。
  2. 默认情况下,它每 15 分钟丢弃一次缓存,因此问题不仅在启动时出现,而且稍后会再次出现,以防再次执行搜索。

而且,几乎在 2020 年,Java 已经发展,BeanInfos 和 Descriptor 主要在构建 UI 的 IDE 中有用,而不是在处理也恰好具有 setter 和 getter 的实体时。

Spring 尝试使用忽略 BeanInfos 的标志来缓解这种调用 Introspector(并且还为此公开了一个属性),但至少在 1.8 中它显然什么都不做,肯定不会跳过对描述符的搜索,正如 JDK 中所报告的那样票https://bugs.openjdk.java.net/browse/JDK-8172961看起来这是 1.8 上的一个已知问题。

所以,它加载实体类,调用 Introspector,它会搜索 BeanInfo,找不到它仍然扫描所有 jar,然后搜索 Descriptor,再次扫描所有 jar,这使得它变慢了。

在我的例子中,一个通常需要毫秒的 HTTP 请求在第一次被调用时变成了 15/20 秒,这扼杀了我们的 E2E 测试。

第一个解决方案是至少关闭嵌入式 Tomcat 缓存驱逐器,使用要点https://gist.github.com/SimoneGianni/74b3b7e5f5986a72a95e705cc6abe6dc完成。它是一个拦截 Spring Tomcat 配置并转换正确标志的 bean。它已针对 Spring 2.0 进行了更新,网上有 1.X 的版本。

它将防止事情每 15 分钟再次发生一次,并且不知何故它还在某种程度上加快了启动/首次请求的速度,这可能是因为 Tomcat 正在更积极地缓存。

但是,它仍然会搜索 BeanInfo 和 Descriptor,并且只有在扫描完所有 jar 后才会放弃。

第二种解决方案是为每个@Entity(和@MappedSuperclass,最好是任何接口或其他超类)提供一个BeanInfo 类。通过放置这个 BeanInfo,它会找到它(希望很快,在同一个类加载器中)并避免扫描所有 jar,并且在 BeanInfo 中,您还可以指定没有描述符并完全跳过其他搜索。

由于为每个实体编写空 BeanInfos 很乏味并且会污染您的代码,因此我编写了一个注释处理器来完成此任务,并且在我的项目中使用它。

暂时你必须克隆它, mvn build install 它,并将它用作依赖项。我最终将在 github 上设置一个 maven 存储库,因为出于我的目的,仅将其作为本地依赖项是不健康的。

请注意,在 1.9 中,他们引入了一些特定的注解来从注解生成 BeanInfos,但这又是面向 Swing 的,所以看起来在以后的版本中也没有“避免在这些东西中搜索实体”。

希望这会有所帮助,至少对我有帮助,现在初始启动稍微快一些,第一个请求需要几秒钟而不是 15/20,以后在执行 E2E 测试时不会有进一步的延迟。

于 2019-12-20T00:36:27.200 回答