1. 概述

在本文中,我们将介绍 Spring Boot Actuator。我们将首先介绍基础知识,然后详细讨论 Spring Boot 2.x 和 1.x 中的可用内容。

我们将学习如何利用响应式编程模型(Reactive Programming Model)在 Spring Boot 2.x 和 WebFlux 中使用、配置和扩展此监视工具。随后,我们将讨论如何在 Boot 1.x 中执行相同的操作。

自 2014 年 4 月起,Spring Boot Actuator 随 Spring Boot 一起发布。随着 Spring Boot 2 的推出,Actuator 进行了重新设计,并添加了新的令人兴奋的 Endpoint。

我们将本指南分为三个主要部分:

2. 什么是 Actuator?

本质上,Actuator 为我们的应用带来了生产就绪功能。

监视应用程序、收集指标、了解流量或数据库状态对于这种依赖性变得微不足道。

该库的主要好处是,我们可以获得生产级工具,而不必自己真正实现这些功能。

Actuator 主要用于公开有关正在运行的应用程序的操作信息——健康状态、指标、信息、转储、环境等。它使用 HTTP Endpoint 或 JMX Bean 使我们能够与其交互。

一旦此依赖关系位于类路径上,便可以立即使用几个 Endpoint。与大多数 Spring 模块一样,我们可以通过多种方式轻松地对其进行配置或扩展。

2.1. 入门

要启用 Spring Boot Actuator,我们只需要将 spring-boot-actuator 依赖项添加到我们的包管理器中即可。在 Maven 中:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

请注意,这与 Boot 版本无关,因为 Spring Boot 材料清单 (BOM) 中已指定版本。

3. Spring Boot 2.x Actuator

在 2.x 中,Actuator 保留了其基本意图,但简化了其模型,扩展了其功能并合并了更好的默认设置。

首先,该版本与技术无关。此外,它通过与应用程序合并来简化其安全模型。

最后,在各种更改中,请务必记住其中一些正在中断。这包括 HTTP 请求和响应以及 Java API。

此外,与旧的读/写模型相比,最新版本现在支持 CRUD 模型。

3.1. 技术支持

在第二个主要版本中,Actuator 现在与技术无关,而在 1.x 中,它与 MVC 关联,因此与 Servlet API 关联。

在 2.x 中,Actuator 定义了其模型,可插入且可扩展,而无需依赖 MVC。

因此,通过这种新模型,我们能够利用 MVC 和 WebFlux 作为基础 Web 技术。

而且,可以通过实现正确的适配器来添加即将到来的技术。

最后,仍然支持 JMX 公开 Endpoint,而无需任何其他代码。

3.2. 重要变化

与以前的版本不同,Actuator 禁用了大多数 Endpoint

因此,默认情况下唯一可用的两个是 /health/info

如果要启用所有这些功能,可以设置 management.endpoints.web.exposure.include=*。或者,我们可以列出应启用的 Endpoint。

现在,Actuator 与常规 App 安全规则共享安全配置。因此,极大地简化了安全模型。

