admin管理员组

文章数量:1032176

Spring Boot Actuator

1. 概述

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

我们将学习如何在 Spring Boot 2.x 和 WebFlux 中使用、配置和扩展这个监控工具,利用反应式编程模型。然后我们将讨论如何使用 Boot 1.x 执行相同的操作。

Spring Boot Actuator 自 2014 年 4 月起与第一个 Spring Boot 版本一起上市。

2. 什么是 Actuator?

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

使用此依赖项,监视我们的应用程序、收集指标、了解流量或数据库的状态变得微不足道。

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

Actuator主要用于公开有关正在运行的应用程序的操作信息 - 运行状况,指标,信息,转储,环境等。它使用 HTTP 端点或 JMX bean 来使我们能够与之交互。

一旦此依赖关系位于类路径上,就可以开箱即用地使用多个端点。与大多数 Spring 模块一样,我们可以通过多种方式轻松配置或扩展它。

2.1. 入门

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

在马文:

代码语言:javascript代码运行次数:0运行复制
<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 仍然支持公开端点,而无需任何其他代码。

3.2. 重要变更

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

因此,默认情况下只有两个可用是/health/info

如果我们想启用所有这些,我们可以设置management.endpoints.web.exposure.include=*。或者,我们可以列出应启用的终结点。

Actuator现在与常规应用程序安全规则共享安全配置,因此安全模型大大简化。

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

代码语言:javascript代码运行次数:0运行复制
@Bean
public SecurityWebFilterChain securityWebFilterChain(
  ServerHttpSecurity http) {
    return http.authorizeExchange()
      .pathMatchers("/actuator/**").permitAll()
      .anyExchange().authenticated()
      .and().build();
}

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

此外,默认情况下,所有 Actuator端点现在都放置在 / Actuator路径下

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

3.3. 预定义端点

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

此外,还添加了一些端点,删除了一些端点,重新构建了一些端点:

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

3.4. Actuator端点的超媒体

Spring Boot添加一个发现端点,该端点返回指向所有可用 Actuator端点的链接。这将有助于发现 Actuator端点及其相应的 URL。

默认情况下,可通过 / Actuator端点访问此发现终结点。

因此,如果我们向此 URL 发送GET请求,它将返回各个端点的 Actuator链接:

代码语言:javascript代码运行次数:0运行复制
{
  "_links": {
    "self": {
      "href": "http://localhost:8080/actuator",
      "templated": false
    },
    "features-arg0": {
      "href": "http://localhost:8080/actuator/features/{arg0}",
      "templated": true
    },
    "features": {
      "href": "http://localhost:8080/actuator/features",
      "templated": false
    },
    "beans": {
      "href": "http://localhost:8080/actuator/beans",
      "templated": false
    },
    "caches-cache": {
      "href": "http://localhost:8080/actuator/caches/{cache}",
      "templated": true
    },
    // truncated
}

如上所示,/ Actuator端点在 _links字段下报告所有可用的 Actuator端点。

此外,如果我们配置custom管理基本路径,则应使用该基本路径作为发现 URL。

例如,如果我们将management.endpoints.web.base-path设置为 /mgmt,那么我们应该向 /mgmt端点发送请求以查看链接列表。

非常有趣的是,当管理基本路径设置为 / 时,将禁用发现终结点以防止与其他映射发生冲突的可能性。

3.5. health指标

就像在以前的版本中一样,我们可以轻松添加custom指标。与其他 API 相反,用于创建custom运行状况终结点的抽象保持不变。但是,添加了一个新的接口,反应式health指示器,以实现反应式health检查。

让我们看一个简单的custom反应式运行状况检查:

代码语言:javascript代码运行次数:0运行复制
@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());
    }
}

health指标的一个方便功能是,我们可以将它们聚合为层次结构的一部分。

因此,按照前面的示例,我们可以将所有下游服务分组到下游服务类别下。只要每个嵌套服务都可以访问,此类别就会正常。

3.6. health组

从 Spring Boot 2.2 开始,我们可以将health指示器组织成组,并将相同的配置应用于所有组成员。

例如,我们可以创建一个名为custom的health组,方法是将其添加到我们的application.properties

