优秀的编程知识分享平台

网站首页 > 技术文章 正文

精通Spring Boot 3 : 11. Spring Boot 监控工具 (2)

nanyue 2025-01-14 16:14:29 技术文章 5 ℃

实现自定义监控端点

实现自定义执行器端点非常简单!为了向您展示这一点,让我们为用户应用程序添加一个简单的功能。当发生日志事件时,我们将添加前缀和后缀字符,以便能够立即识别该事件。

首先,打开或创建 LogEventConfig 类,该类将保存我们的功能数据,这里指的是我们将要使用的前缀和后缀字符。请参见列表 11-6。

package com.apress.users.actuator;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class LogEventConfig {
    private Boolean enabled = true;
    private String prefix = ">> ";
    private String postfix = " <<";
}

列表 11-6 源代码:src/main/java/com/apress/users/actuator/LogEventConfig.java

列表 11-6 展示了 LogEventConfig 类。这个简单的 LogEventConfig 类表明,默认的前缀和后缀值分别是>>和<<。如您所见,这非常简单。

接下来,打开或创建 LogEventEndpoint 类。请参阅第 11-7 号列表。

package com.apress.users.actuator;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
@Component
@Endpoint(id="event-config")
public class LogEventEndpoint {
    private LogEventConfig config = new LogEventConfig();
    @ReadOperation
    public LogEventConfig config() {
        return config;
    }
    @WriteOperation
    public void eventConfig(@Nullable Boolean enabled,@Nullable String prefix, @Nullable String postfix) {
        if (enabled != null)
            this.config.setEnabled(enabled);
        if (prefix != null)
            this.config.setPrefix(prefix);
        if (postfix != null)
            this.config.setPostfix(postfix);
    }
    public Boolean isEnable() {
        return config.getEnabled();
    }
}

列表 11-7 源代码:src/main/java/com/apress/users/actuator/LogEventEndpoint.java

LogEventEndpoint 类中包含以下注解:

  • @Endpoint:该注解允许您创建自定义的 actuator 端点。您需要提供一个与其他端点不冲突的 id,这里是 event-config。该注解创建了 /actuator/event-config 端点,并可以通过 Web(HTTP)和 JMX 访问。如果您只需要它用于 Web,可以使用 @WebEndpoint 注解;如果只需要用于 JMX,则使用 @JmxEndpoint。在这种情况下,我们希望它同时适用于两者,因此使用 @Endpoint。
  • @ReadOperation:该注解标识一个方法,该方法将返回状态为 200(OK)的值;如果该方法不返回值,则响应状态为 404(未找到)。此注解可以通过 GET HTTP 方法访问。在这种情况下,我们仅返回 LogEventConfig 对象的值。
  • 此注释标记一个接受简单类型参数的方法;这些参数不能接受自定义对象,因为端点应该是独立的,并且需要经过转换过程。此操作仅支持 HTTP POST 方法。如果此操作返回值,则响应状态为 200(OK);如果不返回值,则响应状态为 204(无内容)。参数使用 @Nullable 注释标记(可以是 @javax.annotation.Nullable 或 @org.springframework.lang.Nullable),因为在 JMX 上下文中,参数默认是必需的,但可以通过添加 @Nullable 使其变为可选。

这个类被标记为@Component,表示它会被 Spring Boot 的自动配置机制识别并作为 Spring bean 进行设置。

虽然我们没有使用它,但有一个@DeleteOperation 注解可以接受 DELETE HTTP 方法。如果返回值,则状态为 200(OK);如果没有返回值,则状态为 204(无内容)。

在我们的例子中,@ReadOperation 注解仅返回一个对象,因此会生成 application/json 的 Content-Type。不过,我们也可以返回一个 org.springframework.core.io.Resource,这样会生成 application/octet-stream 的 Content-Type。

接下来,打开 UserLogs 类,参见第 11-8 。

package com.apress.users.events;
import com.apress.users.actuator.LogEventEndpoint;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.actuate.audit.listener.AuditApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@AllArgsConstructor
@Slf4j
@Component
public class UserLogs {
    private LogEventEndpoint logEventEndpoint;
    @Async
    @EventListener
    void userActiveStatusEventHandler(UserActivatedEvent event){
        if (logEventEndpoint.isEnable())
            log.info("{} User {} active status: {} {}",logEventEndpoint.config().getPrefix(),
                    event.getEmail(),event.isActive(),logEventEndpoint.config().getPostfix());
        else
            log.info("User {} active status: {}",event.getEmail(),event.isActive());
    }
    @Async
    @EventListener
    void userDeletedEventHandler(UserRemovedEvent event){
        if (logEventEndpoint.isEnable())
            log.info("{} User {} DELETED at {} {}",logEventEndpoint.config().getPrefix(),
                    event.getEmail(),event.getRemoved(),logEventEndpoint.config().getPostfix());
        else
            log.info("{} User {} DELETED at {} {}",event.getEmail(),event.getRemoved());
    }
    @EventListener
    public void on(AuditApplicationEvent event) {
        log.info("Audit Event: {}", event);
    }
}

