写点什么

Spring 应用启动分析优化

作者:林贻民
  • 2023-06-21
    浙江
  • 本文字数:2911 字

    阅读完需:约 10 分钟

最近在搞应用的启动优化,实现了此项目(spring-startup-ananlyzer),采集 Spring 应用启动过程数据,生成交互式分析报告(HTML),用于分析 Spring 应用启动卡点,优化 Spring 应用启动速度,并实现了一个 Bean 初始化方法异步化执行工具,实现了应用启动时长降低 50%-60%。

🤩核心能力

📈Spring 应用启动数据采集报告

Spring Bean 初始化详情信息,支持初始化耗时/beanName 搜索、Spring Bean 初始化时序图方法调用次数及耗时统计(支持自定义方法)、应用未加载的 jar 包(帮助 fatjar 瘦身)及应用启动过程线程 wall clock 火焰图,帮助开发者快速分析定位应用启动卡点。


  1. Spring Bean 初始化详情

  2. Spring Bean 初始化时序图



3. 方法调用次数、耗时统计(支持自定义方法)



4. 应用未加载的 jar 包(帮助 fatjar 瘦身)



5. 应用启动过程线程 wall clock 火焰图(支持指定线程名称,不指定则采集全部线程)


🚀应用启动时长优化

提供一个 Spring Bean 异步初始化 jar 包,针对初始化耗时比较长的 bean,异步执行 init 和 @PostConstruct 方法提高应用启动速度。

📈Spring 应用启动数据采集报告

安装 jar 包

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

  1. 手动安装

  • 点击realease下载最新版 tar.gz 包

  • 新建文件夹,并解压


mkdir -p ${HOME}/spring-startup-analyzer
cd 下载路径
tar -zxvf spring-startup-analyzer.tar.gz ${HOME}/spring-startup-analyzer
复制代码


  1. 脚本安装


curl -sS https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/main/bin/install.sh | sh
复制代码

配置项

在启动参数中进行配置,如配置超时时间为 30 分钟:-Dspring-startup-analyzer.app.health.check.timeout=30


请务必配置spring-startup-analyzer.app.health.check.endpoints选项,不然会一直采集直到应用启动检查超时时间(默认 20 分钟)才会停止,每隔 1 秒请求一次 endpoint,请求响应头状态码为 200 则认为应用启动完成。默认健康检查 URL:http://127.0.0.1:7002/actuator/health

应用启动

此项目是以 agent 的方式启动的,所以在启动命令中添加参数-javaagent:$HOME/spring-startup-analyzer/lib/spring-profiler-agent.jar即可。如果是以 java 命令行的方式启动应用,则在命令行中添加,如果是在 IDEA 中启动,则需要在 VM options 选项中添加。


日志文件路径:$HOME/spring-startup-analyzer/logs


  • startup.log: 启动过程中的日志

  • transform.log: 被 re-transform 的类/方法信息


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

自定义扩展

如果需要自定义观测能力,需要引入 spring-profiler-starter 的 pom 作为扩展项目的父 pom,然后就可以使用项目对外暴露的接口进行扩展。更多的细节可以参考spring-profiler-extension的实现



<parent>
<groupId>io.github.linyimin0812</groupId>
<artifactId>spring-profiler-starter</artifactId>
<version>2.0.0</version>
</parent>
复制代码

扩展接口

public interface EventListener extends Startable {
/** * 应用启动时调用 */ void start();
/** * 应用启动完成后调用 */ void stop(); /** * 需要增强的类 * @param className 类全限定名, 如果为空, 默认返回为true
* @return true: 进行增强, false: 不进行增强 */ boolean filter(String className);
/** * 需要增强的方法(此方法会依赖filter(className), 只有filter(className)返回true时,才会执行到此方法) * @param methodName 方法名 * @param methodTypes 方法参数列表 * @return true: 进行增强, false: 不进行增强 */ default boolean filter(String methodName, String[] methodTypes) { return true; }
/** * 事件响应处理逻辑 * @param event 触发的事件 */ void onEvent(Event event);
/** * 监听的事件 * @return 需要监听的事件列表 */ List<Event.Type> listen();
}
复制代码


其中 start()和 stop()方法代表系统的生命周期,分别在应用开始启动和应用启动完成时调用。filter()方法指定需要增强的类/方法。listen()方法指定监听的事件,包括进入方法和方法返回两种事件。onEvent()方法在监听的事件发生时会被调用


例如下面是一个统计应用启动过程中java.net.URLClassLoader.findResource(String)方法调用次数的扩展

打包运行

spring-profiler-starter的 pom 中已经定义了打包 plugin,默认会将生成的 jar 包拷贝到$HOME/spring-startup-analyzer/extension文件下。



mvn clean package
复制代码


只要按照步骤安装jar包安装好此项目,再执行上述的打包命令,打包好后再启动应用即可加载扩展 jar 包。

🚀应用启动时长优化

Spring 应用启动数据采集报告中,可以获取初始化耗时长的 Bean,因为 Spring 启动过程是单线程完成的,为了优化应用的启动时长,可以考虑将这些耗时长的 Bean 的初始化方法异步化,查看实现原理


需要注意:


  • 应该优先从代码层面优化初始化时间长的 Bean,从根本上解决 Bean 初始化耗时长问题

  • 对于二方包/三方包中初始化耗时长的 Bean(无法进行代码优化)再考虑 Bean 的异步化

  • 对于不被依赖的 Bean 可以放心进行异步化,可以通过各个 Bean 加载耗时中的 Root Bean 判断 Bean 是否被其他 Bean 依赖

  • 对于被依赖的 Bean 需要小心分析,在应用启动过程中不能其他 Bean 被调用,否则可能会存在问题

支持异步化的 Bean 类型

支持@Bean, @PostConstruct@ImportResource 方式初始化 bean,使用 demo: spring-boot-async-bean-demo


1. @Bean(initMethod = "init")标识的 Bean


@Bean(initMethod = "init")
public TestBean testBean() {
return new TestBean();
}
复制代码


2. @PostConstruct 标识的 Bean


@Component
public class TestComponent {
@PostConstruct
public void init() throws InterruptedException {
Thread.sleep(20 * 1000);
}
}
复制代码

接入异步 Bean 优化

1. 添加 pom 依赖


<dependency>
<groupId>io.github.linyimin0812</groupId>
<artifactId>spring-async-bean-starter</artifactId>
<version>2.0.0</version>
</dependency>
复制代码


2. 配置一步加载信息


# 异步化的Bean可能在Spring Bean初始化顺序的末尾,导致异步优化效果不佳,打开配置优先加载异步化的
Beanspring-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
复制代码


3. 检查 Bean 是否异步初始化

查看日志$HOME/spring-startup-analyzer/logs/startup.log文件,对于异步执行初始化的方法,会按照以下格式写一条日志:


async-init-bean, beanName: ${beanName}, async init method: ${initMethodName}

用户头像

林贻民

关注

还未添加个人签名 2018-08-07 加入

还未添加个人简介

评论

发布
暂无评论
Spring应用启动分析优化_字节码插桩_林贻民_InfoQ写作社区