Spring Boot 中的运行状况指示器

1. 概述

Spring Boot 提供了几种不同的方法来检查正在运行的应用程序及其组件的状态和运行状况。在这些方法中,HealthContributorHealthIndicatorAPI是其中两个值得注意的方法。

在本教程中,我们将熟悉这些 API,了解它们的工作原理,并了解如何为它们提供自定义信息。

2. 依赖关系

健康信息贡献者是Spring Boot执行器模块的一部分,因此我们需要适当的Maven 依赖项:

代码语言:javascript代码运行次数:0运行复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

3. 内置健康指标

开箱即用,Spring Boot 会注册许多运行状况指示器来报告特定应用程序方面的运行状况。

其中一些指标几乎总是被注册,例如DiskSpaceHealthIndicatorPingHealthIndicator。前者报告磁盘的当前状态,后者用作应用程序的 ping 端点。

另一方面,Spring Boot 有条件地注册一些指标。也就是说,如果类路径上存在某些依赖项或满足其他一些条件,Spring Boot 也可能注册一些其他HealthIndicator。例如,如果我们使用关系数据库,那么Spring Boot会注册DataSourceHealthIndicator。同样,如果我们碰巧使用 Cassandra 作为我们的数据存储,它将注册CassandraHealthIndicator

为了检查 Spring Boot 应用程序的运行状况,我们可以调用 /actuator/health端点。此终结点将报告所有已注册运行状况指示器的聚合结果。

此外,要查看来自一个特定指标的健康报告,我们可以调用 /actuator/health/{name}endpoint。例如,调用 /actuator/health/diskSpace端点将从DiskSpaceHealthIndicator返回状态报告:

代码语言:javascript代码运行次数:0运行复制
{
  "status": "UP",
  "details": {
    "total": 499963170816,
    "free": 134414831616,
    "threshold": 10485760,
    "exists": true
  }
}

4. 自定义健康指示器

除了内置的,我们还可以注册自定义健康指示器来报告组件或子系统的运行状况。为此,我们所要做的就是将HealthIndicator接口的实现注册为Spring bean。

例如,以下实现随机报告失败:

代码语言:javascript代码运行次数:0运行复制
@Component
public class RandomHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        double chance = ThreadLocalRandom.current().nextDouble();
        Health.Builder status = Health.up();
        if (chance > 0.9) {
            status = Health.down();
        }
        return status.build();
    }
}

根据该指标的健康报告,应用程序应该只有 90% 的时间处于上升状态。在这里,我们使用运行状况构建器来报告运行状况信息。

但是,在响应式应用程序中,我们应该注册一个ReactiveHealthIndicator(反应式健康指标)类型的 bean。反应式 health() 方法返回Mono<Health>而不是简单的Health。除此之外,两种 Web 应用程序类型的其他详细信息是相同的。

4.1. 指标名称

要查看此特定指标的报告,我们可以调用 /actuator/health/random端点。例如,API 响应可能如下所示:

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

/actuator/health/randomURL 中的随机是此指标的标识符。特定运行状况指示器实现的标识符等于不带运行状况指示器后缀的 Bean 名称。 由于 Bean 名称是randomHealthIdenticator,因此随机前缀将是标识符。

使用这个算法,如果我们把 bean 名称更改为,比如说,rand

代码语言:javascript代码运行次数:0运行复制
@Component("rand")
public class RandomHealthIndicator implements HealthIndicator {
    // omitted
}

然后指标标识符将是rand而不是Random的。

4.2. 禁用指标

要禁用特定指标,我们可以将“管理.健康.<indicator_identifier>.启用”配置属性设置为false。例如,如果我们将以下内容添加到我们的application.properties

代码语言:javascript代码运行次数:0运行复制
management.health.random.enabled=false

然后 Spring Boot 将禁用RandomHealthIndicator。要激活此配置属性,我们还应在指标上添加@ConditionalOnEnabledHealthIndicator注释:

代码语言:javascript代码运行次数:0运行复制
@Component
@ConditionalOnEnabledHealthIndicator("random")
public class RandomHealthIndicator implements HealthIndicator { 
    // omitted
}

现在,如果我们调用 /actuator/health/random,Spring Boot 将返回 404 Not Found HTTP 响应:

代码语言:javascript代码运行次数:0运行复制
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = "management.health.random.enabled=false")
class DisabledRandomHealthIndicatorIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void givenADisabledIndicator_whenSendingRequest_thenReturns404() throws Exception {
        mockMvc.perform(get("/actuator/health/random"))
          .andExpect(status().isNotFound());
    }
}

请注意,禁用内置或自定义指标彼此相似。因此,我们也可以将相同的配置应用于内置指标。

4.3. 其他详细信息

除了报告状态之外,我们还可以使用withDetail(键,值)附加其他键值详细信息:

代码语言:javascript代码运行次数:0运行复制
public Health health() {
    double chance = ThreadLocalRandom.current().nextDouble();
    Health.Builder status = Health.up();
    if (chance > 0.9) {
        status = Health.down();
    }

    return status
      .withDetail("chance", chance)
      .withDetail("strategy", "thread-local")
      .build();
}

在这里,我们将向状态报告添加两条信息。此外,我们可以通过将Map<String, Object>传递给withDetails(map)方法来实现同样的事情:

代码语言:javascript代码运行次数:0运行复制
Map<String, Object> details = new HashMap<>();
details.put("chance", chance);
details.put("strategy", "thread-local");
        
return status.withDetails(details).build();