代码语言:javascript代码运行次数:0运行复制
management.endpoint.health.group.custom.include=diskSpace,ping

这样,custom组将包含磁盘空间ping运行状况指示器。

现在,如果我们调用 /actuator/health终结点,它会告诉我们 JSON 响应中的新运行状况组:

代码语言:javascript代码运行次数:0运行复制
{"status":"UP","groups":["custom"]}

通过health组,我们可以看到一些health指标的汇总结果。

在这种情况下,如果我们向/ Actuator/health/custom发送请求,则:

代码语言:javascript代码运行次数:0运行复制
{"status":"UP"}

我们可以配置组以通过application.properties 显示更多详细信息:

代码语言:javascript代码运行次数:0运行复制
management.endpoint.health.group.custom.show-components=always
management.endpoint.health.group.custom.show-details=always

现在,如果我们将相同的请求发送到/actuator/health/custom,我们将看到更多详细信息:

代码语言:javascript代码运行次数:0运行复制
{
  "status": "UP",
  "components": {
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 499963170816,
        "free": 91300069376,
        "threshold": 10485760
      }
    },
    "ping": {
      "status": "UP"
    }
  }
}

也可以仅向授权用户显示以下详细信息:

代码语言:javascript代码运行次数:0运行复制
management.endpoint.health.group.custom.show-components=when_authorized
management.endpoint.health.group.custom.show-details=when_authorized

我们还可以有一个custom的状态映射。

例如,它可以返回 207 状态代码,而不是 HTTP 200 OK 响应:

代码语言:javascript代码运行次数:0运行复制
management.endpoint.health.group.custom.status.http-mapping.up=207

在这里,我们告诉 Spring Boot 如果custom组状态为 UP,则返回 207 HTTP 状态代码

3.7. Spring Boot 2 中的指标

在 Spring Boot 2.0 中,内部指标被千分尺支持所取代,因此我们可以期待重大变化。如果我们的应用程序使用的是度量服务或计数器服务等公制服务,则它们将不再可用。

相反,我们希望直接与千分尺进行交互。在 Spring Boot 2.0 中,我们将获得一个自动配置的MeterRegistry类型的 bean。

此外,千分尺现在是致动器依赖关系的一部分,所以只要致动器依赖关系在类路径中,我们就应该很好。

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

代码语言:javascript代码运行次数:0运行复制
{
  "names": [
    "jvm.gc.pause",
    "jvm.buffer.memory.used",
    "jvm.memory.used",
    "jvm.buffer.count",
    // ...
  ]
}Copy

正如我们所看到的,我们在 1.x 中没有实际的指标。

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

代码语言:javascript代码运行次数:0运行复制
{
  "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"
      ]
    }
  ]
}Copy

现在,指标更加全面,不仅包括不同的值,还包括一些关联的元数据。

3.8. custom/信息端点

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

代码语言:javascript代码运行次数:0运行复制
<dependency>
    <groupId>pl.project13.maven</groupId>
    <artifactId>git-commit-id-plugin</artifactId>
</dependency>Copy

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

代码语言:javascript代码运行次数:0运行复制
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>build-info</goal>
            </goals>
        </execution>
    </executions>
</plugin>Copy

3.9. 创建custom端点

如前所述,我们可以创建custom端点。但是,Spring Boot 2 重新设计了实现这一目标的方式,以支持与新技术无关的范式。

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

代码语言:javascript代码运行次数:0运行复制
@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 
    }

}Copy

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

端点的路径由id参数 @Endpoint 确定。在我们的例子中,它会将请求路由到/ Actuator/功能

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

  • @ReadOperation:它将映射到HTTPGET
  • @WriteOperation:它将映射到HTTPPOST。
  • @DeleteOperation:它将映射到 HTTPDELETE。

当我们在应用程序中使用上一个端点运行应用程序时,Spring Boot 将注册它。

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

代码语言:javascript代码运行次数:0运行复制
[...].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]}"[...]Copy

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

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

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

3.10. 扩展现有端点

假设我们希望确保应用程序的生产实例永远不会是SNAPSHOT版本。