示例 11-8 源代码:src/main/java/com/apress/users/events/UserLogs.java

在 UserLogs 类中,我们使用 LogEventEndpoint 类及其前缀(>> 默认值)和后缀(<< 默认值)值。我们正在验证该端点是否启用,以便添加这些前缀和后缀字符。最后,我们有审计安全监听器。

接下来,请在 application-actuator.properties 文件中启用 /actuator/event-config 功能

management.endpoints.web.exposure.include=health,info,event-config,env,shutdown

该属性通过 ID 启用端点,但请记住,您可以使用*来添加所有端点。我们正在启用事件配置的端点。

接下来,运行它。一旦它启动并运行,您就可以查看新的自定义接口。

curl -s -u admin:admin http://localhost:8080/actuator/event-config | jq .
{
  "enabled": true,
  "prefix": ">> ",
  "postfix": " <<"
}

太好了!那么,如果你添加一个新用户,

curl -XPOST -H "Content-Type: application/json" -d '{"email":"felipe@email.com","name":"Felipe","gravatarUrl":"https://www.gravatar.com/avatar/23bb62a7d0ca63c9a804908e57bf6bd5?d=wavatar","password":"awesome","userRole":["USER","ADMIN"],"active":true}' http://localhost:8080/users

你应该在控制台看到类似这样的输出:

>>  User felipe@email.com active status: true  <<

接下来,我们来更改前缀和后缀字符。我们可以选择使用 HTTP POST 请求或 JMX。执行 HTTP POST 请求就像运行以下命令一样简单:

curl -i -u admin:admin -H"Content-Type: application/json" -XPOST -d'{"prefix":"[ ","postfix":"]"}' http://localhost:8080/actuator/event-config

我们正在将前缀字符更改为 [,后缀字符更改为 ]。您可以通过以下方式来验证这个更改。

curl -s -u admin:admin http://localhost:8080/actuator/event-config | jq .
{
  "enabled": true,
  "prefix": "[ ",
  "postfix": "]"
}

所以,如果我们删除之前的用户的话:

curl -s -u admin:admin -XDELETE http://localhost:8080/users/felipe@email.com

我们应该在控制台输出中看到这一点:

[  User felipe@email.com DELETED at 2023-11-21T11:14:54.639662 ]

很简单,对吧?接下来,我们来看看如何在这种情况下使用 JMX。

通过 JMX 访问自定义端点

Java 管理扩展(JMX)是一项技术,允许您管理和监控 Java 应用程序及其资源。它提供了一种标准方式来展示您正在运行的应用程序的信息(例如性能指标或配置设置),并允许您动态地更改这些设置。可以将其视为一个控制面板,您可以远程访问该面板,以获取应用程序的健康状况信息,并在运行时进行调整。Spring Boot Actuator 利用 JMX 来公开您正在运行的应用程序的操作信息。默认情况下,Actuator 会自动将其端点注册为 JMX MBeans(管理 Bean),使其可以通过 JMX 客户端(如 JConsole)进行访问。 这使您能够远程监控和管理您的应用程序,包括健康检查、指标、配置详情、线程转储等。JMX 基本上充当了 Actuator 的通信通道,提供对您应用程序运行时行为的有价值的洞察和控制。现在,让我们在应用程序中使用 JMX 控制台。在终端中执行以下命令,以打开如图 11-5 所示的窗口:

jconsole

JConsole 是一个符合 JMX 规范的图形界面,主要用于对 JVM 进行仪器化,以便观察在 JVM 上运行的应用程序的性能和资源消耗。


图 11-5 J 控制台

如图 11-5 所示,在“本地进程”字段中选择 com.apress.users.UsersApplication,在“用户名”和“密码”字段中都输入 admin,然后点击“连接”。接下来,你应该会看到类似于图 11-6 的界面,默认显示“概述”选项卡。

图 11-6J 控制台概览标签

选择最后一个标签页 MBeans(管理 Bean)。如图 11-7 所示,在左侧的导航面板中,展开节点 org.springframework.boot ? Endpoint ? Event-config ? Operations,您会看到我们已启用 /event-config 端点,以及读取(config)和写入(eventConfig)操作。

图 11-7 JConsole 的 MBeans 选项卡

点击配置操作后,您将看到关于读取操作的信息、返回类型(Map)等内容,如图 11-8 所示。


图 11-8 JConsole MBeans 选项卡展开至事件配置 ? 操作 ? 配置

然后,如果您在操作调用部分点击配置按钮,您将看到如图 11-9 所示的弹出窗口。