因此,要调整 Actuator 安全性规则,我们可以为 /actuator/** 添加一个条目:

@Bean
public SecurityWebFilterChain securityWebFilterChain(
  ServerHttpSecurity http) {
    return http.authorizeExchange()
      .pathMatchers("/actuator/**").permitAll()
      .anyExchange().authenticated()
      .and().build();
}

我们可以在 全新的 Actuator 官方文档中 找到更多详细信息。

另外,默认情况下,所有 Actuator Endpoint 现在都放在 /actuator 路径下。

与以前的版本相同,我们可以使用新的属性 management.endpoints.web.base-path 来调整此路径。

3.3. 预定义 Endpoint

让我们看一下一些可用的 Endpoint,其中大多数已经在 1.x 中可用。

尽管如此,还是添加了一些 Endpoint,删除了一些 Endpoint,并重构了一些 Endpoint:

  • /auditevents – 列出与安全审核相关的事件,例如用户登录/注销。此外,我们可以在其他字段中按主体或类型进行过滤
  • /beans – 返回 BeanFactory 中所有可用的 bean。与 /auditevents 不同,它不支持过滤
  • /conditions – 以前称为 /autoconfig,围绕自动配置生成条件报告
  • /configprops – 允许我们获取所有 @ConfigurationProperties bean
  • /env – 返回当前环境属性。此外,我们可以检索单个属性
  • /flyway – 提供有关我们的 Flyway 数据库迁移的详细信息
  • /health – 总结我们应用程序的健康状态
  • /heapdump – 从我们的应用程序使用的 JVM 构建并返回堆转储
  • /info – 返回常规信息。它可能是自定义数据、构建信息或有关最新提交的详细信息
  • /liquibase – 行为类似于 /flyway 但针对 Liquibase
  • /logfile – 返回普通的应用程序日志
  • /loggers – 使我们能够查询和修改应用程序的日志记录级别
  • /metrics – 详细说明我们应用程序的指标。这可能包括一般指标和自定义指标
  • /prometheus – 返回与上一个相似的指标,但其格式可以与 Prometheus 服务器一起使用
  • /scheduledtasks – 提供有关我们应用程序中每个计划任务的详细信息
  • /sessions – 列出我们正在使用 Spring Session 的 HTTP 会话
  • /shutdown – 正常关闭应用程序
  • /threaddump – 转储底层 JVM 的线程信息

3.4. 健康指标

就像以前的版本一样,我们可以轻松添加自定义指标。与其他 API 相反,用于创建自定义健康状况终结点的抽象保持不变。但是,添加了新接口 ReactiveHealthIndicator 来实现响应式健康检查

让我们看一个简单的自定义响应式健康检查:

@Component
public class DownstreamServiceHealthIndicator implements ReactiveHealthIndicator {
 
    @Override
    public Mono<Health> health() {
        return checkDownstreamServiceHealth().onErrorResume(
          ex -> Mono.just(new Health.Builder().down(ex).build())
        );
    }
 
    private Mono<Health> checkDownstreamServiceHealth() {
        // we could use WebClient to check health reactively
        return Mono.just(new Health.Builder().up().build());
    }
}

健康指标的一个方便功能是我们可以将它们汇总为层次结构的一部分。 因此,根据前面的示例,我们可以将所有下游服务归为“下游服务”类别。只要可以访问每个嵌套服务,此类别就很正常。

复合健康检查通过 CompositeHealthIndicator 在 1.x 中进行。另外,在 2.x 中,我们可以将 CompositeReactiveHealthIndicator 用作其响应式对象。

与 Spring Boot 1.x 中的 Endpoint 不同,<id>.sensitive 标志已被删除。要隐藏完整的健康报告,我们可以利用新的 management.endpoint.health.show-details。默认情况下,此标志为 false

3.5. Spring Boot 2 中的指标

在 Spring Boot 2.0 中,内部指标已被 Micrometer 支持所取代。 因此,我们可以期待重大的变化。如果我们的应用程序使用的是度量服务,例如 GaugeServiceCounterService,则它们将不再可用。

相反,我们应该直接与 Micrometer 进行交互。在 Spring Boot 2.0 中,我们将获得为我们自动配置的 MeterRegistry 类型的 Bean。

此外,Micrometer 现在已成为 Actuator 依赖项的一部分。因此,只要 Actuator 依赖项在类路径中,我们就应该很好。

此外,我们将从 /metrics Endpoint 获得全新的响应:

{
  "names": [
    "jvm.gc.pause",
    "jvm.buffer.memory.used",
    "jvm.memory.used",
    "jvm.buffer.count",
    "// ..."
  ]
}

正如我们在前面的示例中所观察到的,没有像 1.x 那样的实际指标。

要获取特定指标的实际值,我们现在可以导航到所需指标,即 /actuator/metrics/jvm.gc.pause 并获得详细的响应:

{
  "name": "jvm.gc.pause",
  "measurements": [
    {
      "statistic": "Count",
      "value": 3.0
    },
    {
      "statistic": "TotalTime",
      "value": 7.9E7
    },
    {
      "statistic": "Max",
      "value": 7.9E7
    }
  ],
  "availableTags": [
    {
      "tag": "cause",
      "values": [
        "Metadata GC Threshold",
        "Allocation Failure"
      ]
    },
    {
      "tag": "action",
      "values": [
        "end of minor GC",
        "end of major GC"
      ]
    }
  ]
}

如我们所见,指标现在更加全面。不仅包括不同的值,还包括一些相关的元数据。

3.6. 自定义 /info Endpoint

/info Endpoint 保持不变。和以前一样,我们可以使用 Maven 或 Gradle 各自的依赖项添加 git 详细信息

<dependency>
    <groupId>pl.project13.maven</groupId>
    <artifactId>git-commit-id-plugin</artifactId>
</dependency>

同样,我们也可以使用 Maven 或 Gradle 插件来包含构建信息,包括名称、组和版本

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>build-info</goal>
            </goals>
        </execution>
    </executions>
</plugin>

3.7. 创建自定义 Endpoint

如前所述,我们可以创建自定义 Endpoint。但是,Spring Boot 2 重新设计了实现此目标的方法,以支持与技术无关的新范例。

让我们创建一个 Actuator Endpoint 来查询、启用和禁用应用程序中的功能标志

@Component
@Endpoint(id = "features")
public class FeaturesEndpoint {
 
    private Map<String, Feature> features = new ConcurrentHashMap<>();
 
    @ReadOperation
    public Map<String, Feature> features() {
        return features;
    }
 
    @ReadOperation
    public Feature feature(@Selector String name) {
        return features.get(name);
    }
 
    @WriteOperation
    public void configureFeature(@Selector String name, Feature feature) {
        features.put(name, feature);
    }
 
    @DeleteOperation
    public void deleteFeature(@Selector String name) {
        features.remove(name);
    }
 
    public static class Feature {
        private Boolean enabled;
 
        // [...] getters and setters
    }
 
}

为了获得 Endpoint,我们需要一个 bean。在我们的示例中,我们为此使用 @Component。另外,我们需要使用 @Endpoint 装饰该 bean。

Endpoint 的路径由 @Endpointid 参数确定,在本例中,它将把请求路由到 /actuator/features

准备就绪后,我们可以使用以下方法开始定义操作:

  • @ReadOperation – 它将映射到 HTTP GET
  • @WriteOperation – 它将映射到 HTTP POST
  • @DeleteOperation – 它将映射到 HTTP DELETE

当我们使用应用程序中的上一个 Endpoint 运行该应用程序时,Spring Boot 将对其进行注册。

一种快速的验证方法是检查日志:

[...].WebFluxEndpointHandlerMapping: Mapped "{[/actuator/features/{name}],
  methods=[GET],
  produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features],
  methods=[GET],
  produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features/{name}],
  methods=[POST],
  consumes=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features/{name}],
  methods=[DELETE]}"[...]

在以前的日志中,我们可以看到 WebFlux 如何公开我们的新 Endpoint。我们将切换到 MVC,它将简单地委托该技术,而无需更改任何代码。

此外,对于这种新方法,我们需要牢记一些重要的注意事项:

  • 没有 MVC 依赖关系
  • 之前(sensitive, enabled...)作为方法存在的所有元数据都不再存在。但是,我们可以使用 @Endpoint(id = "features", enableByDefault = false) 启用或禁用 Endpoint。
  • 与 1.x 不同,不再需要扩展给定的接口
  • 与旧的读取/写入模型相反,现在我们可以使用 @DeleteOperation 定义 DELETE 操作

3.8. 扩展现有 Endpoint

假设我们要确保应用程序的生产实例绝不是 SNAPSHOT 版本。我们决定通过更改返回此信息的 Actuator Endpoint 的 HTTP 状态代码(即 /info)来执行此操作。如果我们的应用恰巧是 SNAPSHOT,我们将获得不同的 HTTP 状态代码。

我们可以使用 @EndpointExtension 批注或其更具体的专长 @EndpointWebExtension@EndpointJmxExtension 轻松扩展预定义 Endpoint 的行为

@Component
@EndpointWebExtension(endpoint = InfoEndpoint.class)
public class InfoWebEndpointExtension {
 
    private InfoEndpoint delegate;
 
    // standard constructor
 
    @ReadOperation
    public WebEndpointResponse<Map> info() {
        Map<String, Object> info = this.delegate.info();
        Integer status = getStatus(info);
        return new WebEndpointResponse<>(info, status);
    }
 
    private Integer getStatus(Map<String, Object> info) {
        // return 5xx if this is a snapshot
        return 200;
    }
}

3.9. 启用所有 Endpoint

为了使用 HTTP 访问 Actuator Endpoint,我们需要同时启用和公开它们。默认情况下,除 /shutdown 之外的所有 Endpoint 均处于启用状态。只有 /health/info Endpoint 默认情况下暴露出来。

我们需要添加以下配置以公开所有 Endpoint:

management.endpoints.web.exposure.include=*

要显式启用特定 Endpoint(例如 /shutdown),我们使用:

management.endpoint.shutdown.enabled=true

要公开除一个(例如 /loggers)以外的所有启用的 Endpoint,我们使用:

management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=loggers

4. Spring Boot 1.x Actuator

在 1.x 中,Actuator 遵循 R/W 模型,这意味着我们可以对其进行读取或写入。例如,我们可以检索指标或应用程序的运行状况。另外,我们可以优雅地终止我们的应用程序或更改日志记录配置。

为了使其工作,Actuator 要求 Spring MVC 通过 HTTP 公开其 Endpoint。不支持其他技术。

4.1. Endpoint

在 1.x 中,Actuator 带来了自己的安全模型。它利用了 Spring Security 构造,但是需要与应用程序的其余部分独立配置。

另外,大多数 Endpoint 都是敏感的,这意味着它们不是完全公开的,换句话说,大多数信息将被省略,而少数 Endpoint 不是,例如 /info

以下是 Boot 提供的一些最常见的 Endpoint:

  • /health – 显示应用程序健康信息(通过未经身份验证的连接访问时为简单的“状态”,或通过身份验证时显示为完整的消息详细信息);默认情况下不敏感
  • /info – 显示任意应用程序信息;默认不敏感
  • /metrics – 显示当前应用程序的“指标”信息;默认情况下也很敏感
  • /trace – 显示跟踪信息(默认情况下,最后几个 HTTP 请求)

我们可以 在官方文档中 找到现有 Endpoint 的完整列表。

4.2. 配置现有 Endpoint

我们可以使用以下格式,使用属性来自定义每个 Endpoint:endpoints.[endpoint name].[property to custom]

提供三个属性:

  • id – 将通过 HTTP 访问该 Endpoint
  • enabled – 如果为 true,则可以访问,否则不能访问
  • sensitive – 如果为 true,则需要授权以通过 HTTP 显示关键信息

例如,添加以下属性将自定义 /beans Endpoint:

endpoints.beans.id=springbeans
endpoints.beans.sensitive=false
endpoints.beans.enabled=true

4.3. /health Endpoint

/health Endpoint 用于检查运行的应用程序的运行状况或状态。 监视软件通常会执行此操作,以警告我们正在运行的实例出现故障或由于其他原因而变得不正常。例如,数据库的连接问题、磁盘空间不足…

默认情况下,未经授权的用户只能通过 HTTP 访问时看到状态信息:

{
    "status" : "UP"
}

此健康信息是从在我们的应用程序上下文中配置的、实现了 HealthIndicator 接口的所有 bean 收集的。

HealthIndicator 返回的某些信息本质上是敏感的–但是我们可以配置 endpoints.health.sensitive = false 来显示更多详细信息,例如磁盘空间、消息传递代理连接、自定义检查等等。请注意,这仅适用于 1.5.0 以下的 Spring Boot 版本。对于 1.5.0 及更高版本,我们还应该通过将 management.security.enabled = false 设置为未经授权的访问来禁用安全性。

我们还可以实现自己的自定义运行状况指示器 - 可以收集特定于应用程序的任何类型的自定义运行状况数据,并通过 /health Endpoint 自动将其公开:

@Component("myHealthCheck")
public class HealthCheck implements HealthIndicator {
  
    @Override
    public Health health() {
        int errorCode = check(); // perform some specific health check
        if (errorCode != 0) {
            return Health.down()
              .withDetail("Error Code", errorCode).build();
        }
        return Health.up().build();
    }
     
    public int check() {
        // Our logic to check health
        return 0;
    }
}

输出结果如下所示:

{
    "status" : "DOWN",
    "myHealthCheck" : {
        "status" : "DOWN",
        "Error Code" : 1
     },
     "diskSpace" : {
         "status" : "UP",
         "free" : 209047318528,
         "threshold" : 10485760
     }
}

4.4. /info Endpoint

我们还可以自定义 /info Endpoint 显示的数据,例如:

info.app.name=Spring Sample Application
info.app.description=This is my first spring boot application
info.app.version=1.0.0

以及示例输出:

{
    "app" : {
        "version" : "1.0.0",
        "description" : "This is my first spring boot application",
        "name" : "Spring Sample Application"
    }
}

4.5. /metrics Endpoint

指标终结点发布有关 OS、JVM 以及应用程序级别指标的信息。启用后,我们将获得诸如内存、堆、处理器、线程、已加载类、已卸载类、线程池以及一些 HTTP 指标之类的信息。

以下是该 Endpoint 的输出结果:

{
    "mem" : 193024,
    "mem.free" : 87693,
    "processors" : 4,
    "instance.uptime" : 305027,
    "uptime" : 307077,
    "systemload.average" : 0.11,
    "heap.committed" : 193024,
    "heap.init" : 124928,
    "heap.used" : 105330,
    "heap" : 1764352,
    "threads.peak" : 22,
    "threads.daemon" : 19,
    "threads" : 22,
    "classes" : 5819,
    "classes.loaded" : 5819,
    "classes.unloaded" : 0,
    "gc.ps_scavenge.count" : 7,
    "gc.ps_scavenge.time" : 54,
    "gc.ps_marksweep.count" : 1,
    "gc.ps_marksweep.time" : 44,
    "httpsessions.max" : -1,
    "httpsessions.active" : 0,
    "counter.status.200.root" : 1,
    "gauge.response.root" : 37.0
}

为了收集自定义指标,我们支持“仪表”(即数据的单值快照)和“计数器”(即递增/递减指标)。

让我们在 /metrics Endpoint 中实现我们自己的自定义指标。例如,我们将自定义登录流程以记录成功和失败的登录尝试:

@Service
public class LoginServiceImpl {
 
    private final CounterService counterService;
     
    public LoginServiceImpl(CounterService counterService) {
        this.counterService = counterService;
    }
     
    public boolean login(String userName, char[] password) {
        boolean success;
        if (userName.equals("admin") && "secret".toCharArray().equals(password)) {
            counterService.increment("counter.login.success");
            success = true;
        }
        else {
            counterService.increment("counter.login.failure");
            success = false;
        }
        return success;
    }
}

输出内容如下所示:

{
    "...": "...",
    "counter.login.success" : 105,
    "counter.login.failure" : 12,
    "...": "..."
}

请注意,登录尝试和其他与安全性相关的事件都可以作为审计事件在 Actuator 中直接使用。

4.6. 创建一个新 Endpoint

除了使用 Spring Boot 提供的现有 Endpoint 之外,我们还可以创建一个全新的 Endpoint。

首先,我们需要让新的 Endpoint 实现 Endpoint<T> 接口:

@Component
public class CustomEndpoint implements Endpoint<List<String>> {
     
    @Override
    public String getId() {
        return "customEndpoint";
    }
 
    @Override
    public boolean isEnabled() {
        return true;
    }
 
    @Override
    public boolean isSensitive() {
        return true;
    }
 
    @Override
    public List<String> invoke() {
        // Custom logic to build the output
        List<String> messages = new ArrayList<String>();
        messages.add("This is message 1");
        messages.add("This is message 2");
        return messages;
    }
}

为了访问此新 Endpoint,使用其 ID 对其进行映射,即,可以按 /customEndpoint 对其进行访问。

输出:

[ "This is message 1", "This is message 2" ]

4.7. 进一步定制

为了安全起见,我们可能选择通过非标准端口公开 Actuator Endpoint——可以轻松地使用 management.port 属性进行配置。

同样,正如我们已经提到的,在 1.x 中,Actuator 基于 Spring Security 配置其自己的安全模型,但与应用程序的其余部分无关。
因此,我们可以更改 management.address 属性以限制可以通过网络访问 Endpoint 的位置:

#port used to expose actuator
management.port=8081
 
#CIDR allowed to hit actuator
management.address=127.0.0.1
 
#Whether security should be enabled or disabled altogether
management.security.enabled=false

此外,默认情况下,除 /info 外的所有内置终结点都是敏感的。如果应用程序使用的是 Spring Security,我们可以通过在 application.properties 文件中定义默认的安全属性(用户名、密码和角色)来保护这些 Endpoint:

security.user.name=admin
security.user.password=secret
management.security.role=SUPERUSER

5. 总结

在本文中,我们讨论了 Spring Boot Actuator。我们开始定义 Actuator 的含义及其对我们的作用。


说明: 本文内容主要基于 Spring Boot 1.x 和 2.x 版本。Spring Boot 1.x 已停止维护,2.x 也已进入维护末期,建议新项目参考 Spring Boot 3.x 官方文档,其中 Actuator 的部分配置属性与安全机制可能有所调整。