我们决定通过更改返回此信息的 Actuator端点的 HTTP 状态代码(即/info)来执行此操作。如果我们的应用恰好是快照,我们将获得不同的HTTP状态代码。

我们可以使用@EndpointExtension注释或其更具体的专用化@EndpointWebExtension或@EndpointJmxExtension轻松扩展预定义端点的行为:

代码语言:javascript代码运行次数:0运行复制
@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;
    }
}Copy

3.11. 启用所有端点

为了使用 HTTP 访问 Actuator端点,我们需要启用和公开它们。

默认情况下,启用所有终结点,但/关闭。默认情况下,仅公开 /运行状况//信息终结点。

我们需要添加以下配置来公开所有终结点:

代码语言:javascript代码运行次数:0运行复制
management.endpoints.web.exposure.include=*Copy

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

代码语言:javascript代码运行次数:0运行复制
management.endpoint.shutdown.enabled=trueCopy

为了公开除一个(例如/loggers)之外的所有已启用的端点,我们使用:

代码语言:javascript代码运行次数:0运行复制
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=loggersCopy

4. Spring Boot 1.x Actuator

在 1.x 中, Actuator遵循读/写模型,这意味着我们可以读取或写入它。

例如,我们可以检索应用程序的指标或运行状况。或者,我们可以正常终止应用或更改日志记录配置。

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

4.1. 端点

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

此外,大多数端点都是敏感的——这意味着它们不是完全公开的,或者大多数信息将被省略——而少数则不是,例如/info

以下是启动提供的开箱即用的一些最常见终结点:

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

我们可以在官方文档上找到现有端点的完整列表。

4.2. 配置现有端点

我们可以使用格式端点custom每个具有属性的端点。终结点名称]。[要custom的属性]。

有三个属性可用:

  • id:通过 HTTP 访问此端点
  • 启用:如果为 true,则可以访问它;否则不会
  • 敏感:如果为 true,则需要授权才能通过 HTTP 显示关键信息

例如,添加以下属性将custom /beans终结点

代码语言:javascript代码运行次数:0运行复制
endpoints.beans.id=springbeans
endpoints.beans.sensitive=false
endpoints.beans.enabled=true

4.3./运行状况终结点

/运行状况终结点用于检查正在运行的应用程序的运行状况或状态。

它通常由监控软件执行,以在正在运行的实例出现故障或由于其他原因(例如,数据库的连接问题、磁盘空间不足等)而变得不health时提醒我们。

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

代码语言:javascript代码运行次数:0运行复制
{
    "status" : "UP"
}

此运行状况信息是从实现应用程序上下文中配置的运行状况指示器接口的所有 Bean 中收集的。

HealthIndicator返回的某些信息本质上是敏感的,但我们可以配置endpoints.health.sensitive=false以公开更详细的信息,如磁盘空间,消息代理连接,custom检查等。

请注意,这仅适用于低于 1.5.0 的 Spring Boot版本。对于 1.5.0 及更高版本,我们还应该通过设置management.security.enabled=false来禁用安全性以进行未经授权的访问。

我们还可以实现自己的custom运行状况指示器,该指示器可以收集特定于应用程序的任何类型的custom运行状况数据,并通过 /health终结点自动公开它:

代码语言:javascript代码运行次数:0运行复制
@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;
    }
}

输出的外观如下:

代码语言:javascript代码运行次数:0运行复制
{
    "status" : "DOWN",
    "myHealthCheck" : {
        "status" : "DOWN",
        "Error Code" : 1
     },
     "diskSpace" : {
         "status" : "UP",
         "free" : 209047318528,
         "threshold" : 10485760
     }
}

4.4./信息端点

我们还可以custom /info端点显示的数据:

代码语言:javascript代码运行次数:0运行复制
info.app.name=Spring Sample Application
info.app.description=This is my first spring boot application
info.app.version=1.0.0

和示例输出:

代码语言:javascript代码运行次数:0运行复制
{
    "app" : {
        "version" : "1.0.0",
        "description" : "This is my first spring boot application",
        "name" : "Spring Sample Application"
    }
}

4.5./指标端点

