简体   繁体   English

加快 Spring 开机启动时间

[英]Speed up Spring Boot startup time

I have a Spring Boot application.我有一个 Spring 启动应用程序。 I've added a lot of dependencies (unfortunately, looks I need all of them) and the startup time went up quite a lot.我添加了很多依赖项(不幸的是,看起来我需要所有这些)并且启动时间增加了很多。 Just doing a SpringApplication.run(source, args) takes 10 seconds.仅仅做一个SpringApplication.run(source, args)需要 10 秒。

While that might not be much compared to what are "used" to, I'm unhappy that it takes that much, mostly because it breaks the development flow.虽然与“习惯”的相比,这可能并不多,但我很不高兴它需要那么多,主要是因为它打破了开发流程。 The application itself is rather small at this point, so I assume most of the time is related to the added dependencies, not the app classes themselves.此时应用程序本身相当小,所以我假设大部分时间与添加的依赖项有关,而不是应用程序类本身。

I assume the issue is classpath scanning, but I am not sure how to:我假设问题是类路径扫描,但我不确定如何:

  • Confirm that is the issue (ie how to "debug" Spring Boot)确认这是问题所在(即如何“调试”Spring 启动)
  • If it really is the cause, how can I limit it, so it gets faster?如果真的是原因,我该如何限制它,让它变得更快? For example, if I know that some dependency or package does not contain anything that Spring should be scanning, is there a way to limit that?例如,如果我知道某些依赖项或 package 不包含 Spring 应该扫描的任何内容,有没有办法限制它?

I assume that enhancing Spring to have parallel bean initialization during startup would speed up things, but that enhancement request has been open since 2011, without any progress.我假设增强 Spring 以在启动期间进行并行 bean 初始化会加快速度,但该增强请求自 2011 年以来一直开放,没有任何进展。 I see some other efforts in Spring Boot itself, such as Investigate Tomcat JarScanning speed improvements , but that is Tomcat specific and has been abandoned.我在 Spring Boot 本身看到了一些其他的努力,例如Investigate Tomcat JarScanning speed improvements ,但这是 Tomcat 特定的并且已被放弃。

This article:本文:

although aimed at integration tests, suggests using lazy-init=true , however I do not know how to apply this to all beans in Spring Boot using Java configuration - any pointers here?虽然针对集成测试,建议使用lazy-init=true ,但是我不知道如何将它应用于 Spring Boot using Java 配置中的所有 beans - 这里有任何指针吗?

Any (other) suggestions would be welcome.欢迎任何(其他)建议。

Spring Boot does a lot of auto-configuration that may not be needed. Spring Boot 做了很多可能不需要的自动配置。 So you may want to narrow down only auto-configuration that is needed for your app.因此,您可能只想缩小应用程序所需的自动配置范围。 To see full list of auto-configuration included, just run logging of org.springframework.boot.autoconfigure in DEBUG mode ( logging.level.org.springframework.boot.autoconfigure=DEBUG in application.properties ).要查看包含的自动配置的完整列表,只需在调试模式下运行org.springframework.boot.autoconfigure日志记录( logging.level.org.springframework.boot.autoconfigure=DEBUG in application.properties )。 Another option is to run spring boot application with --debug option: java -jar myproject-0.0.1-SNAPSHOT.jar --debug另一种选择是使用--debug选项运行 spring boot 应用程序: java -jar myproject-0.0.1-SNAPSHOT.jar --debug

There would be something like this in output:输出中会有这样的东西:

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

Inspect this list and include only autoconfigurations you need:检查此列表并仅包含您需要的自动配置:

@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 {

Code was copied from this blog post .代码是从这篇博文中复制

The most voted answer so far is not wrong, but it doesn't go into the depth I like to see and provides no scientific evidence.迄今为止投票最多的答案并没有错,但它没有深入到我喜欢看到的深度,也没有提供任何科学证据。 The Spring Boot team went through an exercise for reducing startup time for Boot 2.0, and ticket 11226 contains a lot of useful information. Spring Boot 团队进行了一次缩短 Boot 2.0 启动时间的练习,票证11226包含了很多有用的信息。 There is also a ticket 7939 open to adding timing information to condition evaluation, but it doesn't seem to have a specific ETA.还有一张票7939可以将时间信息添加到条件评估中,但它似乎没有特定的 ETA。

The most useful, and methodical approach for debugging Boot startup has been done by Dave Syer.调试引导启动的最有用、最有条理的方法是由 Dave Syer 完成的。 https://github.com/dsyer/spring-boot-startup-bench https://github.com/dsyer/spring-boot-startup-bench

I had a similar use case as well, so I took Dave's approach of micro-benchmarking with JMH and ran with it.我也有一个类似的用例,所以我采用了 Dave 的 JMH 微基准测试方法并运行了它。 The result is the boot-benchmark project.结果是引导基准项目。 I designed it such that it can be used to measure startup time for any Spring Boot application, using the executable jar produced by bootJar (previously called bootRepackage in Boot 1.5) Gradle task.我将它设计为可用于测量任何 Spring Boot 应用程序的启动时间,使用由bootJar (以前在 Boot 1.5 中称为bootRepackage )Gradle 任务生成的可执行 jar。 Feel free to use it and provide feedback.随意使用它并提供反馈。

My findings are as follows:我的发现如下:

  1. CPU matters. CPU 很重要。 A lot.很多。
  2. Starting the JVM with -Xverify:none helps significantly.使用-Xverify:none启动 JVM 有很大帮助。
  3. Excluding unnecessary autoconfigurations helps.排除不必要的自动配置会有所帮助。
  4. Dave recommended JVM argument -XX:TieredStopAtLevel=1 , but my tests didn't show significant improvement with that. Dave 推荐了 JVM 参数-XX:TieredStopAtLevel=1 ,但我的测试没有显示出显着的改进。 Also, -XX:TieredStopAtLevel=1 would probably slow down your first request.此外, -XX:TieredStopAtLevel=1可能会减慢您的第一个请求。
  5. There have been reports of hostname resolution being slow, but I didn't find it to be a problem for the apps I tested.报告称主机名解析很慢,但我没有发现这对我测试的应用程序来说是个问题。

Spring Boot 2.2.M1 has added feature to support Lazy Initialization in Spring Boot. Spring Boot 2.2.M1添加了支持 Spring Boot 中延迟初始化的功能。

By default, when an application context is being refreshed, every bean in the context is created and its dependencies are injected.默认情况下,刷新应用程序上下文时,会创建上下文中的每个 bean 并注入其依赖项。 By contrast, when a bean definition is configured to be initialized lazily it will not be created and its dependencies will not be injected until it's needed.相比之下,当 bean 定义被配置为延迟初始化时,它不会被创建,并且在需要之前不会注入它的依赖项。

Enabling Lazy Initialization Set spring.main.lazy-initialization to true启用延迟初始化spring.main.lazy-initialization设置为true

When to Enable Lazy Initialization何时启用延迟初始化

lazy initialization can offer significant improvements in start up time but there are some notable downsides too and it's important to enable it with care延迟初始化可以显着改善启动时间,但也有一些明显的缺点,小心启用它很重要

For more details please check Doc有关更多详细信息,请查看文档

Update:更新:

Spring Boot Spring Boot 2.4.0 - Startup Endpoint Spring Boot Spring Boot 2.4.0 - 启动端点

Spring Boot 2.4.0 has added a new Startup endpoint that can be used to identify beans that are taking longer than expected to start. Spring Boot 2.4.0 添加了一个新的 Startup 端点,可用于识别启动时间比预期更长的 bean。 You can get more details about the Application Startup tracking here您可以在此处获取有关应用程序启动跟踪的更多详细信息

As described in this question/answer, I think the best approach is to instead of adding only those you think you need, exclude the dependencies you know you don't need.如本问题/答案中所述,我认为最好的方法是不要只添加您认为需要的依赖项,而是排除您知道不需要的依赖项。

See: Minimise Spring Boot Startup Time请参阅: 最小化 Spring Boot 启动时间

In summary:总之:

You can see what is going on under the covers and enable debug logging as simple as specifying --debug when starting the application from the command-line.您可以看到幕后发生的事情并启用调试日志记录,就像在从命令行启动应用程序时指定 --debug 一样简单。 You can also specify debug=true in your application.properties.您还可以在 application.properties 中指定 debug=true。

Also, you can set the logging level in application.properties as simple as:此外,您可以在 application.properties 中设置日志记录级别,如下所示:

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

If you detect an auto-configured module you don't want, it can be disabled.如果您检测到不需要的自动配置模块,则可以将其禁用。 The docs for this can be found here: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-disabling-specific-auto-configuration可以在此处找到此文档: http : //docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-disabling-specific-auto-configuration

An example would look like:一个例子看起来像:

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

Well there is entire list of possible actions described here: https://spring.io/blog/2018/12/12/how-fast-is-spring好吧,这里描述了完整的可能操作列表: https : //spring.io/blog/2018/12/12/how-fast-is-spring

I will put the most important notes from Spring side (adjusted a little bit):我将把 Spring 方面最重要的笔记(稍微调整一下):

  • Classpath exclusions from Spring Boot web starters: Spring Boot web starters 中的类路径排除:
    • Hibernate Validator休眠验证器
    • Jackson (but Spring Boot actuators depend on it). Jackson(但 Spring Boot 执行器依赖于它)。 Use Gson if you need JSON rendering (only works with MVC out of the box).如果您需要 JSON 渲染,请使用 Gson(仅适用于开箱即用的 MVC)。
    • Logback: use slf4j-jdk14 instead Logback:改用 slf4j-jdk14
  • Use the spring-context-indexer.使用弹簧上下文索引器。 It's not going to add much, but every little helps.它不会增加太多,但每一点都有帮助。
  • Don't use actuators if you can afford not to.如果您负担不起,请不要使用执行器。
  • Use Spring Boot 2.1 and Spring 5.1.使用 Spring Boot 2.1 和 Spring 5.1。 Switch to 2.2 and 5.2 when they are available.可用时切换到 2.2 和 5.2。
  • Fix the location of the Spring Boot config file(s) with spring.config.location (command line argument or System property etc.).使用spring.config.location (命令行参数或系统属性等)修复 Spring Boot 配置文件的位置。 Example for testing in IDE: spring.config.location=file://./src/main/resources/application.properties .在 IDE 中进行测试的示例: spring.config.location=file://./src/main/resources/application.properties
  • Switch off JMX if you don't need it with spring.jmx.enabled=false (this is the default in Spring Boot 2.2)如果不需要使用spring.jmx.enabled=false关闭 JMX(这是 Spring Boot 2.2 中的默认值)
  • Make bean definitions lazy by default.默认情况下使 bean 定义为惰性。 There's a new flag spring.main.lazy-initialization=true in Spring Boot 2.2 (use LazyInitBeanFactoryPostProcessor for older Spring). Spring Boot 2.2 中有一个新标志spring.main.lazy-initialization=true (对于较旧的 Spring,使用LazyInitBeanFactoryPostProcessor )。
  • Unpack the fat jar and run with an explicit classpath.解压胖 jar 并使用显式类路径运行。
  • Run the JVM with -noverify .使用-noverify运行 JVM。 Also consider -XX:TieredStopAtLevel=1 (that will slow down the JIT later at the expense of the saved startup time).还要考虑-XX:TieredStopAtLevel=1 (稍后会以节省的启动时间为代价减慢 JIT)。

The mentioned LazyInitBeanFactoryPostProcessor (you can use it for Spring 1.5 if you cannot apply flag spring.main.lazy-initialization=true available from Spring 2.2):提到的LazyInitBeanFactoryPostProcessor (如果不能应用 Spring 2.2 提供的标志spring.main.lazy-initialization=true则可以将其用于 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);
      }
  }
}

