16.2 消费Actuator端点

16.2 消费Actuator端点

Actuator是一个真正的宝藏,我们可以通过表16.1列出的HTTP端点获取正在运行中的应用的有用信息。作为HTTP端点,它们可以像任意REST API那样被消费,我们可以选择任意的HTTP客户端,包括Spring的RestTemplate和WebClient、基于浏览器的JavaScript应用以及简单的curl命令行客户端。

在探索Actuator端点的过程中,我们将会在本章中使用curl命令行客户端。在第17章中,我将会为你介绍Spring Boot Admin,这是一个构建在Actuator端点之上的用户友好的Web应用。

为了了解Actuator都提供了哪些端点,我们可以向Actuator的基础路径发送一个GET请求,这样能够得到每个端点的HATEOAS链接。如果使用curl向“/actuator”发送一个请求,那么我们将会看到如下所示的响应(为了节省空间,进行了删减):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ curl localhost:8081/actuator
{
"_links": {
"self": {
"href": "http://localhost:8081/actuator",
"templated": false
},
"auditevents": {
"href": "http://localhost:8081/actuator/auditevents",
"templated": false
},
"beans": {
"href": "http://localhost:8081/actuator/beans",
"templated": false
},
"health": {
"href": "http://localhost:8081/actuator/health",
"templated": false
},
...
}
}

因为不同的库可能会贡献自己的Actuator端点而且某些端点可能没有对外暴露,所以不同应用之间的实际结果也许会有所差异。

不管在什么情况下,Actuator基础路径提供的链接集合都可以作为Actuator所提供端点的一幅地图。我们首先从两个提供应用基本信息的端点开始探索Actuator,这两个端点就是“/health”和“/info”。

16.2.1 获取应用的基础信息

在去医院看病的时候,医生通常会首先问两个问题:你是谁?你感觉怎样?尽管医生或护士选择的说法可能会有所不同,但是他们的最终目的都是想要了解接诊的人以及为什么要去医院找医生看病。

Actuator的“/info”和“/health”端点为Spring Boot应用同等重要的问题提供了答案。“/info”端点告诉我们关于应用的信息,而“/health”端点则告诉我们应用健康状况的信息。

请求关于应用的信息

要了解正在运行中的应用的信息,我们可以请求“/info”端点。但是,默认情况下,“/info”并不会提供什么信息。如下是我们使用curl发送请求后可能会看到的效果:

1
2
$ curl localhost:8081/actuator/info
{}

虽然这样看起来,“/info”端点似乎没有太大的用处,但是我们最好将它视为一块干净的画布,我们可以在上面绘制任何想要展现的信息。

我们可以有多种为“/info”端点提供信息的方式,但是最简单直接的就是创建一个或多个属性名带有“info.”前缀的配置属性。例如,假设我们希望在“/info”的响应中包含售后支持的联系信息,包括Email地址和电话号码。为了实现这一点,我们可以在application.yml文件中配置如下的属性:

1
2
3
4
info:
contact:
email: support@tacocloud.com
phone: 822-625-6831

对于Spring Boot和应用上下文中的bean来说,info.contact.email property和info.contact.phone属性可能都没有什么特殊的意义。但是,因为它们的前缀是info,所以“/info”端点将会在响应中包含这两个属性的值:

1
2
3
4
5
6
{
"contact": {
"email": "support@tacocloud.com",
"phone": "822-625-6831"
}
}

在16.3.1小节,我们将会看到使用关于应用的有用信息来填充“/info”端点的其他几种方式。

探查应用的健康状况

发送HTTP GET请求到“/health”端点将会得到一个简单的JSON响应,其中包含了应用的健康状态。例如,如下是我们使用curl访问“/health”端点可能看到的响应:

1
2
$ curl localhost:8080/actuator/health
{"status":"UP"}

你可能会想,一个端点报告应用的状态是UP,这能有什么用处呢。如果应用停掉,那么它又该报告什么呢?

实际上,这里显示的是一个或多个健康指示器的聚合状态。健康指示器会报告应用要与之交互的外部系统的健康状态,比如数据库、消息代理甚至Spring Cloud组件,比如Eureka和Config Server。每个指示器的健康状态可能会是如下的可选值中的某一个。

  • UP:外部系统已经启动并且可以访问。
  • DOWN:外部系统已经停机或者不可访问。
  • UNKNOWN:外部系统的状态尚不清楚。
  • OUT_OF_SERVICE:外部系统可以访问得到,但是目前不可用。

