Spring Boot 中的 Startup Actuator 端点

1、概览

Spring Boot 的启动过程可能涉及到繁琐的资源初始化。本文将带你了解如何通过 Spring Boot Actuator 的 Startup 端点追踪和监控这些启动信息。

2、应用启动追踪

追踪应用启动过程中的各个步骤可以提供有用的信息,帮助我们了解应用启动各个阶段所花费的时间。这种工具还能提高我们对上下文生命周期和应用启动顺序的理解。

Spring 提供了 记录应用启动 功能。此外,Spring Boot Actuator 还通过 HTTP 或 JMX 提供了多种生产级监控和管理功能。

Spring Boot 2.4 开始,应用启动追踪指标可通过 /actuator/startup 端点获得。

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 端点

为了捕获启动事件,需要使用 @ApplicationStartup 接口的实现来配置应用。默认情况下,管理应用生命周期的 ApplicationContext 使用 “空” 操作的实现。这显然不会执行启动检测和追踪,从而将开销降至最低。

因此,与其他 Actuator 端点不同,需要一些额外的设置。

4.1、使用 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 端点

现在,启动应用并查询 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、过滤启动事件

如上所述,缓存实现在内存中存储事件的容量是固定的。因此,在缓冲区中存储大量事件可能并不可取。

可以过滤事件,只存储可能感兴趣的事件:

BufferingApplicationStartup startup = new BufferingApplicationStartup(2048);
startup.addFilter(startupStep -> startupStep.getName().matches("spring.beans.instantiate");

这里,使用 addFilter 方法只检测指定名称的步骤。

4.4、自定义

还可以继承 BufferingApplicationStartup 以提供自定义的启动追踪行为。

由于这种工具在测试环境中比在生产环境中更有价值,因此可以通过使用系统属性来轻松地在 “空” 操作、内存缓冲或自定义实现之间进行切换。

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 数组。
  • 从排序数组中选择与 spring.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、总结

本文介绍了如何配置和使用 Spring Boot Actuator 的 startup 端点来获取和分析应用详细的启动指标,从而优化启动时间。


Ref:https://www.baeldung.com/spring-boot-actuator-startup