环保

OS:macOS Mojave版本10.14.5(centOS也有同样的问题)

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

我的母语不是英语,请原谅我的英语不好。

我是SpringBoot的新手,我认为这对构建我的项目很有帮助。现在我已经完成它的工作了,但是一个奇怪的现象困扰着我。我的项目花费大约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)

最佳答案

事实证明,这里发生了一些事情,我在github上做了个要旨和一个仓库,以共享解决方案以加快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)

内省者需要一个bean,首先要首先..搜索一个对应的BeanInfo,最后是Descriptor。因此,这些是Class.forName查找。

至少在Java 1.8(尚未检查最新版本)中,此查找以相当复杂的方式完成,并在不同的类加载器上尝试了多次。

同样,到目前为止,还不错,只是搜索两个类。

然后,它到达Tomcat嵌入式类加载器。它有两个问题。


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


而且,几乎在2020年,Java有了发展,并且BeanInfos和Descriptor在构建UI的IDE中非常有用,而不是在处理同时具有setter和getter的实体时。

Spring尝试通过标记来忽略BeanInfos来减轻对Introspector的调用(并为此公开一个属性),但是至少在1.8版本中,它显然什么都不做,当然不会跳过对Descriptor的搜索,这在JDK中有所报道。票证https://bugs.openjdk.java.net/browse/JDK-8172961在1.8上似乎是一个已知问题。

因此,它加载了实体类,调用了Introspector,它将搜索BeanInfo,不会发现它仍在扫描所有的jar,然后搜索Descriptor,再次扫描所有的jar,这会使它变慢。

以我为例,HTTP请求通常耗时几毫秒,在首次调用时变成15/20秒,这终止了我们的E2E测试。

第一种解决方案是至少要关闭要基于https://gist.github.com/SimoneGianni/74b3b7e5f5986a72a95e705cc6abe6dc的嵌入式Tomcat缓存退出器。它是一个拦截Spring Tomcat配置并打开正确标志的bean。它是针对Spring 2.0进行的更新,网上有一个针对1.X的版本。

它可以防止每15分钟再次发生该事件,并且在某种程度上还可以加快启动/首次请求的速度,这可能会导致Tomcat更加积极地缓存。

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

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

由于为每个实体编写空的BeanInfos都很繁琐并且会污染您的代码,因此我编写了一个注释处理器来实现此目的,并且正在我的项目中使用它。

暂时,您必须克隆它,mvn build安装它,并将其用作依赖项。我最终将在github上设置一个Maven回购,这也是出于我的目的,仅将其作为本地依赖项并不健康。

请注意,在1.9中,他们引入了一些特定的批注以从批注生成BeanInfo,但这又是面向Swing的,因此以后的版本中似乎也没有“避免在实体中搜索内容”。

希望这会有所帮助,至少对我有所帮助,现在的初始启动速度稍快,并且第一个请求只需花费几秒钟而不是15/20的时间,以后在执行E2E测试时不会再有延迟。

关于java - 为什么我的带有嵌入式tomcat的Spring Boot在处理第一次请求时太慢?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59242577/

10-09 19:12