1、概览 {#1概览}
Spring Boot 的启动过程可能涉及到繁琐的资源初始化。本文将带你了解如何通过 Spring Boot Actuator 的 Startup
端点追踪和监控这些启动信息。
2、应用启动追踪 {#2应用启动追踪}
追踪应用启动过程中的各个步骤可以提供有用的信息,帮助我们了解应用启动各个阶段所花费的时间。这种工具还能提高我们对上下文生命周期和应用启动顺序的理解。
Spring 提供了 记录应用启动 功能。此外,Spring Boot Actuator 还通过 HTTP 或 JMX 提供了多种生产级监控和管理功能。
从 Spring Boot 2.4 开始,应用启动追踪指标可通过 /actuator/startup
端点获得。
3、设置 {#3设置}
在 pom.xml
中添加 spring-boot-starter-actuator
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
还需要 spring-boot-starter-web
依赖项,用于暴露 HTTP 端点:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.4</version>
</dependency>
在 application.properties
文件中配置属性,在 HTTP 上公开所需的端点:
management.endpoints.web.exposure.include=startup
最后,使用 curl
查询 Actuator 的 HTTP 端点,使用 jq
解析 JSON 响应。
4、Actuator 端点 {#4actuator-端点}
为了捕获启动事件,需要使用 @ApplicationStartup
接口的实现来配置应用。默认情况下,管理应用生命周期的 ApplicationContext
使用 "空" 操作的实现。这显然不会执行启动检测和追踪,从而将开销降至最低。
因此,与其他 Actuator 端点不同,需要一些额外的设置。
4.1、使用 BufferingApplicationStartup
{#41使用-bufferingapplicationstartup}
将应用的 "启动配置" 设置为 BufferingApplicationStartup
的实例。这是 Spring Boot 提供的 ApplicationStartup
接口的内存实现。它捕获 Spring 启动过程中的事件,并将其存储在内部缓冲区中。
创建一个简单的应用,并实现这一功能:
@SpringBootApplication
public class StartupTrackingApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(StartupTrackingApplication.class);
app.setApplicationStartup(new BufferingApplicationStartup(2048));
app.run(args);
}
}
为内部缓冲区指定了 2048 的容量。一旦缓冲区中的事件达到这个容量,就不会再记录任何数据。因此,必须根据应用的复杂度和启动过程中执行的各种步骤,使用适当的值来存储事件。
只有按这种方式配置了实现,Actuator 端点才可用。
4.2、startup
端点 {#42startup-端点}
现在,启动应用并查询 startup
Actuator 端点。
使用 curl
来调用这个 POST 端点,并使用 jq
来格式化 JSON 输出:
> curl 'http://localhost:8080/actuator/startup' -X POST | jq
{
"springBootVersion": "2.5.4",
"timeline": {
"startTime": "2021-10-17T21:08:00.931660Z",
"events": [
{
"endTime": "2021-10-17T21:08:00.989076Z",
"duration": "PT0.038859S",
"startTime": "2021-10-17T21:08:00.950217Z",
"startupStep": {
"name": "spring.boot.application.starting",
"id": 0,
"tags": [
{
"key": "mainApplicationClass",
"value": "com.baeldung.startup.StartupTrackingApplication"
}
],
"parentId": null
}
},
{
"endTime": "2021-10-17T21:08:01.454239Z",
"duration": "PT0.344867S",
"startTime": "2021-10-17T21:08:01.109372Z",
"startupStep": {
"name": "spring.boot.application.environment-prepared",
"id": 1,
"tags": [],
"parentId": null
}
},
... other steps not shown
{
"endTime": "2021-10-17T21:08:12.199369Z",
"duration": "PT0.00055S",
"startTime": "2021-10-17T21:08:12.198819Z",
"startupStep": {
"name": "spring.boot.application.running",
"id": 358,
"tags": [],
"parentId": null
}
}
]
}
}
如你所见,详细的 JSON 响应包含了一个启动事件的列表。它包含每个步骤的各种详细信息,如步骤名称、开始时间、结束时间以及步骤计时详细信息。有关响应结构的详细信息,请参阅 Spring Boot Actuator Web API 文档。
此外,核心容器中定义的完整步骤列表和每个步骤的更多细节可在 Spring 参考文档 中找到。
这里需要注意的一个重要细节是,再次调用端点不会响应详细的 JSON 。这是因为 startup 端点调用会清除内部缓冲区。因此,需要重新启动应用,调用相同的端点并再次接收完整的响应。
你可以保存 Payload,以便在必要时进行进一步分析。
4.3、过滤启动事件 {#43过滤启动事件}
如上所述,缓存实现在内存中存储事件的容量是固定的。因此,在缓冲区中存储大量事件可能并不可取。
可以过滤事件,只存储可能感兴趣的事件:
BufferingApplicationStartup startup = new BufferingApplicationStartup(2048);
startup.addFilter(startupStep -> startupStep.getName().matches("spring.beans.instantiate");
这里,使用 addFilter
方法只检测指定名称的步骤。
4.4、自定义 {#44自定义}
还可以继承 BufferingApplicationStartup
以提供自定义的启动追踪行为。
由于这种工具在测试环境中比在生产环境中更有价值,因此可以通过使用系统属性来轻松地在 "空" 操作、内存缓冲或自定义实现之间进行切换。
5、分析启动时间 {#5分析启动时间}
举个实际的例子,尝试找出启动过程中可能需要较长时间初始化的任何 Bean 实例。例如,这可能是缓存加载、数据库连接池或应用启动过程中其他昂贵的初始化操作。
可以像以前一样调用端点,使用 jq
来处理输出。
由于响应非常冗长,所以过滤与 spring.beans.instantiate
名称相匹配的步骤,并按持续时间排序:
> curl 'http://localhost:8080/actuator/startup' -X POST \
| jq '[.timeline.events
| sort_by(.duration) | reverse[]
| select(.startupStep.name | match("spring.beans.instantiate"))
| {beanName: .startupStep.tags[0].value, duration: .duration}]'
上述表达式处理了响应 JSON,以提取时序信息:
- 按降序排列
timeline.events
数组。 - 从排序数组中选择与 s
pring.beans.instantiate
名称匹配的所有步骤。 - 创建一个新的 JSON 对象,其中包含
beanName
和每个匹配步骤的持续时间。
输出结果显示了应用启动过程中实例化的各种 Bean 的简明、有序和过滤后的视图:
[
{
"beanName": "resourceInitializer",
"duration": "PT6.003171S"
},
{
"beanName": "tomcatServletWebServerFactory",
"duration": "PT0.143958S"
},
{
"beanName": "requestMappingHandlerAdapter",
"duration": "PT0.14302S"
},
...
]
如上,可以看到 resourceInitializer
Bean 在启动过程中耗时约 6 秒。这对整个应用的启动时间造成了很大的影响。使用这种方法,可以有效地识别这个问题,并集中精力进一步调查和寻找可能的解决方案。
需要注意的是,ApplicationStartup
仅用于应用程序启动时。换句话说,它不能取代 Java Profiler 和指标收集(Metrics Collection)框架来进行应用检测。
6、总结 {#6总结}
本文介绍了如何配置和使用 Spring Boot Actuator 的 startup
端点来获取和分析应用详细的启动指标,从而优化启动时间。
Ref:https://www.baeldung.com/spring-boot-actuator-startup