加快Spring Boot的启动时间


114

我有一个Spring Boot应用程序。我添加了很多依赖项(不幸的是,看起来我需要所有这些依赖项),并且启动时间增加了很多。仅需SpringApplication.run(source, args)花费10秒钟。

尽管这与“使用”相比可能不算什么,但我不愿意花那么多钱,主要是因为它破坏了开发流程。此时应用程序本身很小,因此我认为大多数时间与添加的依赖项有关,而不是与应用程序类本身有关。

我认为问题是类路径扫描,但是我不确定如何:

  • 确认这是问题所在(即如何“调试” Spring Boot)
  • 如果确实是原因,我该如何加以限制,以使其更快?例如,如果我知道某个依赖项或程序包不包含Spring应该扫描的任何内容,是否有办法限制它?

我认为增强Spring在启动期间具有并行bean初始化会加快速度,但是该增强请求自2011年以来一直没有任何进展。我在Spring Boot本身中看到了其他一些工作,例如调查Tomcat JarScanning的速度改进,但这是特定于Tomcat的,已被放弃。

本文:

尽管针对集成测试,建议使用lazy-init=true,但是我不知道如何使用Java配置将此应用于Spring Boot中的所有bean-这里有没有指针?

任何(其他)建议都将受到欢迎。


发布您的代码。通常只扫描定义应用程序运行程序的包。如果您还定义了其他软件包,@ComponentScan则也会对其进行扫描。另一件事是要确保您没有启用调试或跟踪日志记录,因为通常日志记录的速度非常慢。
M. Deinum 2014年

如果您使用Hibernate,它在应用程序启动时也会占用大量时间。
Knut Forkalsrud

如果添加大量bean和依赖项,Spring的按类型进行的自动绑定(加上工厂bean)可能会变慢。
Knut Forkalsrud'3

或者您可以使用缓存spring.io/guides/gs/caching
Cassian 2015年

2
感谢所有人的评论-很遗憾,我将无法发布代码(很多内部jar),但是我仍在寻找一种调试方法。是的,我可能正在使用A或B,或者正在使用X或Y,这会减慢速度。我该如何确定?如果我添加了一个依赖项X,它具有15个可传递依赖项,我怎么知道这16个中有哪些使它变慢了?如果我能找到答案,我以后有什么办法可以阻止Spring检查它们?这样的指针会很有用!
持续降雨

Answers:


61

Spring Boot做了很多可能不需要的自动配置。因此,您可能只想缩小应用程序所需的自动配置。要查看包含的自动配置的完整列表,只需org.springframework.boot.autoconfigure在DEBUG模式下运行的日志记录(logging.level.org.springframework.boot.autoconfigure=DEBUG在中application.properties)。另一个选择是运行带有--debug选项的spring boot应用程序:java -jar myproject-0.0.1-SNAPSHOT.jar --debug

在输出中会有这样的内容:

=========================
AUTO-CONFIGURATION REPORT
=========================

检查此列表,仅包括所需的自动配置:

@Configuration
@Import({
        DispatcherServletAutoConfiguration.class,
        EmbeddedServletContainerAutoConfiguration.class,
        ErrorMvcAutoConfiguration.class,
        HttpEncodingAutoConfiguration.class,
        HttpMessageConvertersAutoConfiguration.class,
        JacksonAutoConfiguration.class,
        ServerPropertiesAutoConfiguration.class,
        PropertyPlaceholderAutoConfiguration.class,
        ThymeleafAutoConfiguration.class,
        WebMvcAutoConfiguration.class,
        WebSocketAutoConfiguration.class,
})
public class SampleWebUiApplication {

代码是从此博客文章中复制


1
你测量这个了吗?快很多吗?在我看来,这是一个例外情况,对于确保Spring测试上下文缓存可以正常工作更重要
idmitriev

@idmitriev我刚刚在我的应用程序上进行了测量,我的应用程序在53秒启动,而没有排除自动配置类的时间是73秒。我确实排除了比上面所列数量更多的课程。
apkisbossin

很高兴导入所有配置。如何处理BatchConfigurerConfiguration.JpaBatchConfiguration应该将依赖项添加到projet中?如何处理诸如ConfigurationPropertiesRebinderAutoConfiguration#configurationPropertiesBeans之类的引用方法?
user1767316

如何处理私有配置类?
user1767316

44

到目前为止,获得投票最多的答案并没有错,但是它并没有深入到我喜欢看到的深度,也没有提供科学依据。Spring Boot团队进行了一个减少Boot 2.0启动时间的练习,工单11226包含许多有用的信息。还有一张7939的票证可用于向条件评估中添加时间信息,但似乎没有特定的ETA。

Dave Syer已完成了用于调试启动的最有用,最系统的方法。https://github.com/dsyer/spring-boot-startup-bench

我也有一个类似的用例,因此我采用了Jave的Dave微基准测试方法并进行了运行。结果是启动基准项目。我对其进行了设计,以便可以使用Gradle任务bootJarbootRepackage在Boot 1.5中先前称为)生成的可执行jar来测量任何Spring Boot应用程序的启动时间。随时使用它并提供反馈。

