SpringBoot
提供了应用监控和管理的功能,允许我们可以在系统运行时通过http、JMX、SSH
等其他协议进行相关操作。一般检测系统健康以及各个资源相关信息时,还是比较有用的。目前公司运维会要求各个产品提供相应的健康指标进行系统健康监测,就时通过采用SpringBoot
提供的监控体系完成的。
SpringBoot
监控、管理的端点主要有:
端点名称 | 描述 |
---|---|
actuator | 所有端点的列表信息 |
autoconfig | 系统所有的自动配置 |
beans | 系统中所有的bean信息 |
configprops | 系统中所有的配置属性 |
dump | 当前系统的线程状态信息 |
env | 当前系统的环境信息 |
health | 系统的健康状态信息 |
info | 系统信息 |
metrics | 系统的各项指标信息 |
mappings | 映射路径信息,@RequestMapping |
shutdown | 默认不起作用,通过端点可以关闭系统 |
trace | 追踪信息,依赖最新的http请求 |
虽然SpringBoot
支持多种协议访问和管理这些监控端点,不过最常用的应该还是http,可以通过API直接访问,运维有时会写一些脚本来监控这些API,检测系统各个健康指标,因此这里主要就以http的方式进行学习。
端点配置方式
创建一个SpringBoot项目,并添加spring-boot-starter-web
引用。
org.springframework.boot
spring-boot-starter-web
SpringBoot版本为:
2.2.4.RELEASE
2.0后端点的地址均为
/actuator/**
模式,如/actuator/health
项目创建成功后,直接启动并访问http://localhost:8080/actuator
,将得到如下结果。
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
}
}
}
此时直接访问http://localhost:8080/actuator/info
,将得到空的JSON对象{}
,是因为没有配置任何的站点信息。配置以下信息(信息从POM中获取),这里只配置了站点名称,描述信息以及版本信息。
info:
app:
name: "@project.name@"
description: "@project.description@"
version: "@project.version@"
再次访问http://localhost:8080/actuator/info
,得到如下信息。
{
"app": {
"name": "actuator",
"description": "SpringBoot监控模块测试",
"version": "0.0.1-SNAPSHOT"
}
}
yml
中通过@@
包裹的字符串形式访问项目POM中的配置信息,如上面的name、description、version,按照路径的写法可以访问到POM中所有配置的信息。
properties
文件通过${}
形式访问POM中的配置信息。默认创建项目后,直接配置启动此时显示的原始字符串,并没有读取到POM中的配置信息,需要在POM的
build
节点中添加以下信息
src/main/resources true 此时再次编译启动,将对yml文件的
@@
字符使用POM中的信息进行替换,在编译后或打包后的文件中查看application.yml
文件,发现@project.name
等信息已被替换为真实数据信息。
在之前的列表中提到SpringBoot的端点默认有许多种,但是通过创建项目后检测发现默认只有actuator、health、info
三种。这是因为从2.0后默认将其他的端点都关闭了,可以通过以下方式进行开启。
management:
endpoints:
web:
exposure:
include: "*"
重启项目后,再次访问http://localhost:8080/actuator
,将得到更全的端点访问信息
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"beans": {
"href": "http://localhost:8080/actuator/beans",
"templated": false
},
"caches-cache": {
"href": "http://localhost:8080/actuator/caches/{cache}",
"templated": true
},
"caches": {
"href": "http://localhost:8080/actuator/caches",
"templated": false
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
},
"conditions": {
"href": "http://localhost:8080/actuator/conditions",
"templated": false
},
"configprops": {
"href": "http://localhost:8080/actuator/configprops",
"templated": false
},
"env": {
"href": "http://localhost:8080/actuator/env",
"templated": false
},
"env-toMatch": {
"href": "http://localhost:8080/actuator/env/{toMatch}",
"templated": true
},
"loggers": {
"href": "http://localhost:8080/actuator/loggers",
"templated": false
},
"loggers-name": {
"href": "http://localhost:8080/actuator/loggers/{name}",
"templated": true
},
"heapdump": {
"href": "http://localhost:8080/actuator/heapdump",
"templated": false
},
"threaddump": {
"href": "http://localhost:8080/actuator/threaddump",
"templated": false
},
"metrics": {
"href": "http://localhost:8080/actuator/metrics",
"templated": false
},
"metrics-requiredMetricName": {
"href": "http://localhost:8080/actuator/metrics/{requiredMetricName}",
"templated": true
},
"scheduledtasks": {
"href": "http://localhost:8080/actuator/scheduledtasks",
"templated": false
},
"mappings": {
"href": "http://localhost:8080/actuator/mappings",
"templated": false
}
}
}
为了站点的安全,有时也不会开启所有的端点。如果只有有限的端点要开启,可以通过以下方式配置实现,除了默认的info、health
外,开启了beans
端点。
management:
endpoints:
web:
exposure:
include: [info, health, beans]
再次访问http://localhost:8080/actuator
,此时开启的端点和预期的一致
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"beans": {
"href": "http://localhost:8080/actuator/beans",
"templated": false
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
}
}
}
如果大部分端点要开启,只有少部分端点关闭,可以先开启所有的端点,并关闭需要的端点实现
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
beans:
enabled: false
这里只关闭了beans
端点,再次访问http://localhost:8080/actuator
,发现beans
端点已无法访问。
开启shutdown端点
前面的配置过程中,无论如何配置都无法访问shutdown
端点,如果开启该端点则可以通过API的形式将当前系统关闭,还是比较危险的。
通过以下配置方式开启该端点
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
shutdown:
enabled: true
重启系统,发现shutdown
端点已开启,该端口是POST
请求
返回的消息为Shutting down, bye...
,此时查看系统发现已被关闭。
2020-02-02 23:45:28.657 INFO 16704 --- [ Thread-16] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Process finished with exit code 0
端点的个性化配置
关闭、开启特定端点,开启或关闭所需端点等功能通过前面的使用已经都已完成。除了这些基本的功能外SpringBoot还提供了额外的配置方式,可以完成一些个性化的配置方法。
修改端点访问路径
SpringBoot2默认的访问路径(根路径)为/actuator
,可以通过一些简单的配置实现个性化的根路径访问,主要是通过如下的配置management.endpoints.web.base-path
:
management:
endpoints:
web:
base-path: /my-actuator
重启服务后,通过访问http://localhost:8080/my-actuator
,和之前的/actuator
结果一致。
{
"_links": {
"self": {
"href": "http://localhost:8080/my-actuator",
"templated": false
}...
}
}
修改端点名称
默认的端点都具有一个id,该id即为访问时的Mapping(默认的端点参见之前的表格)。之前修改了端点的访问路径,即完成了查看所有端点列表的id名称(将/actuator
改为/my-actuator
)。其他的端点也可以进行调整,其调整的方法如下management.endpoints.web.path-mapping.[id]
,其中id
为所有默认的端点名称。
management:
endpoints:
web:
path-mapping:
beans: my-beans
上例中将默认的beans
端点修改为了my-beans
,重启后,原有的my-actuator/beans
将无法访问,取代的是my-actuator/my-beans
。
{
"_links": {
"self": {
"href": "http://localhost:8080/my-actuator",
"templated": false
},
"beans": {
"href": "http://localhost:8080/my-actuator/my-beans",
"templated": false
}...
}
}
修改访问端口
通常情况下监控程序和系统主服务的端口是一致的,之前的使用过程中8080
端口即使业务服务的端口也是监控模块的端口,两者共用。在实际工作中,为了系统的安全,毕竟各个端点会暴露很多服务器的内部信息,需要隐藏断点,此时可以考虑业务端口和监控端口分离的配置方式。
其配置过程如下management.server.port
management:
server:
port: 8081
这里将监控端口调整为了8081
,此时再访问http://localhost:8080/my-actuator
将报404
,通过访问http://localhost:8081/my-actuator
,得到如下信息
{
"_links": {
"self": {
"href": "http://localhost:8081/my-actuator",
"templated": false
},
"beans": {
"href": "http://localhost:8081/my-actuator/my-beans",
"templated": false
}...
}
}
在配置端口时,还有一个隐藏的功能,即将端口配置为负数,此时呈现的功能和关闭整个监控断点效果一致。并且配置更方便
management: server: port: -1
将
prot
配置为-1
,将默认关闭所有断点。
开启Health详细信息
默认情况下health
信息都是被隐藏的,只能先是一个up|down
信息,可以通过如下配置开启展示详细信息。
management:
endpoint:
health:
show-details: always
自定义端点进行健康检查
SpringBoot
提供了一系列的默认端点,并且也默认提供了一些健康检查的工具,通过这些端点以及健康检查工具可以完成大部分的监控需求。但总有一些情况下,这些默认的断点和工具无法满足我们的需求,当我们需要对这些特殊需求进行监控时,就可以通过自定义的端点及健康检查工具来满足需求。
在这里描述一个虚拟需求,属于业务级别,而当前系统的最主要功能就是该业务,默认的功能都无法实现健康检查,因此需要通过自定义端点以及自定义健康检查去实现此需求。
现有一个图书销售平台,自研了一个分布式分发存储平台。其具体功能是资源加工工具完成图书加工后将元数据文件、图片文件、图书文件等上传到分发存储平台;分发存储平台完成数据存储,资源加密,以及将此资源分发到子节点的工作。
不关心加工工具的处理过程,也不关心真实分发到子节点的情况(所有的子节点都是分发存储系统,其健康检查过程一致,只要每个系统都按同样的方式进行监控,那么就可以确认整个分布式系统的稳定性),那么就需要通过以下业务健康检查,以确保核心业务是可正常运行的一个状态
分发存储系统内部模拟上传,保证上传及存储流程功能正确性(这里通过上传一个虚拟图书,图书文件为空)
对图书文件的加密流程
分发流程,自身下发流程OK即可
其他的注入消息、数据库、磁盘等监控由
SpirngBoot
默认健康管理流程实现即可,这里只做业务监测。
自定义端点
这里先定义一个新的端点,用于描述业务的健康检查情况,通过该端点的状态就能轻松的判定核心流程是否正确。
在SpringBoot2
中实现一个新的端点比较简单,使用@EndPoint
、@WebEndPoint
、@JmxEndPoint
注解实现一个新的端点。
-
EndPoint
端点可以生成支持http和Jmx两种模式的端点,一般情况下使用该注解即可 -
WebEndPoint
只能生成支持http模式的端点 -
JmxEndPoint
只能生成jmx模式的端点
一旦使用了EndPoint
即可创建一个端点,如@EndPoint(id = 'myHealth')
,则创建了一个新的端点myHealth
,访问时通过/my-actuator/myHealth
进行访问。
此外端点指定的方法,可以使用下列三种方法注解,分别可以实现不同的HttpMethod
。
Operation | Method |
---|---|
@ReadOperation |
Get请求,最常用 |
@WriteOperation |
POST请求,往服务器写入数据 |
@DeleteOperation |
DELETE请求,删除数据 |
断点的创建方法清楚后,也知道了访问的端点配置方式,还有一处需要明确:当一个端点实现一组访问断点时,如何实现。比如/health
断点,该组有两个访问端点:/my-actuator/health
、/my-actuator/health/{*path}
。查看一下HealthEndPoint
@Endpoint(id = "health")
public class HealthEndpoint extends HealthEndpointSupport {
// ...略
@ReadOperation
public HealthComponent health() {
HealthComponent health = health(ApiVersion.V3, EMPTY_PATH);
return (health != null) ? health : DEFAULT_HEALTH;
}
@ReadOperation
public HealthComponent healthForPath(@Selector(match = Match.ALL_REMAINING) String... path) {
return health(ApiVersion.V3, path);
}
// ...略
}
从SpringBoot
内置端点来看,当使用Operation
的三种定义进行注解一个方法,将产生一个端点,当函数无参数时,则为默认的EndPint
,当出现@Selector
时,则为子断点,如上例中的/my-actuator/health/{*path}
。
在实现自定义端点前,先创建一个自检的服务,用于实现需求中的三点程序检查功能。
@Service
@Getter
@Setter
@ToString
public class StorageStatusService {
private String status;
}
该服务最终给为提供一个状态status
,其值为running
(正常)、stoping
(服务异常)。这里暂时不实现自建功能。
创建新的断点/my-actuator/storageStatus
。
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Component;
@Endpoint(id = "storageStatus")
@Component
public class StorageStatusEndPoint {
@Autowired
StorageStatusService service;
@ReadOperation
public String storageStatus() {
return "当前服务状态:" + service.getStatus();
}
@ReadOperation
public String storageStatusForName(@Selector String name) {
return "当前服务:" + name + ",当前服务状态:" + service.getStatus();
}
}
@Endpoint(id = "storageStatus")
指明了端点名为storageStatus
。storageStatus()
方法为默认的端点,为GET请求ReadOperation
。这个例子中只要这一个端点即可,为了演示又增加了一个/storageStatus/{name}
的端点,也是GET
请求。
这里由于没有地方设置StorageStatusService
状态的值,可以在系统启动时给定一个,用于演示,也可以创建一个接口,由外部提供改变,如下所示
@Controller
public class StatusCtrl {
@Autowired
StorageStatusService service;
@GetMapping("/storage/status")
public String status(String status) {
service.setStatus(status);
return "success";
}
}
启动服务,访问http://localhost:8080/my-actuator/storageStatus
# 刚开始没有状态
当前服务状态:null
# 设置状态后,再次访问
当前服务状态:running
# 不同设置值
当前服务状态:stoping
自定义健康检查
helath
端点的所有信息都是从系统中所有的HealthIndicator
中收集并展示出来的。Spring
已经内置了大量的HealthIndicator
,这使得我们不需要自己定义新的HealthIndicator
,只要应用了数据库、缓存、消息等常规服务后,health
端点能够直接给我们提供健康检查。这些内置的HealthIndicator
我们比较常用的主要有
名称 | 描述 |
---|---|
DataSourceHealthIndicator |
数据源连接检测 |
DiskSpaceHealthIndicator |
磁盘低空间检测 |
JmsHealthIndicator |
JMS消息搭理是否正常检测 |
ElasticsearchJestHealthIndicator |
Elasticsearch运行状态检测 |
MongoHealthIndicator |
MongoDb是否正常运行监测 |
RabbitHealthIndicator |
RabbitMq是否正常运行监测 |
RedisHealthIndicator |
Redis是否正常运行监测 |
SolrHealthIndicator |
Solr是否正常运行监测 |
接着上面的需求,我们要实现业务的自检功能,已经有了一个状态自检服务StorageStatusService
,为了创建一个自定义的HealthIndicator
,一种方式是类似内置的对象,集成AbstractHealthIndicator
,还有就是直接实现接口HealthIndicator
。
- 这里接着上述的需求,最终也依据
StorageStatusService
的status状态进行显示健康状况信息,如running
为正常,其他状态为失败。为了更好的完成健康自检,模拟图书上传、加密、分发,并且能够得到错误信息并展示到健康检查中,对StorageStatusService
进行完善。
@Service
@Getter
@Setter
@ToString
public class StorageStatusService {
private String status;
/**
* 错误的点
*/
private String errorPoint = "notRuning";
/**
* 错误信息
*/
private String errorMsg = "系统未正常启动";
/**
* 自检
*/
public void selfChecking(String status, String point) {
this.status = status;
this.errorPoint = point;
if ("running".equalsIgnoreCase(status)) {
return;
}
// 第一步上传自检失败
if ("upload".equalsIgnoreCase(point)) {
this.errorMsg = "图书资源上传失败";
}
if ("encrypt".equalsIgnoreCase(point)) {
this.errorMsg = "图书资源加密失败";
}
if ("distribute".equalsIgnoreCase(point)) {
this.errorMsg = "图书资源分发失败";
}
}
}
这里新增了一个自检方法selfChecking(String status, String point)
,这场情况下应该是定期执行的一个方法,这里为了简单,能够被外部接口调用,因此直接传递了执行的结果状态,以及那个错误点发生错误。并对错误点和错误信息添加了默认值。
如果系统运行正常,则status=running,当有一步自检出错时,则将status置为非running,并设置哪一步出错,显示到健康检查中,给运维人员进行提醒。
- 测试状态变更接口进行调整,能够制定错误点。
@GetMapping("/storage/status")
public String status(String status, String point) {
service.selfChecking(status, point);
return "success";
}
- 创建自定义
HealthIndicator
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class StorageHealthIndicator implements HealthIndicator {
@Autowired
StorageStatusService service;
@Override
public Health health() {
if (!"running".equalsIgnoreCase(service.getStatus())) {
return Health.down().withDetail(service.getErrorPoint(), service.getErrorMsg()).build();
}
return Health.up().build();
}
}
采用继承HealthIndicator
接口的实现方式,重写health()
方法
在health()
方法中,直接检查StorageStatusService
的状态信息,当不为running
时,直接返回down
信息,此时整个系统的健康状态为down
。并将错误点以及错误信息作为详细信息返回。
- 启动服务,进行测试
当系统启动后,由于StorageStatusService
为模拟状态,没有后台程序运行,初始时,状态为null
。此时访问/my-actuator/health
,显示如下信息
{
"status": "DOWN",
"components": {
"storage": {
"status": "DOWN",
"details": {
"notRuning": "系统未正常启动"
}
}
}
}
其中storage
节点描述了自定义的HealthIndicator
,此时显示系统未正常启动
,通过接口启动服务
curl http://localhost:8080/storage/status?status=running
再次访问/my-actuator/health
,此时系统正常运行
{
"status": "UP",
"components": {
"storage": {
"status": "UP"
}
}
}
认为设置自检服务第二步加密失败
curl http://localhost:8080/storage/status?status=stoping&point=encrypt
再次访问/my-actuator/health
,此时系统宕机,且提示加密模块出现问题
{
"status": "DOWN",
"components": {
"storage": {
"status": "DOWN",
"details": {
"encrypt": "图书资源加密失败"
}
}
}
}