所有健康指示器的状态会聚合成应用整体的健康状态,这个过程中会使用如下的规则。

  • 如果所有指示器都是UP,那么应用的健康状态是UP。
  • 如果一个或多个健康指示器是DOWN,那么应用的健康状态就是DOWN。
  • 如果一个或多个健康指示器是OUT_OF_SERVICE,那么应用的健康状态就是OUT_OF_SERVICE。
  • UNKNOWN的健康状态会被忽略,不会计入应用的聚合状态中。

默认情况下,请求“/health”端点的响应中只会包含聚合的状态。但是,我们可以配置management.endpoint.health.show-details属性,以便于展示所有健康指示器的完整细节:

1
2
3
4
management:
endpoint:
health:
show-details: always

management.endpoint.health.show-details属性的默认值是never。我们可以将它设置成always,这样就会始终显示健康指示器的完整细节;也可以将其设置成when-authorized,只有当客户端是完整认证的情况下才展示完整的细节信息。

现在,我们向“/health”端点发送GET请求的话,就会得到健康指示器的完整细节。如下是一个与Mongo文档数据库集成的服务样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"status": "UP",
"details": {
"mongo": {
"status": "UP",
"details": {
"version": "3.2.2"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 499963170816,
"free": 177284784128,
"threshold": 10485760
}
}
}
}

所有的应用,不管其外部依赖是什么,至少都会有一个针对文件系统的健康指示器,名为diskSpace。diskSpace健康指示器能够显示文件系统的健康状况(希望它是UP状态),这个状态的值是由还有多少剩余空间决定的。如果可用磁盘空间低于阈值,那么它将会报告DOWN的状态。

在前面的样例中,还有一个mongo健康指示器,它报告了Mongo数据库的状态。细节信息包括了Mongo数据库的版本。

自动配置功能能够确保只有与应用程序相关的健康指示器才会显示到“/health”端点中。除了mongo和diskSpace健康指示器,Spring Boot还为多个外部数据库和系统提供了健康指示器,包括:

  • Cassandra
  • Config Server
  • Couchbase
  • Eureka
  • Hystrix
  • JDBC数据源
  • Elasticsearch
  • InfluxDB
  • JMS消息代理
  • LDAP
  • Email服务器
  • Neo4j
  • Rabbit消息代理
  • Redis
  • Solr

另外,第三方库可以贡献自己的健康指示器。我们将会在16.3.2小节看一下如何编写自定义的健康指示器。

我们可以看到“/info”和“/health”端点提供了正在运行中的应用的基本信息。同时,还有一些其他的Actuator端点能够探查应用内部的配置信息。接下来,我们看一下Actuator是如何展现应用的配置的。

16.2.2 查看配置细节

除了接收应用的基本信息之外,了解应用是如何配置的也很有指导意义。例如,应用上下文中都有哪些bean?自动配置中哪些条件通过了,哪些条件失败了?应用中有哪些可用的环境变量?HTTP请求是如何映射控制器的?某些包或类所设置的日志级别是什么?

这些问题可以通过Actuator的“/beans”“/conditions”“/env”“/configprops”“/mappings”和“/loggers”端点来回答。在有些情况下,我们甚至还可以使用“/env”和“/loggers”端点,在应用运行的过程中对配置信息进行调整。我们将会逐个看一下这些端点,它们能够让我们洞察正在运行中的应用的配置情况。下面首先从“/beans”端点开始。

获取bean的装配报告

要研究Spring应用上下文,最基础的端点就是“/beans”。这个端点返回的JSON文档描述了应用上下文中的每个bean,其中包括它的Java类型以及它被注入的其他bean。

对“/beans”端点发送GET请求的完整响应可以轻松地填满这一整章。所以,我们不会列出“/beans”的完整响应,而是只考虑下面的片段,主要关注一个bean条目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"contexts": {
"application-1": {
"beans": {
...
"ingredientsController": {
"aliases": [],
"scope": "singleton",
"type": "tacos.ingredients.IngredientsController",
"resource": "file [/Users/habuma/Documents/Workspaces/
TacoCloud/ingredient-service/target/classes/tacos/
ingredients/IngredientsController.class]",
"dependencies": [
"ingredientRepository"
]
},
...
},
"parentId": null
}
}
}