我的发现如下:

  1. CPU很重要。很多。
  2. 使用-Xverify:none启动JVM 会有很大帮助。
  3. 排除不必要的自动配置会有所帮助。
  4. Dave建议JVM参数-XX:TieredStopAtLevel = 1,但是我的测试并未显示出明显的改进。另外,-XX:TieredStopAtLevel=1可能会减慢您的第一个请求。
  5. 报告称主机名解析速度很慢,但是我发现它对我测试的应用程序没有问题。

1
@ user991710不确定如何损坏,但现已修复。感谢您的报告。
Abhijit Sarkar

2
为了补充这一点,您能否添加一个示例,说明有人如何将您的基准与自定义应用程序一起使用?是否必须将其添加为类似于的项目minimal,或者可以简单地提供jar?我尝试做前者,但没有走得太远。
user991710

1
不要-Xverify:none在生产环境中运行,因为它会破坏代码验证,您可能会遇到麻烦。-XX:TieredStopAtLevel=1如果您在短时间内(几秒钟)运行应用程序,则可以,否则会降低生产率,因为它将为JVM提供长时间运行的优化。
loicmathieu

3
oracle doc列出了Use of -Xverify:none is unsupported.什么意思?
sakura

1
许多池(肯定是Oracle UCP,但在我的测试中还包括Hikari和Tomcat)对池中的数据进行加密。我实际上不知道他们是否在加密连接信息或包装流。无论如何,加密使用随机数生成,因此具有高可用性,高吞吐量的熵源会在性能上产生显着差异。
丹尼尔(Daniel)

18

Spring Boot 2.2.M1 添加了支持Spring Boot中的惰性初始化的功能。

缺省情况下,刷新应用程序上下文时,将创建上下文中的每个bean并注入其依赖项。相反,当将bean定义配置为延迟初始化时,将不会创建它,并且直到需要时才注入依赖项。

启用惰性初始化设置spring.main.lazy-initializationtrue

何时启用延迟初始化延迟初始化可以显着改善启动时间,但也存在一些明显的缺点,因此务必谨慎启用它

有关更多详细信息,请检查Doc


3
如果启用延迟初始化,则首次加载速度非常快,但是当客户端第一次访问时,可能会注意到一些延迟。我真的建议将此用于开发而不是用于生产。
Isuru Dewasurendra

正如@IsuruDewasurendra所建议的那样,不建议这样做,因为它可以显着增加应用程序开始提供负载时的延迟。
Narendra Jaggi

它只是将罐子踢倒了。
Abhijit Sarkar

10

如该问题/答案中所述,我认为最好的方法是,不添加仅您认为需要的依赖项,而排除您不需要的依赖项。

请参阅:最小化Spring Boot启动时间

综上所述:

从命令行启动应用程序时,您可以看到隐藏的内容并启用调试日志记录,就像指定--debug一样简单。您也可以在application.properties中指定debug = true。

同样,您可以在application.properties中设置日志记录级别,方法很简单:

logging.level.org.springframework.web:调试logging.level.org.hibernate:错误

如果检测到不需要的自动配置模块,则可以将其禁用。可以在这里找到有关此文档:http : //docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-disabling-specific-auto-configuration

一个示例如下所示:

@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class MyConfiguration {
}

4

好吧,这里描述了可能的动作的完整列表:https : //spring.io/blog/2018/12/12/how-fast-is-spring

我将在Spring方面提出一些最重要的注意事项(进行一些调整):

  • Spring Boot Web入门中的类路径排除:
    • 休眠验证器
    • 杰克逊(但是Spring Boot执行器取决于它)。如果您需要JSON呈现,请使用Gson(开箱即用仅适用于MVC)。
    • 重新登录:改用slf4j-jdk14
  • 使用spring-context-indexer。它不会增加太多,但是一点点帮助。
  • 如果负担不起,请不要使用执行器。
  • 使用Spring Boot 2.1和Spring 5.1。可用时切换到2.2和5.2。
  • 使用spring.config.location(命令行参数或系统属性等)修复Spring Boot配置文件的位置。在IDE中进行测试的示例:spring.config.location=file://./src/main/resources/application.properties
  • 如果不需要,请关闭JMX spring.jmx.enabled=false(这是Spring Boot 2.2中的默认设置)
  • 默认情况下,使bean定义变得懒惰。spring.main.lazy-initialization=trueSpring Boot 2.2中有一个新标志(LazyInitBeanFactoryPostProcessor用于较早的Spring)。
  • 解开胖子jar并使用显式类路径运行。
  • 使用运行JVM -noverify。还请考虑-XX:TieredStopAtLevel=1(这将在以后降低JIT速度,但会节省启动时间)。