指标端点发布有关操作系统和 JVM 以及应用程序级指标的信息。启用后,我们将获得诸如内存、堆、处理器、线程、加载的类、卸载的类和线程池等信息以及一些 HTTP 指标。

下面是此终结点的现成输出:

代码语言:javascript代码运行次数:0运行复制
{
    "mem" : 193024,
    "mem.free" : 87693,
    "processors" : 4,
    "instance.uptime" : 305027,
    "uptime" : 307077,
    "systemload.average" : 0.11,
    "heapmitted" : 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
}

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

让我们将自己的custom指标实现到 /metrics终端节点中。

我们将custom登录流程以记录成功和失败的登录尝试:

代码语言:javascript代码运行次数:0运行复制
@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;
    }
}

输出可能如下所示:

代码语言:javascript代码运行次数:0运行复制
{
    ...
    "counter.login.success" : 105,
    "counter.login.failure" : 12,
    ...
}

请注意,登录尝试和其他与安全相关的事件在 Actuator中作为审计事件开箱即用。

4.6. 创建一个新的端点

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

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

代码语言:javascript代码运行次数:0运行复制
@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;
    }
}

为了访问此新终结点,其id用于映射它。换句话说,我们可以练习它hit/customEndpoint

输出:

代码语言:javascript代码运行次数:0运行复制
[ "This is message 1", "This is message 2" ]

4.7. 进一步定制

出于安全目的,我们可能会选择通过非标准端口公开 Actuator端点 —management.port属性可以轻松用于配置它。

另外,正如我们已经提到的,在 1.x 中。 Actuator基于 Spring 安全性配置自己的安全模型,但独立于应用程序的其余部分。

因此,我们可以更改management.address属性来限制可以通过网络访问端点的位置:

代码语言:javascript代码运行次数:0运行复制
#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 安全性,我们可以通过在application.properties文件中定义默认安全属性(用户名、密码和角色)来保护这些端点:

代码语言:javascript代码运行次数:0运行复制
security.user.name=admin
security.user.password=secret
management.security.role=SUPERUSER
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2023-03-13,如有侵权请联系 cloudcommunity@tencent 删除javaspringboot模型配置

Spring Boot Actuator

1. 概述

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

我们将学习如何在 Spring Boot 2.x 和 WebFlux 中使用、配置和扩展这个监控工具,利用反应式编程模型。然后我们将讨论如何使用 Boot 1.x 执行相同的操作。

Spring Boot Actuator 自 2014 年 4 月起与第一个 Spring Boot 版本一起上市。

2. 什么是 Actuator?

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

使用此依赖项,监视我们的应用程序、收集指标、了解流量或数据库的状态变得微不足道。

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

Actuator主要用于公开有关正在运行的应用程序的操作信息 - 运行状况,指标,信息,转储,环境等。它使用 HTTP 端点或 JMX bean 来使我们能够与之交互。

一旦此依赖关系位于类路径上,就可以开箱即用地使用多个端点。与大多数 Spring 模块一样,我们可以通过多种方式轻松配置或扩展它。

2.1. 入门

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

在马文:

代码语言:javascript代码运行次数:0运行复制
<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 仍然支持公开端点,而无需任何其他代码。

3.2. 重要变更

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

因此,默认情况下只有两个可用是/health/info

如果我们想启用所有这些,我们可以设置management.endpoints.web.exposure.include=*。或者,我们可以列出应启用的终结点。

Actuator现在与常规应用程序安全规则共享安全配置,因此安全模型大大简化。

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

代码语言:javascript代码运行次数:0运行复制
@Bean
public SecurityWebFilterChain securityWebFilterChain(
  ServerHttpSecurity http) {
    return http.authorizeExchange()
      .pathMatchers("/actuator/**").permitAll()
      .anyExchange().authenticated()
      .and().build();
}

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

此外,默认情况下,所有 Actuator端点现在都放置在 / Actuator路径下

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

3.3. 预定义端点

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

此外,还添加了一些端点,删除了一些端点,重新构建了一些端点:

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

3.4. Actuator端点的超媒体

Spring Boot添加一个发现端点,该端点返回指向所有可用 Actuator端点的链接。这将有助于发现 Actuator端点及其相应的 URL。