响应的根元素是contexts,它包含了一个子元素,代表应用中的每个Spring应用上下文。在每个应用上下文中,都有一个beans元素,它包含了应用上下文所有bean的细节。

在上面的样例中,显示了名为ingredientsController的bean。我们可以看到,它没有别名,scope是singleton并且类型为tacos.ingredients.IngredientsController。另外,resource属性指向了定义这个bean的类文件路径。dependencies属性列出了注入到给定bean的所有其他bean。在本例中,ingredientsController被注入了一个名为ingredientRepository的bean。

阐述自动装配

我们可以看到,自动装配是Spring Boot提供的最强大的功能之一。但是,有时候你可能想要知道这些功能为什么会自动装配在一起。或者,你认为某些功能已经自动装配了,但是它们实际上却没有,你可能想要知道原因所在。在这种情况下,我们可以向“/conditions”发送GET请求,这样我们会知道自动装配过程中都发生了什么。

“/conditions”端点的自动装配报告可以分为3部分:匹配上的(positivematches,即已通过的条件化配置)、未匹配上的(negative matches,即失败的条件化配置)以及非条件化的类。如下的片段是对“/conditions”请求的响应,展现了每个组成部分的示例:

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
{
"contexts": {
"application-1": {
"positiveMatches": {
...
"MongoDataAutoConfiguration#mongoTemplate": [
{
"condition": "OnBeanCondition",
"message": "@ConditionalOnMissingBean (types:
org.springframework.data.mongodb.core.MongoTemplate;
SearchStrategy: all) did not find any beans"
}
],
...
},
"negativeMatches": {
...
"DispatcherServletAutoConfiguration": {
"notMatched": [
{
"condition": "OnClassCondition",
"message": "@ConditionalOnClass did not find required
class 'org.springframework.web.servlet.
DispatcherServlet'"
}
],
"matched": []
},
...
},
"unconditionalClasses": [
...
"org.springframework.boot.autoconfigure.context.
ConfigurationPropertiesAutoConfiguration",
...
]
}
}
}

在positiveMatches区域中,我们可以看到通过自动配置创建了一个MongoTemplate bean,这是因为目前上下文中还没有这样的bean。导致这种配置结果的原因是这里包含了@ConditionalOnMissingBean注解,如果没有明确配置这个bean,就会自动配置它。在本例中,并没有找到MongoTemplate类型的bean,因此自动配置功能介入并创建了一个该类型的bean。

在negativeMatches区域中,Spring Boot要尝试配置一个DispatcherServlet。但是,@ConditionalOnClass条件化注解失败了,这是因为没有找到DispatcherServlet类。

最后,在unconditionalClasses区域中是一个无条件配置的ConfigurationPropertiesAutoConfiguration。配置属性是Spring Boot操作的基础,所以任何与配置属性相关的配置都应该无条件自动装配。

探查环境和配置属性

除了知道应用的bean是如何装配在一起的,我们可能还对有哪些可用的环境属性以及bean中都注入了哪些配置属性感兴趣。

当我们向“/env”端点发送GET请求的时候,我们会得到一个非常长的响应,它包含了Spring应用中所有发挥作用的属性源。其中包括来自环境变量、JVM系统属性、application.properties和application.yml文件甚至来自Spring CloudConfig Server(该应用是Config Server客户端)的属性。

程序清单16.1列出了“/env”端点能够得到的响应示例,不过进行了删减,这样你会对它所提供的信息有一个大致了解:

程序清单16.1 “/env”端点的结果
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
$ curl localhost:8081/actuator/env
{
"activeProfiles": [
"development"
],
"propertySources": [
...
{
"name": "systemEnvironment",
"properties": {
"PATH": {
"value": "/usr/bin:/bin:/usr/sbin:/sbin",
"origin": "System Environment Property \"PATH\""
},
...
"HOME": {
"value": "/Users/habuma",
"origin": "System Environment Property \"HOME\""
}
}
},
{
"name": "applicationConfig: [classpath:/application.yml]",
"properties": {
"spring.application.name": {
"value": "ingredient-service",
"origin": "class path resource [application.yml]:3:11"
},
"server.port": {
"value": 8081,
"origin": "class path resource [application.yml]:9:9"
},
...
}
},
...
]
}