You can also use (or write your own - it's simple) something to analyse beans initialization time: https://github.com/lwaddicor/spring-startup-analysis您还可以使用(或编写自己的 - 这很简单)一些东西来分析 bean 初始化时间: https : //github.com/lwaddicor/spring-startup-analysis

Hope it helps!希望能帮助到你!

If you're trying to optimize development turn-around for manual testing, I strongly recommend the use of devtools .如果您正在尝试优化手动测试的开发周转,我强烈建议使用devtools

Applications that use spring-boot-devtools will automatically restart whenever files on the classpath change.每当 classpath 上的文件发生更改时,使用 spring-boot-devtools 的应用程序将自动重新启动。

Just recompile -- and the server will restart itself (for Groovy you only need to update the source file).只需重新编译——服务器将自行重新启动(对于 Groovy,您只需要更新源文件)。 if you're using an IDE (eg 'vscode'), it may automatically compile your java files, so just saving a java file can initiate a server restart, indirectly -- and Java becomes just as seamless as Groovy in this regard.如果您使用的是 IDE(例如“vscode”),它可能会自动编译您的 java 文件,因此只需保存一个 java 文件就可以间接启动服务器重新启动——在这方面,Java 变得与 Groovy 一样无缝。

The beauty of this approach is that the incremental restart short-circuits some of the from-scratch startup steps -- so your service will be back up and running much more quickly!这种方法的美妙之处在于增量重启缩短了一些从头开始的启动步骤——因此您的服务将更快地备份和运行!


Unfortunately, this doesn't help with startup times for deployment or automated unit testing.不幸的是,这对部署或自动化单元测试的启动时间没有帮助。

using lazy loading in Spring did not give us significant improvement wrt startup time.在 Spring 中使用延迟加载并没有显着改善我们的启动时间。 This is a much better solution ->https://stackoverflow.com/questions/8501975/reuse-spring-application-context-across-junit-test-classes这是一个更好的解决方案->https://stackoverflow.com/questions/8501975/reuse-spring-application-context-across-junit-test-classes

WARNING: If you don't use Hibernate DDL for automatic DB schema generation and you don't use L2 cache, this answer is NOT applicable to you.警告:如果您不使用 Hibernate DDL 自动生成 DB 模式并且不使用 L2 缓存,则此答案不适用于您。 Scroll ahead.向前滚动。

My finding is that Hibernate adds significant time to application startup.我的发现是 Hibernate 为应用程序启动增加了大量时间。 Disabling L2 cache and database initialization results in faster Spring Boot app startup.禁用 L2 缓存和数据库初始化会导致 Spring Boot 应用程序启动更快。 Leave cache ON for production and disable it for your development environment.为生产保留缓存,并为您的开发环境禁用它。

application.yml:应用程序.yml:

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

Test results:检测结果:

  1. L2 cache is ON and ddl-auto: update : 54 seconds L2 缓存开启且ddl-auto: update : 54 秒

     INFO 5024 --- [restartedMain] osweb.context.ContextLoader : Root WebApplicationContext: initialization completed in 23331 ms INFO 5024 --- [restartedMain] bnspring.Application : Started Application in 54.251 seconds (JVM running for 63.766)
  2. L2 cache is OFF and ddl-auto: none : 32 seconds L2 缓存关闭且ddl-auto: none : 32 秒

     INFO 10288 --- [restartedMain] osweb.context.ContextLoader : Root WebApplicationContext: initialization completed in 9863 ms INFO 10288 --- [restartedMain] bnspring.Application : Started Application in 32.058 seconds (JVM running for 37.625)

Gained 22 seconds!获得了 22 秒! Now I wonder what will I do with all this free time现在我想知道这些空闲时间我会做什么

I find it strange nobody suggested these optimizations before.我觉得很奇怪以前没有人建议过这些优化。 Here're some general tips on optimizing project build and startup when developing:以下是一些在开发时优化项目构建和启动的一般提示:

  • exclude development directories from antivirus scanner:从防病毒扫描程序中排除开发目录:
    • project directory项目目录
    • build output directory (if it's outside of project directory)构建输出目录(如果它在项目目录之外)
    • IDE indices directory (eg ~/.IntelliJIdea2018.3) IDE 索引目录(例如 ~/.IntelliJIdea2018.3)
    • deployment directory (webapps in Tomcat)部署目录(Tomcat中的webapps)
  • upgrade hardware.升级硬件。 use faster CPU and RAM, better internet connection (for downloading dependencies) and database connection, switch to SSD (today NVMe SSD is the most performant storage).使用更快的 CPU 和 RAM、更好的互联网连接(用于下载依赖项)和数据库连接,切换到 SSD(今天 NVMe SSD 是性能最高的存储)。 a video card doesn't matter.显卡无所谓。
  • use latest Gradle and JVM versions.使用最新的 Gradle 和 JVM 版本。 Source: easy performance improvements .来源: 简单的性能改进
  • parallel execution.并行执行。 By using more concurrent processes, parallel builds can reduce the overall build time significantly.通过使用更多并发进程,并行构建可以显着减少整体构建时间。

WARNINGS警告

  1. the first option comes for the price of reduced security.第一种选择是以降低安全性为代价的。
  2. the second option costs money (obviously).第二种选择需要花钱(显然)。

In my case, there was too much breakpoints.就我而言,断点太多。 When I clicked "Mute Breakpoints" and restarted application in debug mode, application started in 10 times faster.当我单击“静音断点”并在调试模式下重新启动应用程序时,应用程序的启动速度提高了 10 倍。

To me it sounds like you're using a wrong configuration setting.对我来说,这听起来像是您使用了错误的配置设置。 Start by checking myContainer and possible conflicts.首先检查 myContainer 和可能的冲突。 To determine who is using the most resources you have to check the memory maps (see the amount of data!) for each dependency at a time - and that takes plenty of time, as well... (and SUDO privileges).要确定谁使用的资源最多,您必须一次检查每个依赖项的内存映射(查看数据量!) - 这也需要大量时间......(以及 SUDO 权限)。 By the way: are you usually testing the code against the dependencies?顺便说一句:您通常是否针对依赖项测试代码?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM