Spring Boot之Actuator详解

x33g5p2x  于2022-09-18 转载在 Spring  
字(5.4k)|赞(0)|评价(0)|浏览(1344)

这篇文章将详细介绍Spring Boot Actuator,以及我们如何使用它来优化Spring Boot应用程序的启动时间。

Spring Boot的启动顺序是一个比较复杂的过程。我们可以通过跟踪应用程序的启动事件来加速Spring Boot应用程序的启动。直到现在,我们还没有一个更简单的方法来做到这一点。然而,Spring Boot 2.4.0版本增加了对启动事件的支持。

Startup Actuator介绍

在进入这篇文章之前。我想确保你需要对Spring Boot Actuators有一个了解。一个典型的应用程序启动包含三个主要部分。

  1. 应用程序上下文本身的生命周期。(读取配置、类等)
  2. Bean的创建、评估和后期处理。
  3. 来自应用程序本身的事件处理。

你可以在这个官方spring文档中找到启动步骤的清单。通过使用Startup Actuator Endpoints跟踪这些步骤,我们可以准确地指出应用程序的哪一部分慢了,拖累了应用程序的启动时间。我们甚至可以确定哪些Bean比较耗时间,并可能修复它们。Spring Boot通过新引入的ApplicationStartup实现了这个功能。

ApplicationStartup接口有三个实现变体。

类名描述
DefaultApplicationStartup一个no-op的实现,通过**默认的方式进行配置。
BufferingApplicationStartup一个im-memory buffered实现,用于捕获启动步骤。
FlightRecorderApplicationStartup一个将捕获的步骤作为事件转发给java飞行记录器的实现。

除了DefaultApplicationStartup的实现,其他实现都只需要几行代码。我们将在这篇文章的后面看到这些例子。

对于默认的实现,我们没有什么可说的。因为这个实现只是有一个占位符。BufferingApplicationStartup和FlightRecorderApplicationStartup的实现有它们的空间。让我们来详细分析他们。

配置Startup Actuator endpoint

记住,应用程序的启动是由一系列步骤组成的。当这些事件被触发时,应用程序启动可以跟踪它们并以容易理解的格式提供度量。为此,FlightRecorderApplicationStartup实现是一种直接的方法。这个实现是一个内存中的解决方案,在内存中保存配置好的事件数量。这些信息使用启动执行器端点被提取出来,或者可以在应用程序中被读取和处理。下面是一个简单的例子。

@SpringBootApplication
public class ActuatorStartupExampleApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(ActuatorStartupExampleApplication.class);
        app.setApplicationStartup(new BufferingApplicationStartup(2048));
        app.run(args);
    }

}

在进行上述改变的同时,你需要为启动器启用管理端点以查看结果。要做到这一点,在你的属性文件中添加以下配置。

management.endpoints.web.exposure.include=startup

如果你这样做对了,向http://localhost:8080/actuator/startup 发送POST请求就会得到事件信息。

从响应中理解事件信息需要一点努力,但并不困难。每个事件条目都有以下格式。

{
      "startupStep": {
          "name": "spring.beans.instantiate",
          "id": 23,
          "parentId": 6,
          "tags": [
              {
                  "key": "beanName",
                  "value": "org.springframework.context.annotation.internalCommonAnnotationProcessor"
              },
              {
                  "key": "beanType",
                  "value": "interface org.springframework.beans.factory.config.BeanPostProcessor"
              }
          ]
      },
      "startTime": "2020-11-14T07:28:51.245122900Z",
      "endTime": "2020-11-14T07:28:51.247944100Z",
      "duration": "PT0.0028212S"
  }

它有时间细节以及事件如何使用parentIdid在树状结构中排列。标签信息讲述了关于启动步骤的额外信息。

重要的是:执行器端点只有在与BufferingApplicationStartup一起启用时才可使用。否则,StartupEndpointAutoConfiguration将不会配置StartupEndpoint

###用flight-recorder收集统计

在我看来,BufferingApplicationStartup已经足够在本地测试了。但是,理解这些事件在我们自己身上会变得有点令人沮丧。因此,Spring Boot为启动跟踪提供了另一种实现。这个实现使用Java Flight Recorder事件记录作为其存储。