“/env”的完整响应会包含更多的信息,但是程序清单16.1只包含了几个值得注意的元素。首先,在响应的顶部是名为activeProfiles的字段。在本例中,它表明development profile处于激活状态。如果还有其他profile处于激活状态,那么也将会列到这里。

随后,propertySources字段是一个数组,Spring应用环境的每个属性源对应其中的一个条目。在程序清单16.1中,只显示了systemEnvironment以及引用application.yml文件的属性源。

在每个属性源中,是该属性源所提供的属性的列表以及它们的值。在application.yml属性源中,每个属性的origin字段指明了该属性是在哪里设置的,包括在application.yml文件中的行号和列号。

“/env”端点也可以用来获取特定的属性,只需要将属性名作为路径的第二个元素即可。例如,要检查server.port属性,我们可以提交GET请求到“/env/server.port”,如下所示:

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
$ curl localhost:8081/actuator/env/server.port
{
"property": {
"source": "systemEnvironment", "value": "8081"
},
"activeProfiles": [ "development" ],
"propertySources": [
{ "name": "server.ports" },
{ "name": "mongo.ports" },
{ "name": "systemProperties" },
{ "name": "systemEnvironment",
"property": {
"value": "8081",
"origin": "System Environment Property \"SERVER_PORT\""
}
},
{ "name": "random" },
{ "name": "applicationConfig: [classpath:/application.yml]",
"property": {
"value": 0,
"origin": "class path resource [application.yml]:9:9"
}
},
{ "name": "springCloudClientHostInfo" },
{ "name": "refresh" },
{ "name": "defaultProperties" },
{ "name": "Management Server" }
]
}

我们可以看到,这里依然会展现所有的属性源,但是只有包含特定属性的属性源才会显示额外的信息。在本例中,systemEnvironment属性源和application.yml属性源都包含了server.port属性的值。因为systemEnvironment属性源要优先于后面所列的属性源,所以它的值8081会胜出。胜出的值也会反映在顶部的property字段中。

不仅可以用“/env”端点来读取属性的值,还可以通过向“/env”端点发送POST请求,同时提交JSON文档格式的name和value字段,为正在运行的应用设置属性。例如,要将名为tacocloud.discount.code的属性设置为TACOS1234,我们可以在命令行使用curl提交POST请求,如下所示:

1
2
3
4
$ curl localhost:8081/actuator/env \
-d'{"name":"tacocloud.discount.code","value":"TACOS1234"}' \
-H "Content-type: application/json"
{"tacocloud.discount.code":"TACOS1234"}

在提交该属性之后,在返回的响应中将会包含新设置的属性和它的值。如果后续不需要这个属性,那么我们可以提交一个DELETE请求到“/env”端点,将通过该端点创建的所有属性删除:

1
2
$ curl localhost:8081/actuator/env -X DELETE
{"tacocloud.discount.code":"TACOS1234"}

通过Actuator API设置属性是非常有用的,但是需要记住所有通过向“/env”端点发送POST请求设置的属性只会用到接收到该请求的应用中,是临时的,应用重启的话就会丢失。

HTTP映射导览

尽管Spring MVC(以及Spring WebFlux)编程模型非常易于处理HTTP请求,我们只需要为方法添加请求映射注解即可,但是我们很难对应用整体能够处理哪些HTTP请求以及每种组件分别处理哪些请求有一个整体的了解。

Actuator的“/mappings”端点为应用中的所有HTTP请求处理器提供了一个一站式的视图,不管这些处理器是来自Spring MVC控制器还是Actuator端点,我们都能一目了然地看清。要获取Spring Boot应用中所有端点的完整列表,我们只需要向“/mappings”发送一个GET请求,就会看到大致如程序清单16.2所示的响应。

程序清单16.2 “/mappings”端点所展示的HTTP映射
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
$ curl localhost:8081/actuator/mappings | jq
{
"contexts": {
"application-1": {
"mappings": {
"dispatcherHandlers": {
"webHandler": [
...
{
"predicate": "{[/ingredients],methods=[GET]}",
"handler": "public
reactor.core.publisher.Flux<tacos.ingredients.Ingredient>
tacos.ingredients.IngredientsController.allIngredients()",
"details": {
"handlerMethod": {
"className": "tacos.ingredients.IngredientsController",
"name": "allIngredients",
"descriptor": "()Lreactor/core/publisher/Flux;"
},
"handlerFunction": null,
"requestMappingConditions": {
"consumes": [],
"headers": [],
"methods": [
"GET"
],
"params": [],
"patterns": [
"/ingredients"
],
"produces": []
}
}
},
...
]
}
},
"parentId": "application-1"
},
"bootstrap": {
"mappings": {
"dispatcherHandlers": {}
},
"parentId": null
}
}
}