图 11-9 JConsole – MBeans – 事件配置 – 操作 - 配置

返回导航窗格,选择写操作的事件配置。在操作调用部分,将前缀和后缀字段中的“字符串”替换为“**”。请参见图 11-10。

图 11-10 JMX 控制台 - 事件配置 - 操作

接下来,点击左侧的 eventConfig 按钮,这将设置前缀和后缀的新值。您可以在导航窗格中选择配置来查看这些值,或者添加一个新用户。您可以使用与之前相同的命令:

curl -XPOST -H "Content-Type: application/json" -d '{"email":"felipe@email.com","name":"Felipe","gravatarUrl":"https://www.gravatar.com/avatar/23bb62a7d0ca63c9a804908e57bf6bd5?d=wavatar","password":"awesome","userRole":["USER","ADMIN"],"active":true}' http://localhost:8080/users

执行此命令后,您应该在控制台输出中看到如下内容:

** User felipe@email.com active status: true **

所以,这就是你如何与 JMX 和 actuator 端点进行交互的方式。在关闭 JConsole 之前,建议你四处浏览,查看其他端点。

更多关于 Spring Boot Actuator 的配置

本节将介绍 Spring Boot Actuator 提供的若干额外配置选项。

跨域资源共享支持

为了启用跨源资源共享(CORS)的保护,请使用以下属性语法:

management.endpoints.web.cors.allowed-origins=<*|<site1[,site2]>>
management.endpoints.web.cors.allowed-methods=<*|[http-methods[,]]>

比如说:

management.endpoints.web.cors.allowed-origins=*
management.endpoints.web.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS

修改服务器地址、端口和基础路径

出于安全考虑,更改 Spring Boot Actuator 的服务器端口、地址和基本路径是一个不错的做法。请参考以下示例:

management.server.port=8282
management.server.address=127.0.0.1
management.server.base-path=/management

首先,我们将端口更改为 8282,这意味着 Spring Boot Actuator 将开始在该端口提供服务。此外,我们将地址设置为 127.0.0.1(本地主机),这在您有多个网络接口时可能会很有用,因为您可以使用一个特殊地址,仅用于 Spring Boot Actuator 的端点,这些端点不会公开,仅供内部使用。最后,我们将基本路径更改为/management,这意味着访问 Spring Boot Actuator 的地址将是 http://localhost:8282/management/actuator。

如果您想使用这个新配置运行用户应用程序,重要的是将新的基本路径添加到安全设置中,代码如下:

.requestMatchers(mvcMatcherBuilder.pattern("/management/**")).hasRole("ACTUATOR")

在 Spring Boot Actuator 中使用 SSL

您可以使用以下属性为 Spring Boot Actuator 添加 SSL:

management.server.ssl.enabled=true
management.server.ssl.key-store=classpath:keystore.p12
management.server.ssl.key-store-password=changeit
management.server.ssl.key-password=changeit
management.server.ssl.key-store-type=PKCS12
management.server.ssl.key-alias=tomcat

端点配置

一些执行器端点具有读取操作,例如 /actuator/metrics、/actuator/beans、/actuator/env 等,这些端点可以在一段时间内缓存其值。您可以通过以下执行器属性语法来控制这个时间段:

management.endpoint.<id>.cache.time-to-live=<period-of-time>

下面是一个示例:

management.endpoint.beans.cache.time-to-live=10s
management.endpoint.metrics.cache.time-to-live=10s
management.endpoint.env.cache.time-to-live=10s

/actuator/health 的概览

这个端点是 Spring Boot Actuator 中最重要的功能之一,因为它可以帮助我们根据某些子系统的健康状况或其他业务规则判断我们的应用程序是正常运行还是出现故障。如前所述,这个端点默认是启用的,因此当你访问它时,会看到类似这样的内容:

curl -u admin:admin -s http://localhost:8080/actuator/health | jq .
{
  "status": "UP"
}

这对于确保外部系统或客户端的正常运行和正确响应来说是足够的。例如,当您将应用程序部署到云中的 Kubernetes 环境时(在第 13 章中描述),Spring Boot 会自动配置/actuator/health 端点的额外信息,并创建/actuator/health/liveness 和/actuator/health/readiness,这样您就可以轻松地在 YAML 部署文件中公开这些内容。

/actuator/health 端点的一个优点是,它不仅可以提供更多关于应用的信息,还允许你创建自定义健康指标,构建所需的状态并将其反馈。因此,如果你需要更多信息,可以使用以下语法来调整细节:

管理端点健康状态显示详细信息=<从不, 当授权时|总是>

如果您不设置此属性,默认情况下它的值是“从不”。在我们的案例中,由于使用了 Spring Security,我们可以使用 when_authorized 的值。不仅如此,我们还可以通过以下属性语法实现更细致的角色访问控制:

management.endpoint.health.roles=<role-name[,]>

因此,你可以将其添加到 application-actuator.properties 文件中:

management.endpoint.health.show-details=when_authorized
management.endpoint.health.roles=ACTUATOR

如果您重新运行用户应用,并执行以下命令,您将看到类似于这里所示的输出

curl -u admin:admin -s http://localhost:8080/actuator/health | jq .
{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "H2",
        "validationQuery": "isValid()"
      }
    },
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 1000240963584,
        "free": 97917366272,
        "threshold": 10485760,
        "path": "/Users/felipeg/Progs/Books/pro-spring-boot-3rd/java/11-actuator/users/.",
        "exists": true
      }
    },
    "ping": {
      "status": "UP"
    }
  }
}

请注意,这个输出还显示了 db、磁盘空间和 ping 等组件。这些都是健康指标,接下来将进行讨论。

健康指标数据

使用 Spring Boot Actuator 和 /actuator/health 端点的一个好处是,您可以轻松添加多个健康指标,以获取应用程序健康状况的详细信息。在这种情况下,数据库(您可以查看使用的引擎,例如 H2,以及执行的验证查询以确认数据库是否正常)和磁盘空间(您可以查看执行位置、可用空间等详细信息)就是其中的一些。

这些组件获取从健康贡献者注册表中收集的所有健康信息,默认情况下,应用程序上下文中定义的所有健康贡献者实现,以及在我们的案例中,用户应用程序的应用上下文。

其他可用的内置健康指标包括 Cassandra、Couchbase、Elasticsearch、Hazelcast、InfluxDB、JMS、LDAP、邮件、Mongo、Neo4j、Ping、Rabbit 和 Redis。

所以,让我们进行一个实验,看看我们的应用程序是如何运作的。在 build.gradle 文件的依赖部分,添加以下依赖项:

implementation 'org.springframework.boot:spring-boot-starter-amqp'

我们正在将 AMQP 添加到项目中,尽管目前没有任何代码。因此,请重新运行您的应用程序并访问 /actuator/health 端点(您可能会遇到一些连接错误,但暂时可以忽略这些错误):

curl -u admin:admin -s http://localhost:8080/actuator/health | jq .
{
  "status": "DOWN",
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "H2",
        "validationQuery": "isValid()"
      }
    },
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 1000240963584,
        "free": 97769340928,
        "threshold": 10485760,
        "path": "/Users/felipeg/Progs/Books/pro-spring-boot-3rd/java/11-actuator/users/.",
        "exists": true
      }
    },
    "ping": {
      "status": "UP"
    },
    "rabbit": {
      "status": "DOWN",
      "details": {
        "error": "org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused"
      }
    }
  }
}

现在,您已经拥有了兔子组件(基于 amqp 依赖)的详细信息。由于 RabbitMQ 代理未运行,它的状态报告为 DOWN,应用程序的整体健康状态也显示为 DOWN。换句话说,这一状态会影响到您应用程序的主要健康信息。然而,您可以调整状态的呈现顺序,使得应用程序的状态显示为 UP,即使兔子组件的状态是 DOWN。当然,您也可以通过某些业务规则来指示状态为 UP,即使兔子组件处于 DOWN 状态。因此,为了定义这种顺序,我们可以设置以下属性:

management.endpoint.health.status.order=fatal,down,out-of-service,unknown,up

management.endpoint.health.status.order 属性默认定义了状态的顺序,因此如果您省略 down 值,如下所示:

management.endpoint.health.status.order=fatal,out-of-service,unknown,up

然后重新启动应用程序,主状态应该是 UP,尽管兔子组件显示为 DOWN。如果您希望兔子组件的状态显示为 UP,可以通过 Docker 命令启动 RabbitMQ,然后重新启动应用程序:

docker run -d --rm --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq:management-alpine
curl -u admin:admin -s http://localhost:8080/actuator/health | jq .
{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "H2",
        "validationQuery": "isValid()"
      }
    },
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 1000240963584,
        "free": 97315823616,
        "threshold": 10485760,
        "path": "/Users/felipeg/Progs/Books/pro-spring-boot-3rd/java/11-actuator/users/.",
        "exists": true
      }
    },
    "ping": {
      "status": "UP"
    },
    "rabbit": {
      "status": "UP",
      "details": {
        "version": "3.12.9"
      }
    }
  }
}

现在,RabbitMQ 组件的状态显示为正常,并且提供了版本信息。如果需要,你可以停止 RabbitMQ。

docker stop rabbit

此外,您可以将我们在 build.gradle 文件中添加的 amqp 依赖项注释掉,因为我们不再需要它。

如果您想添加自定义健康指标,可以按照下一节中讨论的几种方法来实现。

最近发表
标签列表