默认情况下,可通过 / Actuator端点访问此发现终结点。

因此,如果我们向此 URL 发送GET请求,它将返回各个端点的 Actuator链接:

代码语言:javascript代码运行次数:0运行复制
{
  "_links": {
    "self": {
      "href": "http://localhost:8080/actuator",
      "templated": false
    },
    "features-arg0": {
      "href": "http://localhost:8080/actuator/features/{arg0}",
      "templated": true
    },
    "features": {
      "href": "http://localhost:8080/actuator/features",
      "templated": false
    },
    "beans": {
      "href": "http://localhost:8080/actuator/beans",
      "templated": false
    },
    "caches-cache": {
      "href": "http://localhost:8080/actuator/caches/{cache}",
      "templated": true
    },
    // truncated
}

如上所示,/ Actuator端点在 _links字段下报告所有可用的 Actuator端点。

此外,如果我们配置custom管理基本路径,则应使用该基本路径作为发现 URL。

例如,如果我们将management.endpoints.web.base-path设置为 /mgmt,那么我们应该向 /mgmt端点发送请求以查看链接列表。

非常有趣的是,当管理基本路径设置为 / 时,将禁用发现终结点以防止与其他映射发生冲突的可能性。

3.5. health指标

就像在以前的版本中一样,我们可以轻松添加custom指标。与其他 API 相反,用于创建custom运行状况终结点的抽象保持不变。但是,添加了一个新的接口,反应式health指示器,以实现反应式health检查。

让我们看一个简单的custom反应式运行状况检查:

代码语言:javascript代码运行次数:0运行复制
@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());
    }
}

health指标的一个方便功能是,我们可以将它们聚合为层次结构的一部分。

因此,按照前面的示例,我们可以将所有下游服务分组到下游服务类别下。只要每个嵌套服务都可以访问,此类别就会正常。

3.6. health组

从 Spring Boot 2.2 开始,我们可以将health指示器组织成组,并将相同的配置应用于所有组成员。

例如,我们可以创建一个名为custom的health组,方法是将其添加到我们的application.properties

代码语言:javascript代码运行次数:0运行复制
management.endpoint.health.group.custom.include=diskSpace,ping

这样,custom组将包含磁盘空间ping运行状况指示器。

现在,如果我们调用 /actuator/health终结点,它会告诉我们 JSON 响应中的新运行状况组:

代码语言:javascript代码运行次数:0运行复制
{"status":"UP","groups":["custom"]}

通过health组,我们可以看到一些health指标的汇总结果。

在这种情况下,如果我们向/ Actuator/health/custom发送请求,则:

代码语言:javascript代码运行次数:0运行复制
{"status":"UP"}

我们可以配置组以通过application.properties 显示更多详细信息:

代码语言:javascript代码运行次数:0运行复制
management.endpoint.health.group.custom.show-components=always
management.endpoint.health.group.custom.show-details=always

现在,如果我们将相同的请求发送到/actuator/health/custom,我们将看到更多详细信息:

代码语言:javascript代码运行次数:0运行复制
{
  "status": "UP",
  "components": {
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 499963170816,
        "free": 91300069376,
        "threshold": 10485760
      }
    },
    "ping": {
      "status": "UP"
    }
  }
}

也可以仅向授权用户显示以下详细信息:

代码语言:javascript代码运行次数:0运行复制
management.endpoint.health.group.custom.show-components=when_authorized
management.endpoint.health.group.custom.show-details=when_authorized

我们还可以有一个custom的状态映射。

例如,它可以返回 207 状态代码,而不是 HTTP 200 OK 响应:

代码语言:javascript代码运行次数:0运行复制
management.endpoint.health.group.custom.status.http-mapping.up=207

在这里,我们告诉 Spring Boot 如果custom组状态为 UP,则返回 207 HTTP 状态代码

3.7. Spring Boot 2 中的指标

在 Spring Boot 2.0 中,内部指标被千分尺支持所取代,因此我们可以期待重大变化。如果我们的应用程序使用的是度量服务或计数器服务等公制服务,则它们将不再可用。

相反,我们希望直接与千分尺进行交互。在 Spring Boot 2.0 中,我们将获得一个自动配置的MeterRegistry类型的 bean。

