消费Actuator端点
Actuator是真正的宝藏——我们可以通过表15.1列出的HTTP端点获取正在运行中的应用的有用信息。HTTP端点可以像任意REST API那样被消费,而我们可以选择任意的HTTP客户端,包括Spring的RestTemplate和WebClient、JavaScript应用,以及简单的curl命令行客户端。
在探索Actuator端点的过程中,本章会使用curl命令行客户端。在第16章,我会介绍Spring Boot Admin,一个构建在Actuator端点之上的用户友好的Web应用。
为了了解Actuator都提供哪些端点,可以向Actuator的基础路径发送一个GET请求,这样能够得到每个端点的HATEOAS链接。我如果使用curl向“/actuator”发送请求,就会看到如下所示的响应(为了节约篇幅,进行了删减):
$ curl localhost:8080/actuator
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"auditevents": {
"href": "http://localhost:8080/actuator/auditevents",
"templated": false
},
"beans": {
"href": "http://localhost:8080/actuator/beans",
"templated": false
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
...
}
}
因为不同的库可能会贡献自己的Actuator端点,而且某些端点可能没有对外暴露,所以不同应用之间的实际结果也许会有所差异。
不管在什么情况下,都可以将Actuator基础路径提供的链接集合看作Actuator所提供端点的一幅地图。我们首先从两个提供应用基本信息的端点开始探索Actuator:“/health”和“/info”。
获取应用的基础信息
在去医院看病的时候,医生通常会首先问两个问题——你是谁?你感觉怎样?尽管医生或护士的表达方式会有所不同,但是他们的最终目的都是想要了解对方的身份以及他为什么要去医院找医生看病。
类似地,我们也要关注Spring Boot“是谁”且“感觉怎样”,Actuator的“/info”和“/health”端点为这些重要问题提供了答案。“/info”端点告诉我们关于应用的信息,而“/health”端点则告诉我们应用健康状况的信息。
请求关于应用的信息
要了解正在运行中的Spring Boot应用的信息,我们可以请求“/info”端点。但是,默认情况下,“/info”并不会提供任何信息。如下是我们使用curl发送请求后可能看到的效果:
$ curl localhost:8080/actuator/info
{}
虽然这样看起来,“/info”端点似乎没有太大的用处,但是我们最好将它视为一块干净的画布,我们可以在上面绘制任何想要展现的信息。
有多种为“/info”端点提供信息的方式,但是最简单直接的就是创建一个或多个属性名带有“info.”前缀的配置属性。例如我们希望在“/info”的响应中包含售后支持的联系信息,包括电子邮件地址和电话号码,可以在application.yml文件中配置如下的属性:
info:
contact:
email: support@tacocloud.com
phone: 822-625-6831
对于Spring Boot或者其应用上下文中的bean,info.contact.email属性和info.contact. phone属性可能都没有什么特殊的意义。但是,因为它们的前缀是info,所以“/info”端点将会在响应中包含这两个属性的值:
{
"contact": {
"email": "support@tacocloud.com",
"phone": "822-625-6831"
}
}
在15.3.1小节,我们会看到使用关于应用的有用信息来填充“/info”端点的其他几种方式。
探查应用的健康状况
发送HTTP GET请求到“/health”端点将会得到一个简单的JSON响应,其中包含了应用的健康状态。例如使用curl访问“/health”端点可能会看到如下响应:
$ 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属性,以便展示所有健康指示器的完整细节:
management:
endpoint:
health:
show-details: always
management.endpoint.health.show-details属性的默认值是never。我们可以将它设置成always,从而使健康指示器的完整细节始终显示;我们也可以将其设置成when-authorized,从而只在客户端经完整认证的情况下完整显示细节信息。
现在向“/health”端点发送GET请求,就会得到健康指示器的完整细节。如下是一个与Mongo文档数据库集成的服务样例:
{
"status": "UP",
"details": {
"mongo": {
"status": "UP",
"details": {
"version": "3.5.5"
}
},
"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;
-
电子邮件服务器;
-
Neo4j;
-
Rabbit消息代理;
-
Redis;
-
Solr。
另外,第三方库可以贡献自己的健康指示器。我们会在15.3.2小节看到如何编写自定义的健康指示器。
我们可以看到“/info”和“/health”端点提供了正在运行的应用的基本信息,以及一些其他Actuator端点能够探查应用内部的配置信息。接下来,我们看一下Actuator展现应用的配置方法。
查看配置细节
除了接收应用的基本信息,了解应用是如何配置的也很有指导意义。例如,应用上下文中都有哪些bean?自动配置中哪些条件通过了,哪些条件失败了?应用中有哪些可用的环境变量?HTTP请求是如何映射控制器的?某些包或类所设置的日志级别是什么?
这些问题可以通过Actuator的“/beans”“/conditions”“/env”“/configprops”“/mappings”和“/loggers”端点来回答。一些情况下,我们甚至还可以使用“/env”和“/loggers”端点,在应用运行的过程中对配置信息进行调整。我们会逐个查看这些能够让我们洞察正在运行中应用的配置情况的端点,从“/beans”开始。
获取bean的装配报告
要研究Spring应用上下文,最基础的端点就是“/beans”。这个端点返回的JSON文档描述了应用上下文中的每个bean,包括其Java类型及其中被注入的其他bean。
对“/beans”端点发送GET请求的完整响应篇幅很长,甚至可以单独成为一章,所以我不会列出“/beans”的完整响应,而是只考虑下面的片段,主要关注一个bean条目:
{
"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,即已通过的条件化配置)、负向匹配(negativeMatches,即不能通过的条件化配置),以及非条件化的类。如下的片段是对“/conditions”请求的响应,展现了每个组成部分的示例:
{
"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 bean。配置属性是Spring Boot操作的基础,所以任何与配置属性相关的配置都应该无条件自动装配。
探查环境和配置属性
知道了应用的bean是如何装配的后,我们可能还对有哪些可用的环境属性以及bean中都注入了哪些配置属性感兴趣。
向“/env”端点发送GET请求时,我们会得到一个非常长的响应,包含Spring应用中所有发挥作用的属性源。其中包括来自环境变量、JVM系统属性、application.properties和application.yml文件,甚至Spring Cloud Config Server(如果该应用是Config Server客户端)的属性。
程序清单15.1列出了“/env”端点能够得到的响应示例(有删减),这会让我们对它所提供的信息有一个大致了解。
$ curl localhost:8080/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": 8080,
"origin": "class path resource [application.yml]:9:9"
},
...
}
},
...
]
}
“/env”的完整响应会包含更多的信息,但是程序清单15.1 只包含了几个值得注意的元素。
首先,在响应的顶部是名为activeProfiles的字段。在本例中,它表明development profile处于激活状态。如果其他profile也处于激活状态,同样会列到这里。
其次,propertySources字段是一个数组,Spring应用环境的每个属性源对应其中的一个条目。在程序清单15.1中,只显示了systemEnvironment和引用application.yml文件的属性源。
属性源包含该属性源所提供的属性列表及属性的值。在application.yml属性源中,每个属性的origin字段指明了该属性是在哪里设置的,包括在application.yml文件中的行号和列号。
也可以借助“/env”端点获取特定的属性,只需将属性名作为路径的第二个元素。例如,要检查server.port属性,可以提交GET请求到“/env/server.port”,如下所示:
$ curl localhost:8080/actuator/env/server.port
{
"property": {
"source": "systemEnvironment", "value": "8080"
},
"activeProfiles": [ "development" ],
"propertySources": [
{ "name": "server.ports" },
{ "name": "mongo.ports" },
{ "name": "systemProperties" },
{ "name": "systemEnvironment",
"property": {
"value": "8080",
"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请求:
$ curl localhost:8080/actuator/env \
-d'{"name":"tacocloud.discount.code","value":"TACOS1234"}' \
-H "Content-type: application/json"
{"tacocloud.discount.code":"TACOS1234"}
在提交该属性之后,在返回的响应中将会包含新设置的属性和它的值。如果后续不需要这个属性,可以提交一个DELETE请求到“/env”端点,将通过该端点创建的所有属性删除:
$ curl localhost:8080/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请求,就会看到大致如程序清单15.2所示的响应。为简洁起见,这里进行了删减。
$ curl localhost:8080/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
}
}
}
在这里,来自curl命令行的响应被输送到一个叫作jq的工具,除其他功能外,它能将请求返回的JSON数据打印成容易阅读的格式。为简洁起见,这个响应被删减了,只留下一个请求处理器的信息。具体来说,它表明对“/ingredients”的GET请求将由AredientsController的allIngredients()方法处理。
管理日志级别
对于任何应用来说,日志都是很重要的特性。日志提供了一种审计方式,也提供了一种较为粗略的调试方法。
设置合适的日志级别需要很强的平衡能力。如果日志级别设置得太低,那么日志中会有太多的噪声,使查找有用的信息变得很困难;如果日志级别设置得过高,那么日志就会过于简洁,对于帮助我们理解应用正在做什么可能没有太大的价值。
日志级别通常会基于Java包设置。如果想要知道正在运行的应用中使用了什么日志级别,可以向“/loggers”端点发送GET请求。如下的JSON展示了“/loggers”响应的一个片段:
{
"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个包的日志级别,但是完整的响应会包含应用中每个包的日志级别,包括我们所使用的库对应的包。如果只关心特定的包,那么可以在请求中以额外路径组件的方式指明包的名称。
例如,如果我们只想知道tacocloud.ingredients包的日志级别,那么可以发送请求到“/loggers/ tacocloud.ingredients”:
{
"configuredLevel": null,
"effectiveLevel": "INFO"
}
除了返回应用程序中包的日志级别,通过向“/loggers”端点发送POST请求,我们还能修改已配置的日志级别。例如,假设我们想要将tacocloud.ingredients包的日志级别设置为DEBUG,如下的curl命令能够实现这一点:
$ curl localhost:8080/actuator/loggers/tacocloud/ingredients \
-d'{"configuredLevel":"DEBUG"}' \
-H"Content-type: application/json"
现在,日志级别已经发生了变化,我们可以向“/loggers/tacocloud.ingredients”发送GET请求,看一下它变成了什么样子:
{
"configuredLevel": "DEBUG",
"effectiveLevel": "DEBUG"
}
注意,在此之前,configuredLevel的值为null,现在它变成了DEBUG。这个变更也会影响到effectiveLevel。最重要的是,如果这个包中的代码以debug级别打印日志,那么日志文件中会包含debug级别的信息。
查看应用的活动
如果能够时刻监视运行中应用的活动,那会很有帮助。我们所关注的信息可能包括应用正在处理什么类型的HTTP请求,以及应用中所有线程的活动。为了实现这一点,Actuator提供了“/httptrace”“/threaddump”和“/heapdump”端点。
“/heapdump”端点可能是最难以详细解释的Actuator端点。简言之,它会下载一个gzip压缩的HPROF堆转储文件,而该文件可以用来跟踪内存和线程问题。由于篇幅所限,且堆转储文件的使用是一个非常高级的特性,所以对“/heapdump”端点的介绍就仅限于此。
跟踪HTTP活动
“/httptrace”端点能够报告应用所处理的最近100个请求的详情。详情内容包括请求的方法和路径、代表请求处理时刻的时间戳、请求和响应的头信息,以及处理该请求的耗时。
如下的JSON片段展示了“/httptrace”端点响应的一个条目:
{
"traces": [
{
"timestamp": "2020-06-03T23:41:24.494Z",
"principal": null,
"session": null,
"request": {
"method": "GET",
"uri": "http://localhost:8080/ingredients",
"headers": {
"Host": ["localhost:8080"],
"User-Agent": ["curl/7.54.0"],
"Accept": ["*/*"]
},
"remoteAddress": null
},
"response": {
"status": 200,
"headers": {
"Content-Type": ["application/json;charset = UTF-8"]
}
},
"timeTaken": 4
},
...
]
}
尽管这些信息对调试很有价值,但是随着时间推移不断跟踪数据更加有趣——基于响应的状态值,这能够让我们洞察应用程序在给定的时间内有多少请求是成功的、有多少请求是失败的。在第16章,我们会看到Spring Boot Admin是如何将这些信息捕获到一个运行图中的。借助这个运行图,我们能够可视化一定时间范围内的HTTP跟踪信息。
监控线程
除了HTTP请求的跟踪信息,在确定应用运行状况时,线程活动也非常有用。“/threaddump”端点能够生成一个当前线程活动的快照。通过如下的“/threaddump”端点响应片段,我们能够大致了解这个端点都提供了什么功能:
{
"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”只提供了请求时线程活动的快照,所以仅靠它很难完整了解随着时间的推移线程的行为都是什么样子的。在第16章,我们会看到Spring Boot Admin如何在一个实时视图中监视“/threaddump”端点。
挖掘应用运行时的指标
“/metrics”端点能够报告运行中的应用程序生成的各种度量指标,包括关于内存、处理器、垃圾收集,以及HTTP请求的指标。Actuator提供了20多个“开箱即用”的指标分类,我们向“/metrics”发送GET请求时得到的指标分类证明了这一点:
$ curl localhost:8080/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”,得到的返回数据如下所示:
$ curl localhost:8080/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的请求指标:
$ curl localhost:8080/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响应是发送到“/**”路径的,又该怎么办呢?要做的就是进一步对其进行过滤,在请求中使用uri标签,如下所示:
% curl "localhost:8080/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秒。
你可能也注意到了,随着我们不断细化请求的条件,响应中availableTags中列出的可用标签越来越有限。这里列出的标签需要能够匹配我们根据展现指标所捕获的请求。在本例中,exception和method标签只有一个值。显然,30个请求都是GET请求,并且都是因为抛出ResponseStatusException而产生的404状态。
导览整个“/metrics”可能很麻烦,但是稍加练习,我们依然可以找到自己想要的数据。在第16章,我们会看到,借助Spring Boot Admin可以更容易地消费“/metrics”端点的数据。
Actuator端点所提供的信息尽管有助于观察运行中Spring Boot应用的内部状况,但并不适用于人类直接阅读。作为REST端点,它们是供其他应用消费的。这里所说的“其他应用”也可能是UI。考虑到这一点,让我们看看如何在一个用户友好的Web应用中展示Actuator的信息。