为了简洁,这个响应进行了删减,只包含了一个请求处理器。具体来讲,它表明对“/ingredients”的GET请求将由IngredientsController的allIngredients()方法来处理。

管理日志级别

对于任何应用来说,日志都是很重要的特性。日志是一种审计方式,也是一种较为粗略的调试方法。

设置日志级别是一种需要很强平衡能力的事情。如果我们将日志级别设置得太低,那么日志中会有太多的噪声,查找有用的信息会变得很困难。另外,如果我们将日志级别设置地过于简洁,那么日志对于理解应用正在做什么可能没有太大的价值。

日志级别通常会基于Java包来进行设置。如果你想要知道正在运行的应用中使用了什么日志级别,那么可以向“/loggers”端点发送GET请求。如下的JSON展示了“/loggers”响应的一个片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"levels": [ "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" ],
"loggers": {
"ROOT": {
"configuredLevel": "INFO", "effectiveLevel": "INFO"
},
...
"org.springframework.web": {
"configuredLevel": null, "effectiveLevel": "INFO"
},
...
"tacos": {
"configuredLevel": null, "effectiveLevel": "INFO"
},
"tacos.ingredients": {
"configuredLevel": null, "effectiveLevel": "INFO"
},
"tacos.ingredients.IngredientServiceApplication": {
"configuredLevel": null, "effectiveLevel": "INFO"
}
}
}

在响应的顶部首先是所有合法日志级别的列表。在此之后,loggers元素列出了应用中每个包的日志级别详情。configuredLevel属性展示了明确配置的日志级别(如果没有明确配置的话,将会显示null)。effectiveLevel属性展示的是实际的日志级别,它可能是从父包或根logger继承下来的。

尽管这个片段只展现了根logger和4个包的日志级别,但是完整的响应会包含应用中每个包的日志级别,包括我们所使用的库对应的包。如果你只关心特定的包,那么可以在请求中以额外路径组件的方式指明包的名称。

例如,你只想知道taco.ingredients包的日志级别,那么可以发送请求到“/loggers/tacos/ ingredients”:

1
2
3
4
{
"configuredLevel": null,
"effectiveLevel": "INFO"
}

除了返回应用程序中包的日志级别之外,通过向“/loggers”端点发送POST请求,我们还能修改已配置的日志级别。例如,假设我们想要将taco.ingredients包的日志级别设置为DEBUG。如下的curl命令能够实现这一点:

1
2
3
$ curl localhost:8081/actuator/loggers/tacos/ingredients \
-d'{"configuredLevel":"DEBUG"}' \
-H"Content-type: application/json"

现在,日志级别已经发生了变化,我们可以向“/loggers/tacos/ingredients”发送GET请求,看一下它变成了什么样子:

1
2
3
4
{
"configuredLevel": "DEBUG",
"effectiveLevel": "DEBUG"
}

注意,在此之前,configuredLevel的值为null,现在它变成了DEBUG。这个变更也会影响到effectiveLevel。最重要的是,如果这个包中的代码以debug级别打印日志,那么日志文件中将会包含debug级别的信息。

16.2.3 查看应用的活动

如果我们能够时刻监视运行中应用的活动,那将会非常有用,我们所关注的信息可能包括应用正在处理什么类型的HTTP请求以及应用中所有线程的活动。为了实现这一点,Actuator提供了“/httptrace”“/threaddump”和“/heapdump”端点。

“/heapdump”端点可能是最难以详细阐述的Actuator端点。简而言之,它会下载一个gzip压缩的HPROF堆转储文件,该文件可以用来跟踪内存和线程问题。由于篇幅的原因,再加上堆转储文件的使用是一个非常高级的特性,所以对“/heapdump”端点的介绍就仅限于此。

跟踪HTTP活动

“/httptrace”端点能够报告应用所处理的最近100个请求的详情。详情内容包括请求的方法和路径、代表请求处理时刻的时间戳、请求和响应的头信息以及处理该请求的耗时。