提到的LazyInitBeanFactoryPostProcessor(如果不能应用spring.main.lazy-initialization=trueSpring 2.2可用的标志,则可以将其用于Spring 1.5 ):

public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
      for (String beanName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
        definition.setLazyInit(true);
      }
  }
}

您还可以使用(或编写自己的-很简单)的东西来分析bean的初始化时间:https : //github.com/lwaddicor/spring-startup-analysis

希望能帮助到你!



-1

如果您要优化手动测试的开发周转时间,强烈建议使用devtools

每当classpath上的文件更改时,使用spring-boot-devtools的应用程序将自动重启。

只需重新编译-服务器将自行重启(对于Groovy,您只需要更新源文件)。如果您使用的是IDE(例如“ vscode”),它可能会自动编译Java文件,因此仅保存Java文件就可以间接启动服务器重启-在这方面,Java与Groovy一样无缝。

这种方法的优点在于,增量重新启动会短路某些从头开始的启动步骤-因此,您的服务将被备份并更快地运行!


不幸的是,这对部署或自动单元测试的启动时间没有帮助。


-1

警告:如果您不使用Hibernate DDL来自动生成数据库模式,也不使用L2缓存,则此答案不适用于您。向前滚动。

我的发现是Hibernate为应用程序启动增加了大量时间。禁用L2缓存和数据库初始化可加快Spring Boot应用程序的启动速度。将缓存保留为生产状态,并在开发环境中将其禁用。

application.yml:

spring:
  jpa:
    generate-ddl: false
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        cache:
          use_second_level_cache: false
          use_query_cache: false

检测结果:

  1. L2缓存已打开,并且 ddl-auto: update

    INFO 5024 --- [restartedMain] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 23331 ms
    INFO 5024 --- [restartedMain] b.n.spring.Application : Started Application in 54.251 seconds (JVM running for 63.766)
  2. L2缓存关闭并且 ddl-auto: none

    INFO 10288 --- [restartedMain] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 9863 ms
    INFO 10288 --- [restartedMain] b.n.spring.Application : Started Application in 32.058 seconds (JVM running for 37.625)

现在我想知道我将如何利用所有这些空闲时间


hibernate.hbm2ddl.auto = update与二级缓存无关。ddl .. = update指定扫描当前数据库架构,并计算必要的sql,以更新架构以反映您的实体。“无”不会执行此验证(也不会尝试更新架构)。最佳实践是使用诸如liquibase之类的工具,在该工具中您将处理模式更改,并且还可以跟踪它们。
Radu Toader

@RaduToader这个问题和我的答案是关于加快Spring Boot启动时间的。它们与Hibernate DDL与Liquibase的讨论无关。这些工具各有利弊。我的观点是,我们可以禁用数据库模式更新并仅在必要时启用。即使自上次运行以来模型未发生更改,Hibernate也会花费大量时间启动(用于比较数据库模式与自动生成的模式)。对于L2缓存,这一点也适用。
naXa

是的,我知道这一点,但是我的意思是,不解释它的真正作用是有点危险的。您可能很容易以db为空而告终。
Radu Toader

@RaduToader我的答案中有一个指向有关数据库初始化的文档页面的链接。你读过它吗?它包含详尽的指南,列出了所有最受欢迎的工具(Hibernate和Liquibase以及JPA和Flyway)。同样在今天,我在回答的顶部添加了明确的警告。您是否认为我还需要其他更改来解释后果?
naXa

完善。谢谢
Radu Toader

-3

我觉得很奇怪,以前没有人建议过这些优化。以下是在开发时优化项目构建和启动的一些一般性提示:

  • 从防病毒扫描程序中排除开发目录:
    • 项目目录
    • 构建输出目录(如果它在项目目录之外)
    • IDE索引目录(例如〜/ .IntelliJIdea2018.3)
    • 部署目录(Tomcat中的webapps)
  • 升级硬件。使用更快的CPU和RAM,更好的Internet连接(用于下载依赖项)和数据库连接,切换到SSD。显卡无关紧要。

警告

  1. 第一种选择是以降低安全性为代价的。
  2. 第二种选择要花钱(很明显)。

问题是关于缩短启动时间,而不是编译时间。
ArtOfWarfare

@ArtOfWarfare再次阅读了问题。该问题指出的问题是“我不愿意花那么多时间,主要是因为它破坏了开发流程”。我觉得这是一个主要问题,并在我的回答中予以解决。
naXa

-9

对我来说,听起来您使用的配置设置错误。首先检查myContainer和可能的冲突。要确定谁在使用最多的资源,您必须一次检查每个依赖项的内存映射(请参阅数据量!),这也要花费大量时间...(和SUDO特权)。顺便说一句:您通常是否针对依赖项测试代码?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.