使用Spring Boot Actuator监控应用

Actuator是Spring Boot的一个附加功能,可以帮助你在应用程序生产环境时监视和管理应用程序。可以使用HTTP的各种请求来监管、审计和收集应用的运行情况。

微服务的特点决定了功能模块的部署是分布式的,大部分功能模块都是运行在不同的机器上,彼此通过服务调用进行交互,前后台的业务流会经过很多个微服务的处理和传递,出现了异常如何快速定位是哪个环节出现了问题。

在这种框架下,微服务的监控显得尤为重要。本文主要结合Spring Boot Actuator,跟大家一起分享微服务Spring Boot Actuator的常见用法,方便我们在日常中对我们的微服务进行监控治理。

初识Actuator

下面,我们可以通过对快速入门中实现的Spring Boot应用增加spring-boot-starter-actuator模块功能,来对它有一个直观的认识。

在现有的Spring Boot应用中引入该模块非常简单,只需要在pom.xmldependencies节点中,新增spring-boot-starter-actuator的依赖即可,具体如下:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

通过增加该依赖之后,重新启动应用。此时,我们可以在控制台中看到如下图所示的输出:

spring-boot-actuator-1

上图显示了一批端点定义,这些端点并非我们自己在程序中创建,而是由spring-boot-starter-actuator模块根据应用依赖和配置自动创建出来的监控和管理端点。通过这些端点,我们可以实时的获取应用的各项监控指标,比如:访问/health端点,我们可以获得如下返回的应用健康信息:

1
2
3
{
"status": "UP"
}

原生端点

通过在快速入门示例中添加spring-boot-starter-actuator模块,我们已经对它有了一个初步的认识。接下来,我们详细介绍一下spring-boot-starter-actuator模块中已经实现的一些原生端点。如果根据端点的作用来说,我们可以原生端点分为三大类:

  • 应用配置类:获取应用程序中加载的应用配置、环境变量、自动化配置报告等与Spring Boot应用密切相关的配置类信息。
  • 度量指标类:获取应用程序运行过程中用于监控的度量指标,比如:内存信息、线程池信息、HTTP请求统计等。
  • 操作控制类:提供了对应用的关闭等操作类功能。
ID HTTP 方法 描述 是否需要鉴权
actuator GET 为其他端点提供“发现页面”。要求Spring HATEOAS在classpath路径上。 需要
auditevents GET 陈列当前应用程序的审计事件信息。 需要
autoconfig GET 展示自动配置信息并且显示所有自动配置候选人以及他们“被不被”应用的原因。 需要
beans GET 显示应用程序中所有Spring bean的完整列表。 需要
configprops GET 显示所有配置信息。 需要
dump GET dump所有线程。 需要
env GET 陈列所有的环境变量。 需要
flyway GET Shows any Flyway database migrations that have been applied. 需要
health GET 显示应用程序运行状况信息 不需要
info GET 显示应用信息。 不需要
loggers GET 显示和修改应用程序中的loggers配置。 需要
liquibase GET 显示已经应用的任何Liquibase数据库迁移。 需要
metrics GET 显示当前应用程序的“指标”信息。 需要
mappings GET 显示所有@RequestMapping的url整理列表。 需要
shutdown POST 关闭应用,要求endpoints.shutdown.enabled设置为true(默认情况下不启用)。 需要
trace GET 显示跟踪信息(默认最后100个HTTP请求)。 需要

下面我们来详细了解一下这三类端点都分别可以为我们提供怎么样的有用信息和强大功能,以及我们如何去扩展和配置它们。

应用配置类

由于Spring Boot为了改善传统Spring应用繁杂的配置内容,采用了包扫描和自动化配置的机制来加载原本集中于xml文件中的各项内容。虽然这样的做法,让我们的代码变得非常简洁,但是整个应用的实例创建和依赖关系等信息都被离散到了各个配置类的注解上,这使得我们分析整个应用中资源和实例的各种关系变得非常的困难。而这类端点就可以帮助我们轻松的获取一系列关于Spring 应用配置内容的详细报告,比如:自动化配置的报告、Bean创建的报告、环境属性的报告等。

/autoconfig

该端点用来获取应用的自动化配置报告,其中包括所有自动化配置的候选项。同时还列出了每个候选项自动化配置的各个先决条件是否满足。所以,该端点可以帮助我们方便的找到一些自动化配置为什么没有生效的具体原因。该报告内容将自动化配置内容分为两部分:

*   `positiveMatches`中返回的是条件匹配成功的自动化配置
*   `negativeMatches`中返回的是条件匹配不成功的自动化配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"positiveMatches": {
"SpringBootAdminClientAutoConfiguration": [{
"condition": "OnWebApplicationCondition",
"message": "@ConditionalOnWebApplication (required) found StandardServletEnvironment"
},
{
"condition": "SpringBootAdminClientEnabledCondition",
"message": "matched"
}
]
...
},
"negativeMatches": {
"CacheStatisticsAutoConfiguration": {
"notMatched": [{
"condition": "OnBeanCondition",
"message": "@ConditionalOnBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) did not find any beans"
}],
"matched": [

]
}
}
...
}

从如上示例中我们可以看到,每个自动化配置候选项中都有一系列的条件,比如上面没有成功匹配的HealthIndicatorAutoConfiguration.DataSourcesHealthIndicatorConfiguration配置,它的先决条件就是需要在工程中包含org.springframework.jdbc.core.JdbcTemplate类,由于我们没有引入相关的依赖,它就不会执行自动化配置内容。所以,当我们发现有一些期望的配置没有生效时,就可以通过该端点来查看没有生效的具体原因。

/beans

该端点用来获取应用上下文中创建的所有Bean。

1
2
3
4
5
6
7
8
9
10
11
12
[{
"context": "wk-xcx:dev:17140",
"parent": null,
"beans": [{
"bean": "wkApiApplication",
"aliases": [],
"scope": "singleton",
"type": "com.peilian.wk.WkApiApplication$$EnhancerBySpringCGLIB$$e2f29167",
"resource": "null",
"dependencies": []
}]
}]

如上示例中,我们可以看到在每个bean中都包含了下面这几个信息:

  • bean:Bean的名称
  • scope:Bean的作用域
  • type:Bean的Java类型
  • reource:class文件的具体路径
  • dependencies:依赖的Bean名称

/configprops

该端点用来获取应用中配置的属性信息报告。从下面该端点返回示例的片段中,我们看到返回了关于该短信的配置信息,prefix属性代表了属性的配置前缀,properties代表了各个属性的名称和值。所以,我们可以通过该报告来看到各个属性的配置路径,比如我们要关闭该端点,就可以通过使用endpoints.configprops.enabled=false来完成设置。

1
2
3
4
5
6
7
8
9
{
"endpoints-org.springframework.boot.actuate.endpoint.EndpointProperties": {
"prefix": "endpoints",
"properties": {
"enabled": true,
"sensitive": null
}
}
}

/env

该端点与/configprops不同,它用来获取应用所有可用的环境属性报告。包括:环境变量、JVM属性、应用的配置配置、命令行中的参数。从下面该端点返回的示例片段中,我们可以看到它不仅返回了应用的配置属性,还返回了系统属性、环境变量等丰富的配置信息,其中也包括了应用还没有没有使用的配置。所以它可以帮助我们方便地看到当前应用可以加载的配置信息,并配合@ConfigurationProperties注解将它们引入到我们的应用程序中来进行使用。另外,为了配置属性的安全,对于一些类似密码等敏感信息,该端点都会进行隐私保护,但是我们需要让属性名中包含:password、secret、key这些关键词,这样该端点在返回它们的时候会使用*来替代实际的属性值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"profiles": [
"dev"
],
"server.ports": {
"local.server.port": 17140
},
"commandLineArgs": {
"server.port": "17140"
},
"servletContextInitParams": {

},
"systemProperties": {
"java.runtime.name": "Java(TM) SE Runtime Environment",
"java.protocol.handler.pkgs": "org.springframework.boot.loader",
"sun.boot.library.path": "/opt/jdk1.8.0_162/jre/lib/amd64",
"java.vm.version": "25.162-b12",
"java.vm.vendor": "Oracle Corporation",
"java.vendor.url": "http://java.oracle.com/",
"path.separator": ":",
...
},
"applicationConfig: [classpath:/application-dev.yml]": {
"server.port": 17140
...
},
}

/mappings

该端点用来返回所有Spring MVC的控制器映射关系报告。从下面的示例片段中,我们可以看该报告的信息与我们在启用Spring MVC的Web应用时输出的日志信息类似,其中bean属性标识了该映射关系的请求处理器,method属性标识了该映射关系的具体处理类和处理函数。

1
2
3
4
5
6
7
8
9
10
11
{
"/webjars/**": {
"bean": "resourceHandlerMapping"
},
"/**": {
"bean": "resourceHandlerMapping"
},
"/**/favicon.ico": {
"bean": "faviconHandlerMapping"
}
}

/info

该端点用来返回一些应用自定义的信息。默认情况下,该端点只会返回一个空的json内容。我们可以在application.properties配置文件中通过info前缀来设置一些属性,比如下面这样:

1
2
3
4
5
{
"name": "wk-xcx",
"version": "1.0.0",
"profile": "dev"
}

度量类指标

上面我们所介绍的应用配置类端点所提供的信息报告在应用启动的时候都已经基本确定了其返回内容,可以说是一个静态报告。而度量指标类端点提供的报告内容则是动态变化的,这些端点提供了应用程序在运行过程中的一些快照信息,比如:内存使用情况、HTTP请求统计、外部资源指标等。这些端点对于我们构建微服务架构中的监控系统非常有帮助,由于Spring Boot应用自身实现了这些端点,所以我们可以很方便地利用它们来收集我们想要的信息,以制定出各种自动化策略。下面,我们就来分别看看这些强大的端点功能。

/metrics

该端点用来返回当前应用的各类重要度量指标,比如:内存信息、线程信息、垃圾回收信息等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
{
"mem": 1653563,
"mem.free": 445910,
"processors": 8,
"instance.uptime": 14159785,
"uptime": 14163058,
"systemload.average": 0.45,
"heap.committed": 1564672,
"heap.init": 1028096,
"heap.used": 1118761,
"heap": 14602240,
"nonheap.committed": 91008,
"nonheap.init": 2496,
"nonheap.used": 88892,
"nonheap": 0,
"threads.peak": 83,
"threads.daemon": 5,
"threads.totalStarted": 905,
"threads": 80,
"classes": 9072,
"classes.loaded": 9072,
"classes.unloaded": 0,
"gc.ps_scavenge.count": 9,
"gc.ps_scavenge.time": 91,
"gc.ps_marksweep.count": 2,
"gc.ps_marksweep.time": 55,
"gauge.response.beans": 28.0,
"gauge.response.mappings": 6.0,
"gauge.response.topic.lecturers.topicId": 33.0,
"gauge.response.topic.page": 47.0,
"gauge.response.autoconfig": 3.0,
"gauge.response.course.comment.list.courseId": 18.0,
"gauge.response.topic.comment.page": 32.0,
"gauge.response.unmapped": 2.0,
"gauge.response.info": 6.0,
"gauge.response.share.miniProgram": 39.0,
"gauge.response.topic.course.topicId": 27.0,
"gauge.response.star-star.favicon.ico": 7.0,
"gauge.response.topic.comment.my": 37.0,
"gauge.response.env": 5.0,
"gauge.response.health.root": 502.0,
"gauge.response.course.content.list.courseId": 35.0,
"gauge.response.topic.detail.topicId": 467.0,
"gauge.response.banner.list": 26.0,
"gauge.response.course.detail.courseId": 56.0,
"gauge.response.topic.comment": 30.0,
"gauge.response.configprops": 80.0,
"gauge.response.order": 52.0,
"counter.status.200.mappings": 1,
"counter.status.200.order": 1,
"counter.status.200.health.root": 979,
"counter.status.200.share.miniProgram": 1,
"counter.status.200.configprops": 1,
"counter.status.200.course.detail.courseId": 1,
"counter.status.200.course.content.list.courseId": 1,
"counter.status.200.autoconfig": 2,
"counter.status.200.topic.course.topicId": 28,
"counter.status.200.banner.list": 30,
"counter.status.200.topic.page": 60,
"counter.status.200.env": 1,
"counter.status.200.course.comment.list.courseId": 1,
"counter.status.200.info": 2,
"counter.status.200.star-star.favicon.ico": 2,
"counter.status.200.beans": 1,
"counter.status.200.topic.comment": 2,
"counter.status.200.topic.lecturers.topicId": 26,
"counter.status.200.topic.comment.page": 35,
"counter.status.403.unmapped": 4,
"counter.status.200.topic.comment.my": 9,
"counter.status.200.topic.detail.topicId": 28
}

从上面的示例中,我们看到有这些重要的度量值:

  • 系统信息:包括处理器数量processors、运行时间uptimeinstance.uptime、系统平均负载systemload.average
  • mem.*:内存概要信息,包括分配给应用的总内存数量以及当前空闲的内存数量。这些信息来自java.lang.Runtime
  • heap.*:堆内存使用情况。这些信息来自java.lang.management.MemoryMXBean接口中getHeapMemoryUsage方法获取的java.lang.management.MemoryUsage
  • nonheap.*:非堆内存使用情况。这些信息来自java.lang.management.MemoryMXBean接口中getNonHeapMemoryUsage方法获取的java.lang.management.MemoryUsage
  • threads.*:线程使用情况,包括线程数、守护线程数(daemon)、线程峰值(peak)等,这些数据均来自java.lang.management.ThreadMXBean
  • classes.*:应用加载和卸载的类统计。这些数据均来自java.lang.management.ClassLoadingMXBean
  • gc.*:垃圾收集器的详细信息,包括垃圾回收次数gc.ps_scavenge.count、垃圾回收消耗时间gc.ps_scavenge.time、标记-清除算法的次数gc.ps_marksweep.count、标记-清除算法的消耗时间gc.ps_marksweep.time。这些数据均来自java.lang.management.GarbageCollectorMXBean
  • httpsessions.*:Tomcat容器的会话使用情况。包括最大会话数httpsessions.max和活跃会话数httpsessions.active。该度量指标信息仅在引入了嵌入式Tomcat作为应用容器的时候才会提供。
  • gauge.*:HTTP请求的性能指标之一,它主要用来反映一个绝对数值。比如上面示例中的gauge.response.hello: 5,它表示上一次hello请求的延迟时间为5毫秒。
  • counter.*:HTTP请求的性能指标之一,它主要作为计数器来使用,记录了增加量和减少量。如上示例中counter.status.200.hello: 11,它代表了hello请求返回200状态的次数为11。

对于gauge.*counter.*的统计,这里有一个特殊的内容请求star-star,它代表了对静态资源的访问。这两类度量指标非常有用,我们不仅可以使用它默认的统计指标,还可以在程序中轻松的增加自定义统计值。只需要通过注入org.springframework.boot.actuate.metrics.CounterServiceorg.springframework.boot.actuate.metrics.GaugeService来实现自定义的统计指标信息。

  • /metrics端点可以提供应用运行状态的完整度量指标报告,这项功能非常的实用,但是对于监控系统中的各项监控功能,它们的监控内容、数据收集频率都有所不同,如果我们每次都通过全量获取报告的方式来收集,略显粗暴。所以,我们还可以通过/metrics/{name}接口来更细粒度的获取度量信息,比如我们可以通过访问/metrics/mem.free来获取当前可用内存数量。

  • /health:该端点在一开始的示例中我们已经使用过了,它用来获取应用的各类健康指标信息。在spring-boot-starter-actuator模块中自带实现了一些常用资源的健康指标检测器。这些检测器都通过HealthIndicator接口实现,并且会根据依赖关系的引入实现自动化装配,比如用于检测磁盘的DiskSpaceHealthIndicator、检测DataSource连接是否可用的DataSourceHealthIndicator等。有时候,我们可能还会用到一些Spring Boot的Starter POMs中还没有封装的产品来进行开发,比如:当使用RocketMQ作为消息代理时,由于没有自动化配置的检测器,所以我们需要自己来实现一个用来采集健康信息的检测器。比如,我们可以在Spring Boot的应用中,为org.springframework.boot.actuate.health.HealthIndicator接口实现一个对RocketMQ的检测器类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Component
    public class RocketMQHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
    int errorCode = check();
    if (errorCode != 0) {
    return Health.down().withDetail("Error Code", errorCode).build();
    }
    return Health.up().build();
    }

    private int check() {
    // 对监控对象的检测操作
    return 1;
    }
    }

通过重写health()函数来实现健康检查,返回的Heath对象中,共有两项内容,一个是状态信息,除了该示例中的UPDOWN之外,还有UNKNOWNOUT_OF_SERVICE,可以根据需要来实现返回;还有一个详细信息,采用Map的方式存储,在这里通过withDetail函数,注入了一个Error Code信息,我们也可以填入一下其他信息,比如,检测对象的IP地址、端口等。重新启动应用,并访问/health接口,我们在返回的JSON字符串中,将会包含了如下信息:

1
2
3
"rocketMQ": {
"status": "UP"
}

/dump

该端点用来暴露程序运行中的线程信息。它使用java.lang.management.ThreadMXBeandumpAllThreads方法来返回所有含有同步信息的活动线程详情。

/trace

该端点用来返回基本的HTTP跟踪信息。默认情况下,跟踪信息的存储采用org.springframework.boot.actuate.trace.InMemoryTraceRepository实现的内存方式,始终保留最近的100条请求记录。它记录的内容格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[
{
"timestamp": 1528117084553,
"info": {
"method": "GET",
"path": "/health/",
"headers": {
"request": {
"Accept": "application/json",
"Connection": "Keep-Alive",
"User-Agent": "Apache-HttpClient/4.5.5 (Java/1.8.0_162)",
"Host": "172.30.15.3:17140",
"Accept-Encoding": "gzip,deflate"
},
"response": {
"Transfer-Encoding": "chunked",
"Connection": "keep-alive",
"X-Application-Context": "wk-xcx:dev:17140",
"Date": "Mon, 04 Jun 2018 12:58:04 GMT",
"Content-Type": "application/json;charset=UTF-8",
"status": "200"
}
},
"timeTaken": "443"
}
}
...
]

操作控制类

/shutdown

仔细的读者可能会发现,我们在“初识Actuator”时运行示例的控制台中输出的所有监控端点,已经在介绍应用配置类端点和度量指标类端点时都讲解完了。那么还有哪些是操作控制类端点呢?实际上,由于之前介绍的所有端点都是用来反映应用自身的属性或是运行中的状态,相对于操作控制类端点没有那么敏感,所以他们默认都是启用的。而操作控制类端点拥有更强大的控制能力,如果要使用它们的话,需要通过属性来配置开启。

在原生端点中,只提供了一个用来关闭应用的端点:/shutdown。我们可以通过如下配置开启它:

1
endpoints.shutdown.enabled=true

在配置了上述属性之后,只需要访问该应用的/shutdown端点就能实现关闭该应用的远程操作。由于开放关闭应用的操作本身是一件非常危险的事,所以真正在线上使用的时候,我们需要对其加入一定的保护机制,比如:定制Actuator的端点路径、整合Spring Security进行安全校验等。