如下的JSON片段展示了“/httptrace”端点响应的一个条目:

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
{
"traces": [
{
"timestamp": "2018-06-03T23:41:24.494Z",
"principal": null,
"session": null,
"request": {
"method": "GET",
"uri": "http://localhost:8081/ingredients",
"headers": {
"Host": ["localhost:8081"],
"User-Agent": ["curl/7.54.0"],
"Accept": ["*/*"]
},
"remoteAddress": null
},
"response": {
"status": 200,
"headers": {
"Content-Type": ["application/json;charset=UTF-8"]
}
},
"timeTaken": 4
},
...
]
}

尽管这些信息对调试很有价值,但是随着时间推移不断跟踪数据是更有意思的,基于响应的状态值,它能够让我们洞察应用程序在给定的时间内有多少请求是成功的、有多少请求是失败的。在第17章中,我们将会看到Spring Boot Admin是如何将这些信息捕获到一个运行图中的,借助这个图我们能够可视化一定的时间范围内的HTTP跟踪信息。

监控线程

除了HTTP请求的跟踪信息,在确定应用运行状况的时候,线程活动也是非常有用的。“/threaddump”端点能够生成一个当前线程活动的快照。通过如下的“/threaddump”端点响应片段,我们能够大致了解这个端点都提供了什么功能:

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
{
"threadName": "reactor-http-nio-8",
"threadId": 338,
"blockedTime": -1,
"blockedCount": 0,
"waitedTime": -1,
"waitedCount": 0,
"lockName": null,
"lockOwnerId": -1,
"lockOwnerName": null,
"inNative": true,
"suspended": false,
"threadState": "RUNNABLE",
"stackTrace": [
{
"methodName": "kevent0",
"fileName": "KQueueArrayWrapper.java",
"lineNumber": -2,
"className": "sun.nio.ch.KQueueArrayWrapper",
"nativeMethod": true
},
{
"methodName": "poll",
"fileName": "KQueueArrayWrapper.java",
"lineNumber": 198,
"className": "sun.nio.ch.KQueueArrayWrapper",
"nativeMethod": false
},
...
],
"lockedMonitors": [
{
"className": "io.netty.channel.nio.SelectedSelectionKeySet",
"identityHashCode": 1039768944,
"lockedStackDepth": 3,
"lockedStackFrame": {
"methodName": "lockAndDoSelect",
"fileName": "SelectorImpl.java",
"lineNumber": 86,
"className": "sun.nio.ch.SelectorImpl",
"nativeMethod": false
}
},
...
],
"lockedSynchronizers": [],
"lockInfo": null
}

完整的线程转储报告包含了运行中应用的每个线程。为了节省空间,这里的线程转储进行了删减,只包含了一个线程条目。我们可以看到,这里包含了线程的阻塞和锁定状态,以及其他的线程细节。这里还有一个堆栈,它能够展现线程都将时间花到了哪块代码中。

因为“/threaddump”只提供了请求时线程活动的快照,所以它很难完整了解随着时间的推移线程的行为都是什么样子的。在第17章中,我们将会看到SpringBoot Admin如何在一个实时视图中监视“/threaddump”端点。

16.2.4 获取应用的指标

“/metrics”端点能够报告运行中的应用程序所生成的各种度量指标,包括关于内存、处理器、垃圾收集以及HTTP请求的指标。Actuator提供了20多个开箱即用的指标分类,当我们向“/metrics”发送GET请求时所得到的指标分类证明了这一点:

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
$ curl localhost:8081/actuator/metrics | jq
{
"names": [
"jvm.memory.max",
"process.files.max",
"jvm.gc.memory.promoted",
"http.server.requests",
"system.load.average.1m",
"jvm.memory.used",
"jvm.gc.max.data.size",
"jvm.memory.committed",
"system.cpu.count",
"logback.events",
"jvm.buffer.memory.used",
"jvm.threads.daemon",
"system.cpu.usage",
"jvm.gc.memory.allocated",
"jvm.threads.live",
"jvm.threads.peak",
"process.uptime",
"process.cpu.usage",
"jvm.classes.loaded",
"jvm.gc.pause",
"jvm.classes.unloaded",
"jvm.gc.live.data.size",
"process.files.open",
"jvm.buffer.count",
"jvm.buffer.total.capacity",
"process.start.time"
]
}

这里涉及太多的指标,所以本章不可能面面俱到地介绍。相反,我们可以只关注一个指标分类,即http.server.requests,以它作为样例介绍如何消费“/metrics”端点。