现在,如果我们调用 /actuator/health/random,我们可能会看到类似以下内容:

代码语言:javascript代码运行次数:0运行复制
{
  "status": "DOWN",
  "details": {
    "chance": 0.9883560157173152,
    "strategy": "thread-local"
  }
}

我们也可以通过自动测试来验证此行为:

代码语言:javascript代码运行次数:0运行复制
mockMvc.perform(get("/actuator/health/random"))
  .andExpect(jsonPath("$.status").exists())
  .andExpect(jsonPath("$.details.strategy").value("thread-local"))
  .andExpect(jsonPath("$.details.chance").exists());

有时,在与系统组件(如数据库或磁盘)通信时会发生异常。我们可以使用withException(ex) 方法报告此类异常:

代码语言:javascript代码运行次数:0运行复制
if (chance > 0.9) {
    status.withException(new RuntimeException("Bad luck"));
}

我们还可以将异常传递给我们之前看到的down(ex)方法:

代码语言:javascript代码运行次数:0运行复制
if (chance > 0.9) {
    status = Health.down(new RuntimeException("Bad Luck"));
}

现在,运行状况报告将包含堆栈跟踪:

代码语言:javascript代码运行次数:0运行复制
{
  "status": "DOWN",
  "details": {
    "error": "java.lang.RuntimeException: Bad Luck",
    "chance": 0.9603739107139401,
    "strategy": "thread-local"
  }
}

4.4. 细节曝光

management.endpoint.health.show-details配置属性控制每个运行状况终结点可以公开的详细信息级别。

例如,如果我们将此属性设置为 always,那么 Spring Boot 将始终返回健康报告中的详细信息字段就像上面的例子一样。

另一方面,如果我们将此属性设置为never,那么 Spring Boot 将始终省略输出中的细节。还有一个when_authorized值,该值仅向授权用户公开其他详细信息。当且仅当以下情况下,用户才获得授权:

  • 她已通过身份验证
  • 并且她拥有management.endpoint.health.roles配置属性中指定的角色

4.5. 健康状况

默认情况下,Spring 引导将四个不同的值定义为运行状况:

  • UP —组件或子系统按预期工作
  • DOWN— — 组件不工作
  • OUT_OF_SERVICE— 组件暂时停止服务
  • UNKNOWN— — 组件状态未知

这些状态被声明为公共静态最终实例,而不是 Java 枚举。因此,可以定义我们自己的自定义运行状况状态。为此,我们可以使用status(name)方法:

代码语言:javascript代码运行次数:0运行复制
Health.Builder warning = Health.status("WARNING");

运行状况会影响运行状况终结点的 HTTP 状态代码。默认情况下,Spring 引导映射DOWN,并OUT_OF_SERVICE状态以抛出 503 状态代码。另一方面,UP和任何其他未映射的状态将被转换为 200 OK 状态代码。

要自定义此映射,我们可以将management.endpoint.health.status.http-mapping.<status>configuration 属性设置为所需的 HTTP 状态代码编号:

代码语言:javascript代码运行次数:0运行复制
management.endpoint.health.status.http-mapping.down=500
management.endpoint.health.status.http-mapping.out_of_service=503
management.endpoint.health.status.http-mapping.warning=500

现在,Spring Boot 会将DOWN状态映射到 500,OUT_OF_SERVICE映射到 503,并将WARNING映射到 500 个 HTTP 状态代码:

代码语言:javascript代码运行次数:0运行复制
mockMvc.perform(get("/actuator/health/warning"))
  .andExpect(jsonPath("$.status").value("WARNING"))
  .andExpect(status().isInternalServerError());

类似地,我们可以注册一个HttpCodeStatusMapper类型的 bean 来自定义 HTTP 状态代码映射:

代码语言:javascript代码运行次数:0运行复制
@Component
public class CustomStatusCodeMapper implements HttpCodeStatusMapper {

    @Override
    public int getStatusCode(Status status) {
        if (status == Status.DOWN) {
            return 500;
        }
        
        if (status == Status.OUT_OF_SERVICE) {
            return 503;
        }
        
        if (status == Status.UNKNOWN) {
            return 500;
        }

        return 200;
    }
}

getStatusCode(status)方法将运行状况作为输入,并返回HTTP状态代码作为输出。此外,还可以映射自定义状态实例:

代码语言:javascript代码运行次数:0运行复制
if (status.getCode().equals("WARNING")) {
    return 500;
}

默认情况下,Spring 引导使用默认映射注册此接口的简单实现。SimpleHttpCodeStatusMapper也能够从配置文件中读取映射,正如我们之前看到的。

5. 健康信息与指标

非平凡的应用程序通常包含几个不同的组件。例如,考虑一个使用 Cassandra 作为数据库、Apache Kafka 作为其发布-订阅平台和 Hazelcast 作为其内存数据网格的 Spring Boot 应用程序。

我们应该使用HealthIndicatorss 来查看应用程序是否可以与这些组件通信。如果通信链路出现故障或组件本身关闭或运行缓慢,那么我们应该注意一个不健康的组件。换句话说,这些指标应该用于报告不同组件或子系统的健康状况。

相反,我们应该避免使用健康指标来测量值、计数事件或测量持续时间。这就是我们有指标的原因。简而言之,指标是报告 CPU 使用率、平均负载、堆大小、HTTP 响应分布等的更好工具。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2023-03-13,如有侵权请联系 cloudcommunity@tencent 删除springbootstatus配置java