此外,千分尺现在是致动器依赖关系的一部分,所以只要致动器依赖关系在类路径中,我们就应该很好。

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

代码语言:javascript代码运行次数:0运行复制
{
  "names": [
    "jvm.gc.pause",
    "jvm.buffer.memory.used",
    "jvm.memory.used",
    "jvm.buffer.count",
    // ...
  ]
}Copy

正如我们所看到的,我们在 1.x 中没有实际的指标。

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

代码语言:javascript代码运行次数:0运行复制
{
  "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"
      ]
    }
  ]
}Copy

现在,指标更加全面,不仅包括不同的值,还包括一些关联的元数据。

3.8. custom/信息端点

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

代码语言:javascript代码运行次数:0运行复制
<dependency>
    <groupId>pl.project13.maven</groupId>
    <artifactId>git-commit-id-plugin</artifactId>
</dependency>Copy

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

代码语言:javascript代码运行次数:0运行复制
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>build-info</goal>
            </goals>
        </execution>
    </executions>
</plugin>Copy

3.9. 创建custom端点

如前所述,我们可以创建custom端点。但是,Spring Boot 2 重新设计了实现这一目标的方式,以支持与新技术无关的范式。

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

代码语言:javascript代码运行次数:0运行复制
@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 
    }

}Copy

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

端点的路径由id参数 @Endpoint 确定。在我们的例子中,它会将请求路由到/ Actuator/功能

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

  • @ReadOperation:它将映射到HTTPGET
  • @WriteOperation:它将映射到HTTPPOST。
  • @DeleteOperation:它将映射到 HTTPDELETE。

当我们在应用程序中使用上一个端点运行应用程序时,Spring Boot 将注册它。

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

代码语言:javascript代码运行次数:0运行复制
[...].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]}"[...]Copy

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

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

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

3.10. 扩展现有端点

假设我们希望确保应用程序的生产实例永远不会是SNAPSHOT版本。

我们决定通过更改返回此信息的 Actuator端点的 HTTP 状态代码(即/info)来执行此操作。如果我们的应用恰好是快照,我们将获得不同的HTTP状态代码。

我们可以使用@EndpointExtension注释或其更具体的专用化@EndpointWebExtension或@EndpointJmxExtension轻松扩展预定义端点的行为:

代码语言:javascript代码运行次数:0运行复制
@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;
    }
}Copy

3.11. 启用所有端点

为了使用 HTTP 访问 Actuator端点,我们需要启用和公开它们。

默认情况下,启用所有终结点,但/关闭。默认情况下,仅公开 /运行状况//信息终结点。

我们需要添加以下配置来公开所有终结点:

代码语言:javascript代码运行次数:0运行复制
management.endpoints.web.exposure.include=*Copy

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

代码语言:javascript代码运行次数:0运行复制
management.endpoint.shutdown.enabled=trueCopy

为了公开除一个(例如/loggers)之外的所有已启用的端点,我们使用:

代码语言:javascript代码运行次数:0运行复制
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=loggersCopy

4. Spring Boot 1.x Actuator

在 1.x 中, Actuator遵循读/写模型,这意味着我们可以读取或写入它。

例如,我们可以检索应用程序的指标或运行状况。或者,我们可以正常终止应用或更改日志记录配置。

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

4.1. 端点

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

此外,大多数端点都是敏感的——这意味着它们不是完全公开的,或者大多数信息将被省略——而少数则不是,例如/info

以下是启动提供的开箱即用的一些最常见终结点:

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

我们可以在官方文档上找到现有端点的完整列表。

4.2. 配置现有端点

我们可以使用格式端点custom每个具有属性的端点。终结点名称]。[要custom的属性]。

有三个属性可用:

  • id:通过 HTTP 访问此端点
  • 启用:如果为 true,则可以访问它;否则不会
  • 敏感:如果为 true,则需要授权才能通过 HTTP 显示关键信息

例如,添加以下属性将custom /beans终结点

代码语言:javascript代码运行次数:0运行复制
endpoints.beans.id=springbeans
endpoints.beans.sensitive=false
endpoints.beans.enabled=true

