一、概述
spring-boot-actuator 是 spring-boot 周边组件之一,主要是用来查询或监控 spring-boot 项目各种组件、各种维度的度量指标,比如环境变量信息、日志级别、spring bean 信息、组件(redis、mq、db)健康状态等,可以通过 jmx 技术或者 http 技术来使用 actuator,下面主要是通过 http 技术来讲解其使用方法以及自定义 endpoint 端点信息和 health indicator 健康状况指示信息。

二、基本使用
1、添加依赖

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



2、启动项目,并访问地址 http://localhost:8081/actuator
默认会 expose 两个 web endpoint,分别是 health、info

打开 http://localhost:8081/actuator/health,看到的响应如下

说明应用程序是健康的。

在 application.properties 配置文件中添加配置 management.endpoint.health.show-details=always 后再打开该链接,得到的响应如下

可以看到显示的结果变多了,不仅显示了应用的健康状况,也显示了应用中组件的健康状况,比如我的应用中使用了 nacos 注册中心,响应中就多了个 nacosDiscovery 组件。

三、进阶使用
默认情况下,只暴露了两个端点(info endpoint 和 health endpoint),现在我想暴露全部的端点,只需要在 application.properties 配置文件中加上配置 management.endpoints.web.

exposure.include=*,访问 http://localhost:8081/actuator 结果如下
{
     "_links": {
         "self": {
             "href": "http://localhost:8081/actuator",
             "templated": false
         },
         "archaius": {
             "href": "http://localhost:8081/actuator/archaius",
             "templated": false
         },
         "nacosdiscovery": {
             "href": "http://localhost:8081/actuator/nacosdiscovery",
             "templated": false
         },
         "beans": {
             "href": "http://localhost:8081/actuator/beans",
             "templated": false
         },
         "caches-cache": {
             "href": "http://localhost:8081/actuator/caches/{cache}",
             "templated": true
         },
         "caches": {
             "href": "http://localhost:8081/actuator/caches",
             "templated": false
         },
         "health": {
             "href": "http://localhost:8081/actuator/health",
             "templated": false
         },
         "health-path": {
             "href": "http://localhost:8081/actuator/health/{*path}",
             "templated": true
         },
         "info": {
             "href": "http://localhost:8081/actuator/info",
             "templated": false
         },
         "conditions": {
             "href": "http://localhost:8081/actuator/conditions",
             "templated": false
         },
         "configprops": {
             "href": "http://localhost:8081/actuator/configprops",
             "templated": false
         },
         "env-toMatch": {
             "href": "http://localhost:8081/actuator/env/{toMatch}",
             "templated": true
         },
         "env": {
             "href": "http://localhost:8081/actuator/env",
             "templated": false
         },
         "loggers": {
             "href": "http://localhost:8081/actuator/loggers",
             "templated": false
         },
         "loggers-name": {
             "href": "http://localhost:8081/actuator/loggers/{name}",
             "templated": true
         },
         "heapdump": {
             "href": "http://localhost:8081/actuator/heapdump",
             "templated": false
         },
         "threaddump": {
             "href": "http://localhost:8081/actuator/threaddump",
             "templated": false
         },
         "metrics-requiredMetricName": {
             "href": "http://localhost:8081/actuator/metrics/{requiredMetricName}",
             "templated": true
         },
         "metrics": {
             "href": "http://localhost:8081/actuator/metrics",
             "templated": false
         },
         "scheduledtasks": {
             "href": "http://localhost:8081/actuator/scheduledtasks",
             "templated": false
         },
         "mappings": {
             "href": "http://localhost:8081/actuator/mappings",
             "templated": false
         },
         "refresh": {
             "href": "http://localhost:8081/actuator/refresh",
             "templated": false
         },
         "features": {
             "href": "http://localhost:8081/actuator/features",
             "templated": false
         },
         "service-registry": {
             "href": "http://localhost:8081/actuator/service-registry",
             "templated": false
         }
     }
 }

可以发现,多了很多个端点,我们重点关注下 metrics 端点,打开浏览器访问 http://localhost:8081/actuator/metrics 得到的结果如下

{
     "names": [
         "http.server.requests",
         "jvm.buffer.count",
         "jvm.buffer.memory.used",
         "jvm.buffer.total.capacity",
         "jvm.classes.loaded",
         "jvm.classes.unloaded",
         "jvm.gc.live.data.size",
         "jvm.gc.max.data.size",
         "jvm.gc.memory.allocated",
         "jvm.gc.memory.promoted",
         "jvm.gc.pause",
         "jvm.memory.committed",
         "jvm.memory.max",
         "jvm.memory.used",
         "jvm.threads.daemon",
         "jvm.threads.live",
         "jvm.threads.peak",
         "jvm.threads.states",
         "logback.events",
         "process.cpu.usage",
         "process.files.max",
         "process.files.open",
         "process.start.time",
         "process.uptime",
         "system.cpu.count",
         "system.cpu.usage",
         "system.load.average.1m",
         "tomcat.sessions.active.current",
         "tomcat.sessions.active.max",
         "tomcat.sessions.alive.max",
         "tomcat.sessions.created",
         "tomcat.sessions.expired",
         "tomcat.sessions.rejected"
     ]
 }



