Hello 「Spring Boot Actuator」 !

337 阅读7分钟

官网:Spring Boot Actuator: Production-ready Features

  • 是什么:actuator( [ˈæktjʊeɪtə],制动器),是 Spring Boot 生产特性的套件之一
  • 做什么:帮助你管理生产环境的应用,可以使用 HTTP,JMX 等管理应用

效果

[root@39b1ab8e279c /]# curl -s -X GET 'http://172.31.96.34:8080/actuator' | jq
{
  "_links": {
    "self": {
      "href": "http://172.31.96.34:8080/actuator",
      "templated": false
    },
    "customer": {
      "href": "http://172.31.96.34:8080/actuator/customer",
      "templated": false
    },
    "beans": {
      "href": "http://172.31.96.34:8080/actuator/beans",
      "templated": false
    },
    "caches-cache": {
      "href": "http://172.31.96.34:8080/actuator/caches/{cache}",
      "templated": true
    },
    "caches": {
      "href": "http://172.31.96.34:8080/actuator/caches",
      "templated": false
    },
    "health": {
      "href": "http://172.31.96.34:8080/actuator/health",
      "templated": false
    },
    "health-path": {
      "href": "http://172.31.96.34:8080/actuator/health/{*path}",
      "templated": true
    },
    "info": {
      "href": "http://172.31.96.34:8080/actuator/info",
      "templated": false
    },
    "conditions": {
      "href": "http://172.31.96.34:8080/actuator/conditions",
      "templated": false
    },
    "configprops": {
      "href": "http://172.31.96.34:8080/actuator/configprops",
      "templated": false
    },
    "env": {
      "href": "http://172.31.96.34:8080/actuator/env",
      "templated": false
    },
    "env-toMatch": {
      "href": "http://172.31.96.34:8080/actuator/env/{toMatch}",
      "templated": true
    },
    "loggers": {
      "href": "http://172.31.96.34:8080/actuator/loggers",
      "templated": false
    },
    "loggers-name": {
      "href": "http://172.31.96.34:8080/actuator/loggers/{name}",
      "templated": true
    },
    "heapdump": {
      "href": "http://172.31.96.34:8080/actuator/heapdump",
      "templated": false
    },
    "threaddump": {
      "href": "http://172.31.96.34:8080/actuator/threaddump",
      "templated": false
    },
    "prometheus": {
      "href": "http://172.31.96.34:8080/actuator/prometheus",
      "templated": false
    },
    "metrics-requiredMetricName": {
      "href": "http://172.31.96.34:8080/actuator/metrics/{requiredMetricName}",
      "templated": true
    },
    "metrics": {
      "href": "http://172.31.96.34:8080/actuator/metrics",
      "templated": false
    },
    "scheduledtasks": {
      "href": "http://172.31.96.34:8080/actuator/scheduledtasks",
      "templated": false
    },
    "mappings": {
      "href": "http://172.31.96.34:8080/actuator/mappings",
      "templated": false
    }
  }
}

Hello Actuator

  1. 环境准备:Spring Boot / Spring Cloud 依赖介绍 - 掘金 (juejin.cn)

  2. 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  1. 配置 Endpoints

Endpoints(端点)是 Actuator 暴露出来用于监控应用和与应用交互的,比如 /health 可以用于查看应用的健康信息

默认的 Endpoints 配置如下(* 代表全部):

PropertyDefault
management.endpoints.jmx.exposure.exclude
management.endpoints.jmx.exposure.include*
management.endpoints.web.exposure.exclude
management.endpoints.web.exposure.includehealth

修改配置暴露全部 Endpoints

application.yml

# Actuator
management:
  endpoints:
    web:
      exposure:
        include: '*'
  1. 编写启动类,启动项目
SpringBootActuatorApp.java

@SpringBootApplication
public class SpringBootActuatorApp {
	public static void main(String[] args) {
		SpringApplication.run(SpringBootActuatorApp.class, args);
	}
}

启动应用,可以看到已经暴露 Endpoint

...
2022-10-31 16:53:38.311  INFO 21580 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 15 endpoint(s) beneath base path '/actuator'
2022-10-31 16:53:38.345  INFO 21580 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
...

访问 http://localhost:8080/actuator/health 即可查看应用健康信息

[root@39b1ab8e279c /]# curl -X GET 'http://172.31.96.34:8080/actuator/health' -s | jq
{
  "status": "UP"
}

常用 Endpoint

这里 是所有可用的 Endpoint

health

应用的健康信息,默认可查看的信息是比较少的,可以通过配置开启查看健康信息详情

application.yml

# Actuator
management:
  endpoints:
    web:
      exposure:
        include: '*'
  # ------------ 展示详细健康信息-------------------
  endpoint:
    health:
      show-details: always
  # -------------------------------

show-details 的可选值如下:

NameDescription
never从不展示
always向所有用户展示
when-authorized只对授权用户展示,授权用户可以通过 management.endpoint.health.roles. 设置

重启之后再次查看,多了硬盘的使用情况和 ping 信息

[root@39b1ab8e279c /]# curl -X GET 'http://172.31.96.34:8080/actuator/health' -s | jq
{
  "status": "UP",
  "components": {
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 258390093824,
        "free": 199666888704,
        "threshold": 10485760,
        "exists": true
      }
    },
    "ping": {
      "status": "UP"
    }
  }
}

此外,我们还可以自定义健康信息

@Component
public class CustomerHealthIndicator extends AbstractHealthIndicator {
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        builder
                // 增加 details,需要在设置 management.endpoint.health.show-details
                .withDetail("availableProcessors", Runtime.getRuntime().availableProcessors())
                // 更改引用状态
                .status(Status.UNKNOWN);
    }
}

重启查看

[root@39b1ab8e279c /]# curl -X GET 'http://172.31.96.34:8080/actuator/health' -s | jq
{
  "status": "UP",
  "components": {
    "customer": {
      "status": "UNKNOWN",
      "details": {
        "availableProcessors": 8
      }
    },
    "diskSpace": {
    //....
  }
}

更多 health 相关

metrics

Actuator 为 Micrometer 提供了依赖管理和自动配置, metrics 支持了对应用的持续监测, 结合监控系统(如 Prometheus)可对系统进行监测

Micrometer 为最流行的监控系统提供了一个简单的仪表客户端外观,允许您在没有供应商锁定的情况下对基于 JVM 的应用程序进行仪表化监控,

metrics 说明了可监测的指标,主要包括:

  • JVM(垃圾收集器/内存/堆)
  • 系统(运行时间、平均负载、处理器的信息)
  • 线程池信息
  • Tomcat 会话信息
  • Spring MVC
  • DataSource
  • Prometheus / Grafana(图表展示,需要添加对应依赖)
[root@39b1ab8e279c /]# curl -X GET 'http://172.31.96.34:8080/actuator/metric' -s | jq
{
  "timestamp": "2022-11-01T04:35:34.709+00:00",
  "status": 404,
  "error": "Not Found",
  "message": "",
  "path": "/actuator/metric"
}
[root@39b1ab8e279c /]# curl -X GET 'http://172.31.96.34:8080/actuator/metrics' -s | jq
{
  "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.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.start.time",
    "process.uptime",
    "system.cpu.count",
    "system.cpu.usage",
    "tomcat.sessions.active.current",
    "tomcat.sessions.active.max",
    "tomcat.sessions.alive.max",
    "tomcat.sessions.created",
    "tomcat.sessions.expired",
    "tomcat.sessions.rejected"
  ]
}

Spring MVC 的监控包括了请求次数,响应时间,最大响应时间等,添加一个 Controller

TestController.java

@RestController
public class TestController {
    @GetMapping("/test")
    public String test(@RequestParam long time) throws InterruptedException {
        TimeUnit.SECONDS.sleep(time);
        return "SUCCESS";
    }
}

重启并发送三次请求

[root@39b1ab8e279c /]# curl -X GET 'http://172.31.96.34:8080/test?time=1'
SUCCESS
[root@39b1ab8e279c /]# curl -X GET 'http://172.31.96.34:8080/test?time=2'
SUCCESS
[root@39b1ab8e279c /]# curl -X GET 'http://172.31.96.34:8080/test?time=3'
SUCCESS

通过 /actuator/prometheus 查看统计结果,该地址暴露的的信息用于 Prometheus 采集,做系统监控

[root@39b1ab8e279c /]# curl -X GET 'http://172.31.96.34:8080/actuator/prometheus'
...
# HELP http_server_requests_seconds  
# TYPE http_server_requests_seconds summary
...
http_server_requests_seconds_count{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/test",} 3.0
http_server_requests_seconds_sum{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/test",} 6.0371463
...
# HELP http_server_requests_seconds_max  
# TYPE http_server_requests_seconds_max gauge
...
http_server_requests_seconds_max{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/test",} 3.0081005
...

其中的指标含义:

  • http_server_requests_seconds_count:应用启动后该 uri 被请求的次数累加
  • http_server_requests_seconds_sum:应用启动后该 uri 响应耗时累加
  • http_server_requests_seconds_max:应用启动后该 uri 响应最大耗时

默认情况下,所有的 uri 都会被统计,可以通过设置 management.metrics.web.server.request.autotime.enabledfalse 关闭全局拦截,然后使用 @Timed 注解对单个 uri 进行统计

TestController.java

@RestController
@Timed
public class TestController {
    @GetMapping("/test")
    @Timed(value = "test", longTask = true, extraTags = {"my-tag", "my-tag-value"})
    public String test(@RequestParam long time) throws InterruptedException {
        //...
    }
}

@Timed 放在类上就会统计类中所有的方法,单独放在方法上只针对该方法统计

更多支持的 metrics,以及 metrics 自定义

loggers

通过 /loggers 可以在运行时查看和修改应用所有日志配置

[root@39b1ab8e279c /]# curl -X GET 'http://172.31.96.34:8080/actuator/loggers' -s | jq
{
  "levels": [
    "OFF",
    "ERROR",
    "WARN",
    "INFO",
    "DEBUG",
    "TRACE"
  ],
  "loggers": {
    "ROOT": {
      "configuredLevel": "INFO",
      "effectiveLevel": "INFO"
    },
    ...
    "org.noahnyy.demo.spring.boot": {
      "configuredLevel": null,
      "effectiveLevel": "INFO"
    },
    "org.noahnyy.demo.spring.boot.actuator": {
      "configuredLevel": null,
      "effectiveLevel": "INFO"
    },
    "org.noahnyy.demo.spring.boot.actuator.CustomerHealthIndicator": {
      "configuredLevel": null,
      "effectiveLevel": "INFO"
    },
    "org.noahnyy.demo.spring.boot.actuator.SpringBootActuatorApp": {
      "configuredLevel": null,
      "effectiveLevel": "INFO"
    },
    ...
  },
  "groups": {
    "web": {
      "configuredLevel": null,
      "members": [
        "org.springframework.core.codec",
        "org.springframework.http",
        "org.springframework.web",
        "org.springframework.boot.actuate.endpoint.web",
        "org.springframework.boot.web.servlet.ServletContextInitializerBeans"
      ]
    },
    "sql": {
      "configuredLevel": null,
      "members": [
        "org.springframework.jdbc.core",
        "org.hibernate.SQL",
        "org.jooq.tools.LoggerListener"
      ]
    }
  }
}

发送 POST 请求临时动态修改日志级别

[root@39b1ab8e279c /]# curl -s -X POST 'http://172.31.96.34:8080/actuator/loggers/ROOT' \
>   -H 'Content-Type: application/json' \
>   -d '{
>   "configuredLevel": "DEBUG"
>    }' 

shutdown

关闭应用,高危操作,默认关闭

info

默认情况下 info 暴露从 InfoContributor beans 收集到的信息,自动装配的 InfoContributors 包括了

NameDescription
EnvironmentInfoContributorExposes any key from the Environment under the info key.
GitInfoContributorExposes git information if a git.properties file is available. 该文件可以通过 maven plugins 自动生成,详情戳 Generate git information
BuildInfoContributorExposes build information if a META-INF/build-info.properties file is available. 该文件可以通过 maven plugins 自动生成,详情戳 Generate build information

也可以通过配置文件自定义 info 信息

application.yml

info:
  app:
    name: '@project.name@'

'@project.name@' 的值在 mven 中设置,在 properties 文件中不需要添加单引号,如果 maven 没有使用 spring-booter-starter-parent,则需要添加

pom.xml

<build>
  <resources>
    <resource>
      <directory>src/main/resources</directory>
      <filtering>true</filtering>
    </resource>
  </resources>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-resources-plugin</artifactId>
      <version>2.7</version>
      <configuration>
        <delimiters>
          <delimiter>@</delimiter>
        </delimiters>
        <useDefaultDelimiters>false</useDefaultDelimiters>
      </configuration>
    </plugin>
  </plugins>
</build>

重启项目

[root@39b1ab8e279c /]# curl -s -X GET 'http://172.31.96.34:8080/actuator/info' | jq
{
  "app": {
    "name": "demo-spring-boot-actuator"
  }
}

自定义 Endpoint

可以通过自定义 Endpoint 暴露自己所需要的监控信息

@Component
@Endpoint(id = "customer")
public class CustomerEndpoint {
    @ReadOperation
    public String read() {
        return "当前时间:" + new Date().toString();
    }
    @WriteOperation
    public String write(String name, int id) {
        return String.format("name: %s, id: %s", name, id);
    }
    @DeleteOperation
    public String del(int id) {
        return "id: " + id;
    }
}

其中包括了读操作 @ReadOperation,写操作 @WriteOperation,删操作 @DeleteOperation,对应的请求方式分别是 GET / POST / DELETE

[root@39b1ab8e279c /]# curl -s -X GET 'http://172.31.96.34:8080/actuator/customer' \
>   -H 'Content-Type: application/json'
当前时间:Tue Nov 08 15:15:30 CST 2022
[root@39b1ab8e279c /]# curl -s -X POST 'http://172.31.96.34:8080/actuator/customer' \
>   -H 'Content-Type: application/json' \
>   -d '{"name": "noah", "id":1}'
name: noah, id: 1
[root@39b1ab8e279c /]# curl -s -X DELETE 'http://172.31.96.34:8080/actuator/customer?id=1' 
id: 1

更多

监控形式

Spring Boot Actuator 提供了两种形式的监控,分别是 HTTP 和 JMX

HTTP

访问 http://localhost:8080/actuator/[endpoint] 即可

JMX

JMX(Java Management Extensions)是 Java 自带的管理工具,通过 JMX,我们可以监控

  • 服务器中的各种资源的使用情况,CPU、内存
  • JVM 内存的使用情况
  • JVM 线程使用情况

命令行输入 jconsole 进入可视化工具

image.png 选择刚启动的应用连接

image.png 可以看到应用资源情况,MBean 中便包含了 Spring 暴露出来的应用信息

image.png

自定义 MBean

我们可以自己实现 Java 提供的接口,创建 MBean

public interface SystemInfoMBean {
    int getCpuCore();

    long getTotalMemory();

    void shutdown();
}
public class SystemInfo implements SystemInfoMBean {
    @Override
    public int getCpuCore() {
        return Runtime.getRuntime().availableProcessors();
    }

    @Override
    public long getTotalMemory() {
        return Runtime.getRuntime().totalMemory();
    }

    @Override
    public void shutdown() {
        System.exit(0);
    }
}

注册 MBean

public class CustomerJMBean {
    public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException, IOException {
        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
        ObjectName objectName = new ObjectName("org.noahnyy.demo.spring.boot.actuator:type=SystemInfo");
        SystemInfo systemInfo = new SystemInfo();
        mBeanServer.registerMBean(systemInfo, objectName);
        // 防止进程结束
        System.in.read();
    }
}

启动

image.png

也可以将 MBean 放在 Spring 生态中

@Component
public class MXBeanRegistrar implements ApplicationContextAware, EnvironmentAware, InitializingBean, DisposableBean {

    private ConfigurableApplicationContext applicationContext;
    private Environment environment = new StandardEnvironment();
    private final ObjectName objectName = new ObjectName("org.noahnyy.demo.spring.boot.actuator:type=SystemInfo");

    public MXBeanRegistrar() throws MalformedObjectNameException {
    }

    @Override
    public void destroy() throws Exception {
        ManagementFactory.getPlatformMBeanServer().unregisterMBean(this.objectName);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        server.registerMBean(new SystemInfo(), this.objectName);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = (ConfigurableApplicationContext) applicationContext;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}

image.png