4.3./运行状况终结点

/运行状况终结点用于检查正在运行的应用程序的运行状况或状态。

它通常由监控软件执行,以在正在运行的实例出现故障或由于其他原因(例如,数据库的连接问题、磁盘空间不足等)而变得不health时提醒我们。

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

代码语言:javascript代码运行次数:0运行复制
{
    "status" : "UP"
}

此运行状况信息是从实现应用程序上下文中配置的运行状况指示器接口的所有 Bean 中收集的。

HealthIndicator返回的某些信息本质上是敏感的,但我们可以配置endpoints.health.sensitive=false以公开更详细的信息,如磁盘空间,消息代理连接,custom检查等。

请注意,这仅适用于低于 1.5.0 的 Spring Boot版本。对于 1.5.0 及更高版本,我们还应该通过设置management.security.enabled=false来禁用安全性以进行未经授权的访问。

我们还可以实现自己的custom运行状况指示器,该指示器可以收集特定于应用程序的任何类型的custom运行状况数据,并通过 /health终结点自动公开它:

代码语言:javascript代码运行次数:0运行复制
@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;
    }
}

输出的外观如下:

代码语言:javascript代码运行次数:0运行复制
{
    "status" : "DOWN",
    "myHealthCheck" : {
        "status" : "DOWN",
        "Error Code" : 1
     },
     "diskSpace" : {
         "status" : "UP",
         "free" : 209047318528,
         "threshold" : 10485760
     }
}

4.4./信息端点

我们还可以custom /info端点显示的数据:

代码语言:javascript代码运行次数:0运行复制
info.app.name=Spring Sample Application
info.app.description=This is my first spring boot application
info.app.version=1.0.0

和示例输出:

代码语言:javascript代码运行次数:0运行复制
{
    "app" : {
        "version" : "1.0.0",
        "description" : "This is my first spring boot application",
        "name" : "Spring Sample Application"
    }
}

4.5./指标端点

指标端点发布有关操作系统和 JVM 以及应用程序级指标的信息。启用后,我们将获得诸如内存、堆、处理器、线程、加载的类、卸载的类和线程池等信息以及一些 HTTP 指标。

下面是此终结点的现成输出:

代码语言:javascript代码运行次数:0运行复制
{
    "mem" : 193024,
    "mem.free" : 87693,
    "processors" : 4,
    "instance.uptime" : 305027,
    "uptime" : 307077,
    "systemload.average" : 0.11,
    "heapmitted" : 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
}

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

让我们将自己的custom指标实现到 /metrics终端节点中。

我们将custom登录流程以记录成功和失败的登录尝试:

代码语言:javascript代码运行次数:0运行复制
@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;
    }
}

输出可能如下所示:

代码语言:javascript代码运行次数:0运行复制
{
    ...
    "counter.login.success" : 105,
    "counter.login.failure" : 12,
    ...
}

请注意,登录尝试和其他与安全相关的事件在 Actuator中作为审计事件开箱即用。

4.6. 创建一个新的端点

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

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

代码语言:javascript代码运行次数:0运行复制
@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;
    }
}

为了访问此新终结点,其id用于映射它。换句话说,我们可以练习它hit/customEndpoint

输出:

代码语言:javascript代码运行次数:0运行复制
[ "This is message 1", "This is message 2" ]

4.7. 进一步定制

出于安全目的,我们可能会选择通过非标准端口公开 Actuator端点 —management.port属性可以轻松用于配置它。

另外,正如我们已经提到的,在 1.x 中。 Actuator基于 Spring 安全性配置自己的安全模型,但独立于应用程序的其余部分。

因此,我们可以更改management.address属性来限制可以通过网络访问端点的位置:

代码语言:javascript代码运行次数:0运行复制
#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 安全性,我们可以通过在application.properties文件中定义默认安全属性(用户名、密码和角色)来保护这些端点:

代码语言:javascript代码运行次数:0运行复制
security.user.name=admin
security.user.password=secret
management.security.role=SUPERUSER
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2023-03-13,如有侵权请联系 cloudcommunity@tencent 删除javaspringboot模型配置

本文标签: Spring Boot Actuator