现在,我们不再简单地请求“/metrics”,而是发送GET请求到“/metrics/{METRICS CATEGORY}”,这样我们就会收到该分类的指标详情。就http.server.requests来说,我们发送GET请求到“/metrics/http.server.requests”所返回的数据如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ curl localhost:8081/actuator/metrics/http.server.requests
{
"name": "http.server.requests",
"measurements": [
{ "statistic": "COUNT", "value": 2103 },
{ "statistic": "TOTAL_TIME", "value": 18.086334315 },
{ "statistic": "MAX", "value": 0.028926313 }
],
"availableTags": [
{ "tag": "exception",
"values": [ "ResponseStatusException",
"IllegalArgumentException", "none" ] },
{ "tag": "method", "values": [ "GET" ] },
{ "tag": "uri",
"values": [
"/actuator/metrics/{requiredMetricName}",
"/actuator/health", "/actuator/info", "/ingredients",
"/actuator/metrics", "/**" ] },
{ "tag": "status", "values": [ "404", "500", "200" ] }
]
}

这个响应中最重要的组成部分是measurements区域,它包含了所请求分类的所有指标数据。在本例中,它表示一共有2103个HTTP请求,处理这些请求的总耗时是18.086334315秒,处理单个请求的最大耗时是0.028926313秒。

这些通用的指标非常有意思,但是我们可以使用availableTags中所列出的标签进一步细化结果。例如,我们知道一共有2103个请求,但是还不知道HTTP 200、HTTP 404或HTTP 500响应状态的请求分别有多少。借助status标签,我们可以得到所有状态为HTTP 404的请求指标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ curl localhost:8081/actuator/metrics/http.server.requests? \
tag=status:404
{
"name": "http.server.requests",
"measurements": [
{ "statistic": "COUNT", "value": 31 },
{ "statistic": "TOTAL_TIME", "value": 0.522061212 },
{ "statistic": "MAX", "value": 0 }
],
"availableTags": [
{ "tag": "exception",
"values": [ "ResponseStatusException", "none" ] },
{ "tag": "method", "values": [ "GET" ] },
{ "tag": "uri",
"values": [
"/actuator/metrics/{requiredMetricName}", "/**" ] }
]
}

通过使用tag请求属性指定标签名和值,我们可以看到所有响应为HTTP 404的请求的指标。这里显示有31个请求的结果是404,耗用了0.522061212秒。除此之外,我们可以看到有一些失败的请求是针对“/actuator/metrics/{requiredMetricsName}”的GET请求(尽管我们并不清楚{requiredMetricsName}路径变量解析成了什么)。另外,有些是发送其他路径的,是由“/**”通配符捕获到的。

如果我们想要知道有多少HTTP 404响应是发送到“/**”路径的,那么又该怎么办呢?我们所要做的就是进一步对其进行过滤,在请求中使用url标签,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
% curl "localhost:8081/actuator/metrics/http.server.requests? \
tag=status:404&tag=uri:/**"
{
"name": "http.server.requests",
"measurements": [
{ "statistic": "COUNT", "value": 30 },
{ "statistic": "TOTAL_TIME", "value": 0.519791548 },
{ "statistic": "MAX", "value": 0 }
],
"availableTags": [
{ "tag": "exception", "values": [ "ResponseStatusException" ] },
{ "tag": "method", "values": [ "GET" ] }
]
}

我们可以看到有30个路径匹配“/**”的请求得到了HTTP 404,并且处理这些请求耗费了0.519791548秒。

你可能也注意到了,随着我们不断细化请求的条件,可用的标签越来越有限。这里只列出了展现指标所对应的请求能够适用的标签。在本例中,exception和method标签只有一个值。显然,30个请求都是GET请求,并且都是因为抛出ResponseStatusException而产生的404状态。

导览整个“/metrics”可能是一件很麻烦的事情,但是稍加练习,我们一定能够找到自己想要的数据。在第17章中,我们将会看到借助Spring Boot Admin,我们能够更容易地消费“/metrics”端点的数据。

尽管Actuator端点所提供的信息有助于观察运行中Spring Boot应用的内部状况,但是它们并不适用于人类直接使用。作为REST端点,它们是供其他应用消费的,这里所说的其他应用也可能是UI。考虑到这一点,我们在第17章会看到如何在用户友好的Web应用中展现Actuator信息。现在,我们看一下如何自定义Actuator的端点。