ChatGPT解决这个技术问题 Extra ChatGPT

加快 Spring Boot 启动时间

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

虽然这与“习惯”相比可能并不多,但我很不高兴它需要这么多,主要是因为它破坏了开发流程。此时应用程序本身相当小,所以我假设大部分时间与添加的依赖项有关,而不是应用程序类本身。

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

确认这是问题(即如何“调试”Spring Boot)

如果真的是原因,我该如何限制它,让它变得更快?例如,如果我知道某些依赖项或包不包含 Spring 应该扫描的任何内容,有没有办法限制它?

我认为 enhancing Spring to have parallel bean initialization during startup 会加快速度,但该增强请求自 2011 年以来一直开放,没有任何进展。我在 Spring Boot 本身中看到了一些其他的努力,例如 Investigate Tomcat JarScanning speed improvements,但那是 Tomcat 特定的并且已被放弃。

本文:

http://www.nurkiewicz.com/2010/12/speeding-up-spring-integration-tests.html

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

欢迎任何(其他)建议。

发布您的代码。通常只扫描应用程序运行器定义的包。如果您为 @ComponentScan 定义了其他包,也会扫描这些包。另一件事是确保您没有启用调试或跟踪日志记录,因为通常日志记录很慢,非常慢。
如果您使用 Hibernate,它也往往会在应用程序启动时占用大量时间。
Spring 的按类型自动绑定加上工厂 bean 可能会很慢,因为您添加了很多 bean 和依赖项。
或者您可以使用缓存,spring.io/guides/gs/caching
感谢大家的评论 - 不幸的是,我无法发布代码(很多内部 jar),但是我仍在寻找一种调试方法。是的,我可能正在使用 A 或 B 或做 X 或 Y,这会减慢速度。我如何确定这一点?如果我添加一个依赖 X,它有 15 个传递依赖,我怎么知道这 16 个中的哪一个减慢了它的速度?如果我能找到,我以后能做些什么来阻止 Spring 检查它们吗?这样的指针会很有用!

l
luboskrnac

Spring Boot 做了很多可能不需要的自动配置。因此,您可能只想缩小应用所需的自动配置范围。要查看包含的自动配置的完整列表,只需在调试模式下运行 org.springframework.boot.autoconfigure 的日志记录(application.properties 中的 logging.level.org.springframework.boot.autoconfigure=DEBUG)。另一种选择是使用 --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 {

代码是从 this blog post 复制的。


你量过这个吗???是不是快了很多??在我看来,这是一个例外情况,更重要的是确保 Spring 测试上下文缓存正常工作
@idmitriev 我刚刚在我的应用程序上测量了这一点,我的应用程序启动时间为 53 秒,而不排除自动配置类为 73 秒。我确实排除了比上面列出的更多的类。
如何处理私有配置类?
有没有办法自动知道实际使用了哪些自动配置?也许是一些长期运行的东西,将整个应用程序生命周期中使用的所有自动配置加起来,然后您可以轮询执行器端点以查看该列表?
@payne,我不知道您所描述的任何事情。
k
kvantour

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

Dave Syer 完成了调试引导启动的最有用、最有条理的方法。 https://github.com/dsyer/spring-boot-startup-bench

我也有一个类似的用例,所以我采用了 Dave 的 JMH 微基准测试方法并运行它。结果是 boot-benchmark 项目。我对其进行了设计,使其可用于测量任何 Spring Boot 应用程序的启动时间,使用由 bootJar(以前在 Boot 1.5 中称为 bootRepackage)Gradle 任务生成的可执行 jar。随意使用它并提供反馈。

我的发现如下:

CPU 很重要。很多。使用 -Xverify:none 启动 JVM 有很大帮助。排除不必要的自动配置会有所帮助。 Dave 推荐了 JVM 参数 -XX:TieredStopAtLevel=1,但我的测试并没有显示出明显的改进。此外, -XX:TieredStopAtLevel=1 可能会减慢您的第一个请求。有报道称主机名解析速度很慢,但我认为这对我测试的应用程序来说不是问题。


您的项目似乎不是在 gradle 4.8.1 下构建的。您能否分享您在基准测试中使用的 gradle 版本?
@user991710 基于我的 Gradle wrapper,我使用的是 v4.6。 “不构建”是一个非常模糊的陈述,如果您有更具体的内容,请创建一个 gist 并在此处发布链接。您的要点应该列出您遵循的步骤以及您遇到的错误。
为了补充这一点,您能否添加一个示例,说明某人如何将您的基准与自定义应用程序一起使用?是否必须将其添加为类似于 minimal 的项目,或者是否可以简单地提供 jar?我试图做前者,但没有走得很远。
不要在生产环境中运行 -Xverify:none,因为它会破坏代码验证,您可能会遇到麻烦。 -XX:TieredStopAtLevel=1 如果您运行应用程序的时间很短(几秒钟)就可以了,否则它会降低生产力,因为它会为 JVM 提供长时间运行的优化。
许多池(当然是 Oracle UCP,但在我的测试中还有 Hikari 和 Tomcat)对池中的数据进行加密。我实际上不知道他们是在加密连接信息还是包装流。无论如何,加密使用随机数生成,因此具有高可用性、高吞吐量的熵源会在性能上产生显着差异。
N
Niraj Sonawane

Spring Boot 2.2.M1 增加了在 Spring Boot 中支持延迟初始化的功能。

默认情况下,当刷新应用程序上下文时,会创建上下文中的每个 bean 并注入其依赖项。相比之下,当 bean 定义被配置为延迟初始化时,它将不会被创建,并且在需要它之前不会注入它的依赖项。

启用延迟初始化spring.main.lazy-initialization 设置为 true

何时启用延迟初始化

延迟初始化可以显着改善启动时间,但也有一些明显的缺点,务必小心启用它

有关详细信息,请查看Doc

更新:

Spring Boot Spring Boot 2.4.0 - 启动端点

Spring Boot 2.4.0 添加了一个新的 Startup 端点,可用于识别启动时间超过预期的 bean。您可以获得有关应用程序启动跟踪的更多详细信息 here


如果开启延迟初始化,第一次加载速度非常快,但是当客户端第一次访问时,它可能会注意到一些延迟。我真的推荐这个用于开发而不是生产。
正如@IsuruDewasurendra 建议的那样,这不是推荐的方式,它会显着增加应用程序开始提供负载时的延迟。
它只是把罐子踢到了路上。
我只在开发中使用延迟初始化,因为第一次访问很懒,但它在 Spring Boot 中是一个很好的特性。
C
Community

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

请参阅:Minimise Spring Boot Startup Time

总之:

您可以看到幕后发生的事情并启用调试日志记录,就像在从命令行启动应用程序时指定 --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 {
}

P
Przemek Nowak

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

我将把 Spring 方面最重要的注释(稍微调整一下):

Spring Boot web starters 的类路径排除:Hibernate Validator Jackson(但 Spring Boot 执行器依赖于它)。如果您需要 JSON 渲染,请使用 Gson(仅适用于开箱即用的 MVC)。 Logback:改用 slf4j-jdk14

休眠验证器

Jackson(但 Spring Boot 执行器依赖于它)。如果您需要 JSON 渲染,请使用 Gson(仅适用于开箱即用的 MVC)。

Logback:改用 slf4j-jdk14

使用弹簧上下文索引器。它不会增加太多,但每一点都有帮助。

如果您负担得起,请不要使用执行器。

使用 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。

如果不需要,请使用 spring.jmx.enabled=false 关闭 JMX(这是 Spring Boot 2.2 中的默认设置)

默认情况下使 bean 定义变得惰性。 Spring Boot 2.2 中有一个新标志 spring.main.lazy-initialization=true (对于较旧的 Spring 使用 LazyInitBeanFactoryPostProcessor)。

解压缩 fat jar 并使用显式类路径运行。

使用 -noverify 运行 JVM。还要考虑 -XX:TieredStopAtLevel=1 (这将在以后以节省的启动时间为代价减慢 JIT)。

提到的 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);
      }
  }
}

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

希望能帮助到你!


B
Brent Bradburn

如果您尝试优化手动测试的开发周期,我强烈建议使用 devtools

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

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

这种方法的美妙之处在于增量重启缩短了一些从头开始的启动步骤——因此您的服务将更快地备份和运行!

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


n
naXa stands with Ukraine

警告:如果您不使用 Hibernate DDL 自动生成 DB 模式并且不使用 L2 缓存,则此答案不适用于您。向前滚动。

我的发现是 Hibernate 增加了应用程序启动的大量时间。禁用 L2 缓存和 database initialization 会导致更快的 Spring Boot 应用程序启动。为生产环境保留缓存并为您的开发环境禁用它。

应用程序.yml:

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

试验结果:

L2 缓存打开并且 ddl-auto: update: 54 seconds INFO 5024 --- [restartedMain] osweb.context.ContextLoader : Root WebApplicationContext: 初始化在 23331 ms 内完成 INFO 5024 --- [restartedMain] bnspring.Application : Started Application in 54.251 秒(JVM 运行 63.766)二级缓存关闭并且 ddl-auto:无:32 秒 INFO 10288 --- [restartedMain] osweb.context.ContextLoader : Root WebApplicationContext:初始化在 9863 毫秒内完成 INFO 10288 --- [restartedMain ] bnspring.Application :在 32.058 秒内启动应用程序(JVM 运行时间为 37.625)

获得22秒!现在我想知道我将如何利用这些空闲时间


hibernate.hbm2ddl.auto=update 与 l2 缓存无关。 ddl..=update 指定扫描当前数据库模式,并计算必要的 sql 以更新模式以反映您的实体。 'None' 不会进行此验证(也不会尝试更新架构)。最佳实践是使用像 liquibase 这样的工具,您将在其中处理架构更改,并且还可以跟踪它们。
@RaduToader 这个问题和我的回答是关于加快 Spring Boot 启动时间。它们与 Hibernate DDL 与 Liquibase 的讨论无关;这些工具各有利弊。我的观点是我们可以禁用数据库模式更新并仅在必要时启用。即使模型自上次运行以来没有更改(用于比较 DB 模式和自动生成的模式),Hibernate 也会在启动时花费大量时间。 L2 缓存也是如此。
是的,我知道,但我的意思是,不解释它的真正作用有点危险。您可能很容易以空的数据库告终。
@RaduToader 在我的回答中有一个关于数据库初始化的文档页面的链接。你读过它吗?它包含详尽的指南,列出了所有最流行的工具(Hibernate 和 Liquibase,以及 JPA 和 Flyway)。同样,今天我在答案的顶部添加了一个明确的警告。您认为我需要任何其他更改来解释后果吗?
完美的。谢谢
n
naXa stands with Ukraine

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

从防病毒扫描程序中排除开发目录:项目目录构建输出目录(如果它在项目目录之外)IDE 索引目录(例如 ~/.IntelliJIdea2018.3)部署目录(Tomcat 中的 webapps)

项目目录

构建输出目录(如果它在项目目录之外)

IDE 索引目录(例如 ~/.IntelliJIdea2018.3)

部署目录(Tomcat 中的 webapps)

升级硬件。使用更快的 CPU 和 RAM,更好的互联网连接(用于下载依赖项)和数据库连接,切换到 SSD(如今 NVMe SSD 是性能最高的存储)。显卡无所谓。

使用最新的 Gradle 和 JVM 版本。来源:简单的性能改进。

并行执行。通过使用更多的并发进程,并行构建可以显着减少整体构建时间。

警告

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


问题是关于改善启动时间,而不是编译时间。
@ArtOfWarfare 再次阅读了这个问题。该问题将问题描述为“我很不高兴花费这么多 [时间],主要是因为它破坏了开发流程”。我觉得这是一个主要问题,并在我的回答中解决了这个问题。
D
Daulet Kadirbekov

就我而言,断点太多。当我单击“静音断点”并在调试模式下重新启动应用程序时,应用程序的启动速度提高了 10 倍。


佚名

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