本文我们将介绍Spring Boot中一个非常有特色的主题,这个主题就是系统监控。系统监控是Spring Boot中引入的一项全新功能,对于管理应用程序运行时状态非常有用。Spring Boot Actuator组件是承载系统监控功能的组件,通过一系列HTTP端点提供系统监控功能。在本文中,我们将先引入这一组件,并介绍如何使用和扩展Actuator端点。
Actuator组件
初始化Spring Boot系统监控功能需要引入Spring Boot Actuator组件,我们在pom中添加如下Maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
请注意,引入Spring Boot Actuator组件之后,并不是所有端点都是对外暴露的。例如,当我们启动customer-service时,在启动日志里会发现如下日志:
Exposing 2 endpoint(s) beneath base path '/actuator'
当访问http://localhost:8080/actuator端点时会得到如下结果:
{
"_links":{
"self":{
"href":"http://localhost:8080/actuator",
"templated":false
},
"health-path":{
"href":"http://localhost:8080/actuator/health/{*path}",
"templated":true
},
"health":{
"href":"http://localhost:8080/actuator/health",
"templated":false
},
"info":{
"href":"http://localhost:8080/actuator/info",
"templated":false
}
}
}
这种结果就是HATEOAS风格的HTTP响应。如果我们想要看到默认情况下看不到的所有端点,则需要在配置文件中添加如下配置信息:
management:
endpoints:
web:
exposure:
include: "*"
重启应用,这时候就能获取Spring Boot 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
},
"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-requiredMetricName":{
"href":"http://localhost:8080/actuator/metrics/{requiredMetricName}",
"templated":true
},
"metrics":{
"href":"http://localhost:8080/actuator/metrics",
"templated":false
},
"scheduledtasks":{
"href":"http://localhost:8080/actuator/scheduledtasks",
"templated":false
},
"mappings":{
"href":"http://localhost:8080/actuator/mappings",
"templated":false
}
}
}
根据端点所起到的作用,我们可以把Spring Boot Actuator提供的这些原生端点分为如下三大类:
- 应用配置类:获取应用程序中加载的应用配置、环境变量、自动化配置报告等与Spring Boot应用密切相关的配置类信息。
- 度量指标类:获取应用程序运行过程中用于监控的度量指标,比如内存信息、线程池信息、HTTP请求统计等。
- 操作控制类:在原生端点中,只提供了一个用来关闭应用的端点,即/shutdown端点。
Spring Boot Actuator默认提供的端点列表中,部分常见端点的类型、路径和描述参考下表:
类型 | 路径 | 描述 |
应用配置类 | /beans | 该端点用来获取应用程序中所创建的所有JavaBean信息 |
/env | 该端点用来获取应用程序中所有可用的环境属性,包括环境变量、JVM属性、应用配置信息等。 | |
/info | 该端点用来返回一些应用自定义的信息。开发人员可以对其进行扩展,本课时后续会有详细案例。 | |
/mappings | 该端点用来返回所有Controller中RequestMapping所表示的映射信息。 | |
度量指标类 | /metrics | 该端点用来返回当前应用程序的各类重要度量指标,如内存信息、线程信息、垃圾回收信息等。 |
/threaddump | 该端点用来暴露应用程序运行中的线程信息。 | |
/health | 该端点用来获取应用的各类健康指标信息,这些指标信息由 HealthIndicator 的实现类提供。本课时后续同样会有扩展HealthIndicator的代码案例。 | |
/trace | 该端点用来返回基本的HTTP跟踪信息。 | |
操作控制类 | /shutdown | 该端点用来关闭应用程序,要求 endpoints.shutdown.enabled 设置为 true。 |
我们可以访问上表中的各个端点以获取自己感兴趣的监控信息,例如访问actuator/health端点可以得到当前account-service的基本状态,如下所示:
{
"status":"UP"
}
可以看到这个健康状态信息非常简单,有没有办法获取更加详细的信息呢?办法很简单,就是在配置文件中添加如下所示的配置项:
management:
endpoint:
health:
show-details: always
上述配置项指定了针对health这个端点需要显示它的详细信息。这时候,如果我们重启Spring Boot应用程序,并重新访问actuator/health端点,那么就可以获取如下所示的详细信息:
{
"status":"UP",
"components":{
"diskSpace":{
"status":"UP",
"details":{
"total":201649549312,
"free":3434250240,
"threshold":10485760
}
},
"ping":{
"status":"UP"
}
}
}
如果Spring Boot Actuator默认提供的端点信息不能满足需求,我们还可以对其进行修改和扩展。常见实现方案有两种,一种是扩展现有的监控端点,另一种是自定义新的监控端点。本课程会对这两种方案都进行详细介绍,今天我们先来关注如何在现有的监控端点上添加定制化功能。
扩展Actuator端点案例分析
前面介绍到Spring Boot默认暴露了Info和Health端点,它们也是日常开发中最常见的两个端点。接下来,我们来讨论如何对这两个端点进行扩展。
扩展Info端点
Info端点用于暴露Spring Boot应用的自身信息。在Spring Boot内部,它把这部分工作委托给了一系列InfoContributor对象。Info端点会暴露所有InfoContributor 对象所收集的各种信息,Spring Boot包含很多自动配置的InfoContributor对象,常见的InfoContributor及其描述如下表所示。
InfoContributor名称 | 描述 |
EnvironmentInfoContributor | 暴露Environment中key为info的所有key |
GitInfoContributor | 暴露git信息,如果存在git.properties文件 |
BuildInfoContributor | 暴露构建信息,如果存在META-INF/build-info.properties文件 |
以上表中的EnvironmentInfoContributor为例,通过在配置文件中添加格式以“info”作为前缀的配置段,我们就可以定义Info端点暴露的数据。所有在“info”配置段下的属性都将被自动暴露,例如你可以将以下配置信息添加到配置文件application.yml中:
info:
app:
encoding: UTF-8
java:
source: 1.8.0_31
target: 1.8.0_31
现在访问Info端点,就能得到如下的Environment信息:
{
"app":{
"encoding":"UTF-8",
"java":{
"source":"1.8.0_31",
"target":"1.8.0_31"
}
}
}
同时,我们还可以在服务构建时扩展Info属性,而不是硬编码这些值。假设使用Maven,就可以按以下配置重写前面的示例并得到同样的效果:
info:
app:
encoding: @project.build.sourceEncoding@
java:
source: @java.version@
target: @java.version@
更多的时候Spring Boot自身提供的Info端点并不能满足我们的业务需求,这就需要编写自定义的InfoContributor对象。方法也很简单,直接实现InfoContributor接口的contribute()方法即可。例如,我们希望在Info端点中能够暴露该应用的构建时间,就可以采用如下所示的代码示例:
@Component
public class CustomBuildInfoContributor implements InfoContributor {
@Override
public void contribute(Builder builder) {
builder.withDetail("build",
Collections.singletonMap("timestamp", new Date()));
}
}
重新构建应用并访问Info端口,将获取如下信息:
{
"app":{
"encoding":"UTF-8",
"java":{
"source":"1.8.0_31",
"target":"1.8.0_31"
}
},
"build":{
"timestamp":1604307503710
}
}
可以看到CustomBuildInfoContributor为Info端口新增了构建时间属性。
扩展Health端点
Health端点用于检查正在运行的应用程序健康状态。健康状态信息是由HealthIndicator对象从Spring的 ApplicationContext中进行获取。和Info端点一样,Spring Boot内部也提供了一系列HealthIndicator对象,而我们也可以实现定制化。默认情况下,最终的系统状态是由 HealthAggregator根据HealthIndicator的有序列表对每个状态进行排序,常见的HealthIndicator如下表所示:
HealthIndicator名称 | 描述 |
检查Cassandra数据库是否启动。 | |
检查磁盘空间是否足够。 | |
检查是否可以获得连接DataSource。 | |
检查Elasticsearch集群是否启动。 | |
检查InfluxDB服务器是否启动。 | |
检查JMS代理是否启动。 | |
检查邮件服务器是否启动。 | |
检查Mongo数据库是否启动。 | |
检查Neo4j服务器是否启动。 | |
检查RabbitMQ服务器是否启动。 | |
检查Redis服务器是否启动。 | |
检查Solr服务器是否已启动。 |
Health端点信息的丰富程度取决于当下应用程序所处的环境,一个真实的Health端点信息如下所示。通过这些信息,我们可以判断该环境中包含了MySQL数据库:
{
"status":"UP",
"components":{
"db":{
"status":"UP",
"details":{
"database":"MySQL",
"result":1,
"validationQuery":"/* ping */ SELECT 1"
}
},
"diskSpace":{
"status":"UP",
"details":{
"total":201649549312,
"free":3491287040,
"threshold":10485760
}
},
"ping":{
"status":"UP"
}
}
}
现在,我们想在Health端点中暴露customer-service的当前运行时状态。为了进一步明确该服务的状态,我们可以自定义一个CustomerServiceHealthIndicator端点用来专门展示customer-service的状态信息。CustomerServiceHealthIndicator的代码如下所示:
@Component
public class CustomerServiceHealthIndicator implements HealthIndicator{
@Override
public Health health() {
try {
URL url = new URL("http://localhost:8083/health/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
int statusCode = conn.getResponseCode();
if (statusCode >= 200 && statusCode < 300) {
return Health.up().build();
} else {
return Health.down().withDetail("HTTP Status Code", statusCode).build();
}
} catch (IOException e) {
return Health.down(e).build();
}
}
}
我们需要提供health()方法的具体实现并返回一个Health 结果。该Health结果应该包括一个状态,并且可以根据需要添加任何细节信息。
以上代码用一种简单而直接的方式判断配置中心服务“customerservice”是否正在运行。我们构建一个HTTP请求,然后根据HTTP响应得出健康诊断的结论。如果HTTP响应的状态码处于200~300之间,我们就认为该服务正在运行,Health.up().build()方法将返回一种Up响应,如下所示。
{
"status": "UP",
"details": {
"customerservice":{
"status": "UP"
}
…
}
}
如果状态码不是处于这个区间(例如返回的是404代表服务不可用)就返回一个Down响应并给出具体的状态码,如下所示。
{
"status": "DOWN",
"details": {
"customerservice":{
"status": "DOWN",
"details": {
"HTTP Status Code": "404"
}
},
…
}
}
如果HTTP请求直接抛出了异常,我们同样返回一个Down响应,同时把异常信息一起返回,效果如下所示。
{
"status": "DOWN",
"details": {
"customerservice":{
"status": "DOWN",
"details": {
"error": "java.net.ConnectException: Connection refused: connect"
}
},
…
}
}
显然,通过扩展Health端点为我们实时监控系统中各个服务的正常运行状态提供了很好的支持,你可以根据需要构建一系列有用的HealthIndicator实现类并添加报警等监控手段。
自定义Actuator端点案例分析
在日常开发过程中,有时候扩展现有端点并不一定能够满足需求,自定义Spring Boot Actuator监控端点是更灵活的方法。
现在,假设我们需要提供一个监控端点以获取当前系统的用户信息和计算机名称,我们就可以实现一个独立的MySystemEndPoint,代码如下所示:
@Configuration
@Endpoint(id = "mysystem", enableByDefault=true)
public class MySystemEndpoint {
@ReadOperation
public Map<String, Object> getMySystemInfo() {
Map<String,Object> result= new HashMap<>();
Map<String, String> map = System.getenv();
result.put("username",map.get("USERNAME"));
result.put("computername",map.get("COMPUTERNAME"));
return result;
}
}
可看到,MySystemEndpoint通过系统环境变量获取所需监控信息。注意到这里引入了一个新的注解@Endpoint,该注解定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Endpoint {
//端点Id
String id() default "";
//是否默认启动标志位
boolean enableByDefault() default true;
}
@Endpoint注解用于设置端点Id以及是否默认启动的标志位。在案例中,我们分别指定id为“mysystem”,enableByDefault标志为true。事实上,在Actuator中存在一批类似@Endpoint的端点注解。其中被@Endpoint注解的端点可通过JMX和Web应用程序访问。对应的@JmxEndpoint只能通过JMX访问,而@WebEndpoint只能通过Web访问。
然后,我们在示例代码中还看到了一个@ReadOperation注解,该注解作用于方法,用于标识读取数据操作。在Actuator中,除了@ReadOperation注解之外,还有@WriteOperation和@DeleteOperation注解分别对应写入和删除操作。
现在访问http://localhost:8080/actuator/mysystem就能获取如下监控信息。
{
"computername":"LAPTOP-EQB59J5P",
"username":"user"
}
有时候,我们需要对某一个端点传递参数以获取特定的度量信息。在Actuator中,专门提供了一个@Selector注解来标识输入参数,示例代码如下所示:
@Configuration
@Endpoint(id = "account", enableByDefault = true)
public class AccountEndpoint {
@Autowired
private AccountRepository accountRepository;
@ReadOperation
public Map<String, Object> getMySystemInfo(@Selector String arg0) {
Map<String, Object> result = new HashMap<>();
result.put(accountName, accountRepository.findAccountByAccountName(arg0));
return result;
}
}
这段代码的逻辑非常简单,就是根据传入的accountName来获取用户账户信息。请注意,通过@Selector注解,这个时候我们就可以使用http://localhost:8080/actuator/ account/account1这样的端口地址来触发度量操作。