可以发现,系统中有这么多可以度量的指标,那么该怎么查看这些指标呢?注意到有个 metrics-requiredMetricName 端点,我们访问这个端点,同时加上指标名称,比如访问 http://localhost:8081/actuator/metrics/jvm.memory.max 来查看 JVM 最大内存,响应结果如下

{
     "name": "jvm.memory.max",
     "description": "The maximum amount of memory in bytes that can be used for memory management",
     "baseUnit": "bytes",
     "measurements": [
         {
             "statistic": "VALUE",
             "value": 5421137919
         }
     ],
     "availableTags": [
         {
             "tag": "area",
             "values": [
                 "heap",
                 "nonheap"
             ]
         },
         {
             "tag": "id",
             "values": [
                 "Compressed Class Space",
                 "PS Old Gen",
                 "PS Survivor Space",
                 "Metaspace",
                 "PS Eden Space",
                 "Code Cache"
             ]
         }
     ]
 }

暴露全部端点的同时,也可以排除某几个指定的端点,比如要排除 env 和 beans 端点,就可以在配置文件中加上 management.endpoints.web.exposure.exclude=env,beans。

四、自定义 endpoint 和健康状况
1、自定义 endpoint
自定义 endpoint 时,做好三点即可,一是使该类成为 spring bean,二是加上 @Endpoint 注解,三是灵活运用 @ReadOperation 和 @WriteOperation 注解,自定义的 books endpoint 如下

package org.tunnel.sample.cloud.endpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
 import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
 import org.springframework.boot.actuate.endpoint.annotation.Selector;
 import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
 import org.springframework.stereotype.Component;import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;/**
  * 自定义Endpoint,Endpoint名称为“books”
  *
  * @author debo
  */
 @Component
 @Endpoint(id = "books")
 public class BookEndpoint {    private List<String> books = Stream.of("java", "c++", "python").collect(Collectors.toList());
    /**
      * 使用GET方法访问,访问链接http://localhost:8081/actuator/books
      * 相当于新增了一个端点,端点名:books
      *
      * @return
      */
     @ReadOperation
     public List<String> books() {
         return books;
     }    /**
      * 使用GET方法访问,访问链接http://localhost:8081/actuator/books/${bookName}
      * 相当于新增了一个端点,端点名:books-bookName
      *
      * @param bookName
      * @return
      */
     @ReadOperation
     public String bookDesc(@Selector String bookName) {
         if ("java".equals(bookName)) {
             return "Java是世界上最好的语言";
         }
         return "你是谁?";
     }    /**
      * 使用POST JSON方法访问
      * curl -X POST -H "Content-Type: application/json" http://localhost:8081/actuator/books -d '{"bookName":"钢铁是怎样炼成的","version":3}'
      *
      * @param bookName
      * @param version
      * @return
      */
     @WriteOperation
     public String addBook(String bookName, Integer version) {
         books.add(bookName + version);
         return "成功";
     }
 }



启动项目后,会发现 actuator 主页新增两个 endpoint:books 和 books-bookName,其中 books 端点同时支持 GET 和 POST 请求,POST 请求是由 @WriteOperation 注解支持,books-bookName 端点支持 GET 请求,参数由 path 路径传入

注意:需要在 maven-complier-plugin 插件配置 -parameters 编译参数,否则端点名称不会是 books-bookName,而是 books-arg0。

2、自定义健康状况检查
自定义健康状况检查时,做好两点即可,一是使该类成为 spring bean,二是继承 AbstractHealthIndicator 类,实现 doHealthCheck 方法

package org.tunnel.sample.cloud.endpoint.health;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
 import org.springframework.boot.actuate.health.Health.Builder;
 import org.springframework.stereotype.Component;/**
  * 自定义健康状态检查,如果beanName是myHealthIndicator,那么在health中出现的标识就是“my”
  *
  * @author debo
  * @date 2022-01-26
  */
 @Component
 public class MyHealthIndicator extends AbstractHealthIndicator {
     @Override
     protected void doHealthCheck(Builder builder) throws Exception {
         builder.up();
     }
 }

打开 http://localhost:8081/actuator/health,发现多了个 my 健康状况检查标识

五、几个实用的 endpoint
1、loggers
通过 loggers 端点,可以动态修改项目的日志级别,比如想要修改 ROOT logger 的日志级别,就可以发送 POST 请求 curl -X POST -H "Content-Type: application/json" http://localhost:8081/actuator/loggers/ROOT -d '{"configuredLevel":"DEBUG"}'

2、shutdown
shutdown 端点可以用来远程关闭 spring-boot 应用,比如发送 POST 请求 curl -X POST http://localhost:8081/actuator/shutdown 即可关闭应用。

默认情况下,除了 shutdown endpoint ,其它所有的 endpoint 都是开启(enable)的,也就是说,shutdown endpoint 被 expose 后,还需要手动 enable 才能生效,enable 的格式如 management.endpoint.<id>.enabled ,比如要开启 shutdown endpoint ,需要在配置文件 application.properties 中添加配置 management.endpoint.shutdown.enabled=true。