前段时间搞应用启动优化的时候参考了这篇文章的很多观点,并基于此开发了此工具——spring-startup-analyzer。这是一个分析Spring应用启动过程的工具,通过采集Spring应用启动过程数据,生成交互式分析报告,用于分析Spring应用启动卡点。同时还提供了一些工具来帮助开发者加快Spring应用的启动时间。下面详细介绍一下其提供的能力。
1. 能力介绍
1.1 分析能力
首先通过项目给出的HTML样例报告来看看此工具提供的分析功能。感兴趣的可以通过下面的链接访问:
- hokage-20230618000928-192.168.0.101-analyzer.html
把报告内容的细节部分收起来,可以看到如下图所示的内容:
主要包含6部分:
- 启动的统计数据。其中包括:启动时间、Bean的数量、使用/总共的jar包数量、未使用/总共的jar包数量及ClassLoader数量;
- Spring Bean初始化数据,采集了每个Spring Bean初始化时间及其详细内容;
- Bean初始化时间线。通过时间线的方式,清晰地展现了Spring应用启动过程中,各个Bean的顺序关系及时间消耗;
- 方法调用详细信息(可配置)。这里统计了方法的调用时间、总时间开销及每次调用平均时间
点开之后,还能看到具体每次调用的时间开销和调用细节:
- 启动后未被加载的JAR。列出了所有Spring应用启动后没有使用的jar包,可以有效地帮助清理不需要的依赖,为应用瘦身;
- 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初始化后对其进行任何自定义的修改或增强。
流程如下:
-
实现BeanPostProcessor扩展点
- 在postProcessBeforeInitialization中判断beanName是否是配置异步初始化Bean
- 如果需要异步化,查找init-method或者@PostConstruct修饰的方法
- 动态代理初始化方法,将初始化方法扔到线程池中执行,并返回Future
-
实现ApplicationListener
- 监听ContextRefreshedEvent事件,等待所有异步执行的init-method完成;
-
异步化的Bean可能在Spring Bean初始化顺序的末尾,导致异步优化效果不佳,支持优先加载配置异步化的Bean
- InstantiationAwareBeanPostProcessorAdapter可以做到在Bean实例化之前,预先回调。优先加载异步初始化的Bean。
1.2.2 热加载
热加载的思路来自一个issue,通过这个issue接触到了trava-jdk-8-dcevm,其基于 DCEVM 并集成了 HotswapAgent ,与标准 JDK 不同(只支持方法体内代码修改的热加载),其允许更高级的热部署,如方法、字段添加等等,如果在日常/预发环境使用trava-jdk的热加载能力,日常开发效率可以有很大的提升。
但是因为日常开发中部署分支和开发分支往往不是同一个分支,要想使用此能力,需要一些操作步骤有点繁琐,所以便实现了一个命令行工具,支持一个命令实现代码热加载。原理如下:
效果如下:
2. 如何使用
2.1 分析能力
2.1.1 安装jar包
提供了手动安装和一键脚本安装两种安装方式
- 手动安装
- 点击realease下载最新版tar.gz包
- 新建文件夹,并解压
- 脚本安装(linux/mac)
curl -sS https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/main/bin/install.sh | sh
脚本默认安装路径:$HOME/spring-startup-analyzer
2.1.2 配置项
本项目提供了以下几个配置项,不是必需配置项,可以直接使用默认配置。两种方式进行配置:
-
直接在配置文件中配置: 安装路径
/spring-startup-analyzer/config/spring-startup-analyzer.properties
-
在启动参数中配置,如应用启动健康检查超时时间为30分钟:
-Dspring-startup-analyzer.app.health.check.timeout=30
需要注意的是,判断应用启动成功的逻辑是:
-
对
SpringApplication.run
方法进行字节码增强,方法退出时则认为应用启动完成(仅对springboot应用生效) -
轮询请求健康检查的url,返回200则认为启动完成(适用于所有spring应用)
-
以上两种方式均未成功时,超出应用启动健康检查超时时间则认为应用启动完成
如果是非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异步加载
- 添加pom依赖
<dependency>
<groupId>io.github.linyimin0812</groupId>
<artifactId>spring-async-bean-starter</artifactId>
<version>${latest_version}</version>
</dependency>
- 配置异步加载信息
# 异步化的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 热加载
-
在release下载spring-startup-cli
-
在项目的工作目录(HOME)下执行此命令行工具
java -jar spring-startup-cli.jar
- 使用config命令配置相关信息
config set
- 编码完成后执行reload命令,即可完成热加载
作者:linyimin
链接:https://juejin.cn/post/7294260754915573769
来源:稀土掘金