这个ApplicationStartup跟踪实现以JFR事件日志的形式转发所有事件细节。如果我们在启用Flight Recorder剖析的情况下运行我们的应用程序,我们可以看到事件开始在事件类别下显示出来。让我们通过修改上述例子中的一行来测试一下。

app.setApplicationStartup(new FlightRecorderApplicationStartup());

另外,请注意,这个实现至少需要Oracle JDK 9或OpenJDK 8.u262。所以要确保你的JDK设置是正确的。一旦上述所有设置完成,你就可以使用flight-recorder剖析调用该应用程序,如下图所示。

$ java -XX:+FlightRecorder -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar actuator-startup-example-0.0.1-SNAPSHOT.jar

一旦你运行该应用程序,在你的当前工作目录中会有一个recording.jfr。你可以使用Java Mission Control。下面是一个屏幕截图的例子。

事件浏览器可以显示启动步骤的适当数据。在事件类型树中,导航到Spring Application > Startup Step。这将给出与之前BufferingApplicationStartup实现相同的信息。

需要考虑的事项

这里有一些需要考虑的重要事项。

startup endpoint 不是所有的

使用BufferingApplicationStartup将激活一个StartupEndpoint。如果你打算使用Default或FlightRecorder实现,那么StartupEndpointAutoConfiguration将不会激活。如果你打算使用一个自定义的实现,那么请扩展BufferingApplicationStartup

FlightRecorderApplicationStartup和JDK版本

FlightRecorderApplicationStartup直接依赖于jdk.jfr包。Oracle JDK从java 9开始才有这个包。Java 8从更新262开始才有JFR支持。因此,如果你收到jdk.jfr包的编译错误,不要害怕。

过滤事件

BufferingApplicationStartup有一个固定的事件容量,这些事件被存储在内存中。这意味着大量的事件对应用程序的性能是不健康的。所以过滤掉你感兴趣的事件可能是个好主意。出于这个原因,BufferingApplicationStartup带有一个addFilter(Predicate<StartupStep> filter)方法,该方法需要一个谓词来匹配哪些步骤需要记录。

例如,如果你只需要看到Bean创建的事件,那么设置将如下所示。

@SpringBootApplication
public class ActuatorStartupExampleApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(ActuatorStartupExampleApplication.class);
        BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(2048);

        applicationStartup.addFilter(startupStep -> startupStep.getName().startsWith("spring.beans.instantiate"));
        app.setApplicationStartup(applicationStartup);

        app.run(args);
    }

}
Code language: JavaScript (javascript)

上述过滤将确保只有名称为spring.beans.instantiate的步骤。同样可以通过对/actuator/startup端点做POST请求来确认。你可以进一步添加更多的谓词来缩小步骤列表的范围。FlightRecorderApplicationStartup转发任何步骤数据到.JFR文件,所以应用程序不会受到任何影响。

在生产中使用仪表是没有意义的

让我们说实话吧。如果你是一个为大公司工作的开发者,你可能有一个像Dynatrace或Wily introscope这样的仪表系统。因此,仅仅为了启动性能数据而使用另一个库是不值得的。所以,只为测试环境实施是一个好主意。

即使你选择对你的应用程序进行仪器检测,也有一个问题。实施时需要对应用程序进行代码修改。因为我们不能作弊,为测试和生产做两个构建。在这种情况下,使用一个命令行参数或一个系统属性来有条件地加载适当的启动类型将是有意义的。下面是我的这个实现的版本。

@SpringBootApplication
public class ActuatorStartupExampleApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(ActuatorStartupExampleApplication.class);
        String startupType = System.getProperty("app.startup.implementation.type", "");
        if ("BUFFERING".equals(startupType)) {

            BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(2048);
            applicationStartup.addFilter(startupStep -> startupStep.getName().startsWith("spring.beans.instantiate"));
            app.setApplicationStartup(applicationStartup);

        } else if ("JFR".equals(startupType)) {
            app.setApplicationStartup(new FlightRecorderApplicationStartup());
        } else {
            app.setApplicationStartup(ApplicationStartup.DEFAULT);
        }

        app.run(args);
    }

}
Code language: JavaScript (javascript)

通过上述设置,你可以使用同一个spring boot构建不同的记录实现。

相关文章