likes
comments
collection
share

Spring应用启动很慢?试试这个工具

作者站长头像
站长
· 阅读数 80

前段时间搞应用启动优化的时候参考了这篇文章的很多观点,并基于此开发了此工具——spring-startup-analyzer。这是一个分析Spring应用启动过程的工具,通过采集Spring应用启动过程数据,生成交互式分析报告,用于分析Spring应用启动卡点。同时还提供了一些工具来帮助开发者加快Spring应用的启动时间。下面详细介绍一下其提供的能力。

1. 能力介绍


1.1 分析能力

首先通过项目给出的HTML样例报告来看看此工具提供的分析功能。感兴趣的可以通过下面的链接访问:

把报告内容的细节部分收起来,可以看到如下图所示的内容:

Spring应用启动很慢?试试这个工具 主要包含6部分:

  • 启动的统计数据。其中包括:启动时间、Bean的数量、使用/总共的jar包数量、未使用/总共的jar包数量及ClassLoader数量;

Spring应用启动很慢?试试这个工具

  • Spring Bean初始化数据,采集了每个Spring Bean初始化时间及其详细内容;

Spring应用启动很慢?试试这个工具

  • Bean初始化时间线。通过时间线的方式,清晰地展现了Spring应用启动过程中,各个Bean的顺序关系及时间消耗;

Spring应用启动很慢?试试这个工具

  • 方法调用详细信息(可配置)。这里统计了方法的调用时间、总时间开销及每次调用平均时间

Spring应用启动很慢?试试这个工具

点开之后,还能看到具体每次调用的时间开销和调用细节:

Spring应用启动很慢?试试这个工具

  • 启动后未被加载的JAR。列出了所有Spring应用启动后没有使用的jar包,可以有效地帮助清理不需要的依赖,为应用瘦身;

Spring应用启动很慢?试试这个工具

  • Spring应用启动过程主线程火焰图。提供更详细的信息用于分析应用启动卡点

Spring应用启动很慢?试试这个工具

1.2 启动优化


提供了生产环境和日常/预发环境两种场景的优化,对于生产环境,提供Spring Bean异步加载工具,将耗时的Bean初始化方法异步化(参考SOFABoot的异步初始化方法实现)。对于日常/预发环境,提供命令行工具,实现一个命令完成热加载。

1.2.1 Spring Bean异步加载


Spring在启动过程中对于Bean的加载是顺序进行的,如果存在部分Bean加载耗时比较严重时,应用的启动时长会被严重拉长。Bean的加载耗时主要在init-method方法或@PostConstruct标识的方法,容器中的Bean多数不存在依赖关系,如果可以将Bean的初始化方法异步化,可以大大降低启动耗时。

Spring在启动过程中对于Bean的加载是顺序进行的,如果存在部分Bean加载耗时比较严重时,应用的启动时长会被严重拉长。Bean的加载耗时主要在init-method方法或@PostConstruct标识的方法,容器中的Bean多数不存在依赖关系,如果可以将Bean的初始化方法异步化,可以大大降低启动耗时。

主要依赖Spring提供一个BeanPostProcessor扩展点实现。BeanPostProcessor允许我们自定义bean的实例化和初始化过程。它是一个接口,定义了两个方法:

主要依赖Spring提供一个BeanPostProcessor扩展点实现。BeanPostProcessor允许我们自定义bean的实例化和初始化过程。它是一个接口,定义了两个方法:

postProcessBeforeInitialization(Object bean, String beanName):在bean初始化之前调用该方法,可以在初始化之前对bean对象进行任何自定义的修改或增强。

postProcessAfterInitialization(Object bean, String beanName):在bean初始化之后调用该方法。可以在bean初始化后对其进行任何自定义的修改或增强。

流程如下:

  1. 实现BeanPostProcessor扩展点

    • 在postProcessBeforeInitialization中判断beanName是否是配置异步初始化Bean
    • 如果需要异步化,查找init-method或者@PostConstruct修饰的方法
    • 动态代理初始化方法,将初始化方法扔到线程池中执行,并返回Future
  2. 实现ApplicationListener

    • 监听ContextRefreshedEvent事件,等待所有异步执行的init-method完成;
  3. 异步化的Bean可能在Spring Bean初始化顺序的末尾,导致异步优化效果不佳,支持优先加载配置异步化的Bean

    • InstantiationAwareBeanPostProcessorAdapter可以做到在Bean实例化之前,预先回调。优先加载异步初始化的Bean。

1.2.2 热加载


热加载的思路来自一个issue,通过这个issue接触到了trava-jdk-8-dcevm,其基于 DCEVM 并集成了 HotswapAgent ,与标准 JDK 不同(只支持方法体内代码修改的热加载),其允许更高级的热部署,如方法、字段添加等等,如果在日常/预发环境使用trava-jdk的热加载能力,日常开发效率可以有很大的提升。

但是因为日常开发中部署分支和开发分支往往不是同一个分支,要想使用此能力,需要一些操作步骤有点繁琐,所以便实现了一个命令行工具,支持一个命令实现代码热加载。原理如下:

Spring应用启动很慢?试试这个工具

效果如下:

Spring应用启动很慢?试试这个工具

2. 如何使用


2.1 分析能力

2.1.1 安装jar包


提供了手动安装和一键脚本安装两种安装方式

  1. 手动安装
  • 点击realease下载最新版tar.gz包
  • 新建文件夹,并解压
  1. 脚本安装(linux/mac)
curl -sS https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/main/bin/install.sh | sh

脚本默认安装路径:$HOME/spring-startup-analyzer

2.1.2 配置项


本项目提供了以下几个配置项,不是必需配置项,可以直接使用默认配置。两种方式进行配置:

  1. 直接在配置文件中配置: 安装路径/spring-startup-analyzer/config/spring-startup-analyzer.properties

  2. 在启动参数中配置,如应用启动健康检查超时时间为30分钟:-Dspring-startup-analyzer.app.health.check.timeout=30

需要注意的是,判断应用启动成功的逻辑是:

  1. SpringApplication.run方法进行字节码增强,方法退出时则认为应用启动完成(仅对springboot应用生效)

  2. 轮询请求健康检查的url,返回200则认为启动完成(适用于所有spring应用)

  3. 以上两种方式均未成功时,超出应用启动健康检查超时时间则认为应用启动完成

如果是非springboot应用,需要通过spring-startup-analyzer.app.health.check.endpoints配置一下健康检查URL。

更多配置项

2.1.3


此项目是以agent的方式启动的,所以在启动命令中添加参数-javaagent:安装路径/spring-startup-analyzer/lib/spring-profiler-agent.jar

  • 以java命令行的方式启动应用,则在命令行中添加参数,例如:
java -javaagent:/Users/runner/spring-startup-analyzer/lib/spring-profiler-agent.jar \ 
-Dproject.name=mac-demo \     
-Dspring-startup-analyzer.admin.http.server.port=8066 \     
-jar /Users/runner/spring-startup-analyzer/spring-boot-demo.jar
  • IDEA中启动,则需要在VM options选项中添加

日志文件路径:安装路径/spring-startup-analyzer/logs

  • startup.log: 启动过程中的日志
  • transform.log: 被re-transform的类/方法信息

应用启动完成后会在console和startup.log文件中输出======= spring-startup-analyzer finished, click [http://localhost](http://localhost/):xxxx to visit details. ======,可以通过此输出来判断采集是否完成。

2.2 启动优化

2.2.1 Spring Bean异步加载


  1. 添加pom依赖
<dependency>
    <groupId>io.github.linyimin0812</groupId>
    <artifactId>spring-async-bean-starter</artifactId>
    <version>${latest_version}</version>
</dependency>
  1. 配置异步加载信息
# 异步化的Bean可能在Spring Bean初始化顺序的末尾,导致异步优化效果不佳,打开配置优先加载异步化的Bean
spring-startup-analyzer.boost.spring.async.bean-priority-load-enable=true
# 指定异步的Bean名称
spring-startup-analyzer.boost.spring.async.bean-names=testBean,testComponent
# 执行异步化Bean初始化方法线程池的核心线程数
spring-startup-analyzer.boost.spring.async.init-bean-thread-pool-core-size=8
# 执行异步化Bean初始化方法线程池的最大线程数
spring-startup-analyzer.boost.spring.async.init-bean-thread-pool-max-size=8

2.2.2 热加载


  1. release下载spring-startup-cli

  2. 在项目的工作目录(HOME)下执行此命令行工具

java -jar spring-startup-cli.jar
  1. 使用config命令配置相关信息
config set
  1. 编码完成后执行reload命令,即可完成热加载

更多信息查看github

3. 参考