簡體   English   中英

為什么我的帶有嵌入式 tomcat 的 Springboot 在處理第一個請求時太慢?

[英]Why my Springboot with embbeded tomcat too slow when process first request?

環境

操作系統: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)

我們面臨着完全相同的問題,我花了好幾天的時間來解決它,但設法通過以下配置解決了它:

@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 在啟動時提取包(這需要更多的磁盤空間),而不是掃描未提取的包。

事實證明這里發生了一些事情,我在 github 上做了一個要點和一個 repo 來分享解決方案以加快 Spring(特別是 JPA 存儲庫)啟動時間。

域名注冊地址:

解決方案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 嵌入式類加載器。 它有兩個問題。

  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 安裝它,並將其用作依賴項。 我最終會在 github 上設置一個 maven repo 也是為了我的目的,僅將它作為本地依賴項是不健康的。

請注意,在 1.9 中,他們引入了一些特定的注釋來從注釋生成 BeanInfo,但這又是面向 Swing 的,因此在更高版本中似乎沒有“避免搜索實體的東西”可用。

希望這會有所幫助,至少對我來說它有所幫助,現在初始啟動速度稍快,並且第一個請求需要幾秒鍾而不是 15/20,在執行 E2E 測試時沒有進一步的延遲。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM