揭秘Actuator的端点

Spring Boot Actuator 的关键特性是在应用程序里提供众多 Web 端点,通过它们了解应用程序运行时的内部状况。有了 Actuator,你可以知道 Bean 在 Spring 应用程序上下文里是如何组装在一起的,掌握应用程序可以获取的环境属性信息,获取运行时度量信息的快照……

Actuator 提供了 13 个端点,具体如表7-1所示。

image 2024 03 22 16 02 30 824
Figure 1. 表7-1 Actuator的端点

要启用 Actuator 的端点,只需在项目中引入 Actuator 的起步依赖即可。在 Gradle 构建说明文件里,这个依赖是这样的:

compile 'org.springframework.boot:spring-boot-starter-actuator'

对于 Maven 项目,引入的依赖是这样的:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

亦或你使用 Spring Boot CLI,可以使用如下 @Grab 注解:

@Grab('spring-boot-starter-actuator')

无论 Actuator 是如何添加的,在应用程序运行时自动配置都会生效。Actuator 会开启。

表7-1中的端点可以分为三大类:配置端点、度量端点和其他端点。让我们分别了解一下这些端点,从提供应用程序配置信息的端点看起。

查看配置明细

关于 Spring 组件扫描和自动织入,最常遭人抱怨的问题之一就是很难看到应用程序中的组件是如何装配起来的。Spring Boot 自动配置让这个问题变得更严重,因为 Spring 的配置更少了。在有显式配置的情况下,你至少还能看到 XML 文件或者配置类,对 Spring 应用程序上下文里的 Bean 关系有个大概的了解。

我个人从不担心这个问题。也许是因为我意识到,在 Spring 出现之前,根本就没有应用程序组件的映射关系。

但是,如果你担心自动配置隐藏了 Spring 应用程序上下文中 Bean 的装配细节,那么我要告诉你一个好消息!Actuator 有一些端点不仅可以显示组件映射关系,还可以告诉你自动配置在配置 Spring 应用程序上下文时做了哪些决策。

获得Bean装配报告

要了解应用程序中 Spring 上下文的情况,最重要的端点就是 /beans。它会返回一个 JSON 文档,描述上下文里每个 Bean 的情况,包括其 Java 类型以及注入的其他 Bean。向 /beans(在本地运行时是 http://localhost:8080/beans)发起 GET 请求后,你会看到与代码清单7-1示例类似的信息。

代码清单7-1 /beans端点提供的Spring应用程序上下文Bean信息

代码清单7-1是阅读列表应用程序 Bean 信息的一个片段。如你所见,所有的 Bean 条目都有五类信息。

  • bean:Spring 应用程序上下文中的 Bean 名称或ID。

  • resource:.class 文件的物理位置,通常是一个 URL,指向构建出的 JAR 文件。这会随着应用程序的构建和运行方式发生变化。

  • dependencies:当前 Bean 注入的 Bean ID 列表。

  • scope:Bean 的作用域(通常是单例,这也是默认作用域)。

  • type:Bean 的 Java 类型。

虽然 Bean 报告不用具体绘图告诉你 Bean 是如何装配的(例如,通过属性或构造方法),但它帮你直观地了解了应用程序上下文中 Bean 的关系。实际上,写出一个工具,把 Bean 报告处理一下,用图形化的方式来展现 Bean 关系,这并不难。请注意,完整的 Bean 报告会包含很多 Bean,还有很多自动配置的 Bean,画出来的图会非常复杂。

详解自动配置

/beans 端点产生的报告能告诉你 Spring 应用程序上下文里都有哪些 Bean。/autoconfig 端点能告诉你为什么会有这个 Bean,或者为什么没有这个 Bean。

正如第 2 章里说的,Spring Boot 自动配置构建于 Spring 的条件化配置之上。它提供了众多带有 @Conditional 注解的配置类,根据条件决定是否要自动配置这些 Bean。/autoconfig 端点提供了一个报告,列出了计算过的所有条件,根据条件是否通过进行分组。

代码清单7-2是阅读列表应用程序自动配置报告里的一个片段,里面有一个通过的条件,还有一个没通过的条件。

代码清单7-2 阅读列表应用程序的自动配置报告

在 positiveMatches 里,你会看到一个条件,决定 Spring Boot 是否自动配置 JdbcTemplate Bean。匹配到的名字是 DataSourceAutoConfiguration.JdbcTemplateConfiguration# jdbcTemplate,这是运用了条件的具体配置类。条件类型是 OnBeanCondition,意味着条件的输出是由某个 Bean 的存在与否来决定的。在本例中,message 属性已经清晰地表明了该条件是检查是否有 JdbcOperations 类型(JbdcTemplate 实现了该接口)的 Bean 存在。如果没有配置这种 Bean,则条件成立,创建一个 JdbcTemplate Bean。

与之类似,在 negativeMatches 里,有一个条件决定了是否要配置 ActiveMQ。这是一个 OnClassCondition,会检查 Classpath 里是否存在 ActiveMQConnectionFactory。因为 Classpath 里没有这个类,条件不成立,所以不会自动配置 ActiveMQ。

查看配置属性

除了要知道应用程序的 Bean 是如何装配的,你可能还对能获取哪些环境属性,哪些配置属性注入了 Bean 里感兴趣。

/env 端点会生成应用程序可用的所有环境属性的列表,无论这些属性是否用到。这其中包括环境变量、JVM属性、命令行参数,以及 applicaition.properties 或 application.yml 文件提供的属性。

代码清单7-3的示例代码是 /env 端点获取信息的一个片段。

代码清单7-3 /env端点会报告所有可用的属性

基本上,任何能给 Spring Boot 应用程序提供属性的属性源都会列在 /env 的结果里,同时会显示具体的属性。

代码清单7-3中的属性来源有很多,包括应用程序配置(application.yml)、Spring Profile、Servlet 上下文初始化参数、系统环境变量和 JVM 系统属性。(本例中没有 Profile 和 Servlet 上下文初始化参数。)

属性常用来提供诸如数据库或 API 密码之类的敏感信息。为了避免此类信息暴露到 /env 里,所有名为 password、secret、key(或者名字中最后一段是这些)的属性在 /env 里都会加上 “*”。举个例子,如果有一个属性名字是 database.password,那么它在 /env 中的显示效果是这样的:

"database.password":"******"

/env 端点还能用来获取单个属性的值,只需要在请求时在 /env 后加上属性名即可。举例来说,对阅读列表应用程序发起 /env/amazon.associate_id 请求,获得的结果是 habuma-20(纯文本形式)。

回想第 3 章,这些环境属性可以通过 @ConfigurationProperties 注解很方便地使用。这些环境属性会注入带有 @ConfigurationProperties 注解的 Bean 的实例属性。/configprops 端点会生成一个报告,说明如何进行设置(注入或其他方式)。代码清单7-4是阅读列表应用程序的配置属性报告片段。

代码清单7-4 配置属性报告

片段中的第一个内容是我们在第 3 章里创建的 amazonProperties Bean。报告显示它添加了 @ConfigurationProperties 注解,前缀为 amazon。associateId 属性设置为 habuma-20。这是因为在 application.yml 里,我们把 amazon.associateId 属性设置成了 habuma-20。

你还会看到一个 serverProperties 条目(前缀是 server),还有一些属性。它们都有默认值,你也可以通过设置 server 前缀的属性来改变这些值。举例来说,你可以通过设置 server.port 属性来修改服务器监听的端口。

除了展现运行中应用程序的配置属性如何设置,这个报告也能作为一个快速参考指南,告诉你有哪些属性可以设置。例如,如果你不清楚怎么设置嵌入式 Tomcat 服务器的最大线程数,可以看一下配置属性报告,里面会有一条 server.tomcat.maxThreads,这就是你要找的属性。

生成端点到控制器的映射

在应用程序相对较小的时候,很容易搞清楚控制器都映射到了哪些端点上。如果 Web 界面的控制器和请求处理方法数量多,那最好能有一个列表,罗列出应用程序发布的全部端点。

/mappings 端点就提供了这么一个列表。代码清单7-5是阅读列表应用程序的映射报告片段。

代码清单7-5 阅读列表应用程序的控制器/端点映射

这里我们可以看到不少端点的映射。每个映射的键都是一个字符串,其内容就是 Spring MVC 的 @RequestMapping 注解上设置的属性。实际上,这个字符串能让你清晰地了解控制器是如何映射的,哪怕不看源代码。每个映射的值都有两个属性:bean 和 method。bean 属性标识了 Spring Bean 的名字,映射源自这个 Bean。method 属性是映射对应方法的全限定方法签名。

头两个映射关乎应用程序中 ReadingListController 的请求如何处理。第一个表明 readersBooks() 方法处理根路径(/)的 HTTP GET 请求。第二个表明 POST 请求映射到 addToReadingList() 方法上。

接下来的映射是 Actuator 提供的端点。/autoconfig 端点的 HTTP GET 请求由 Spring Boot 的 EndpointMvcAdapter 类的 invoke() 方法来处理。当然,还有很多其他 Actuator 的端点没有列在代码清单7-5里,这种省略完全是为了简化代码示例。

Actuator 的配置端点能很方便地让你了解应用程序是如何配置的。能看到应用程序在运行时究竟发生了什么,这很有趣、很实用。度量端点能展示应用程序运行时内部状况的快照。

运行时度量

你到医生那里体检时,会做一系列检查来了解身体状况。有一些重要的项目永远不会变,比如血型。这类测试能让医生了解你身体的一贯情况。其他测试让医生掌握你接受检查时的身体状况。你的心律、血压和胆固醇水平有助于医生评估你的健康。这些指标都是临时的,很可能随时间发生变化,但它们同样是很有帮助的运行时指标。

与之类似,对运行时度量情况做一个快照,这对评估应用程序的健康情况很有帮助。Actuator 提供了一系列端点,让你能在运行时快速检查应用程序。让我们来了解一下这些端点,从 /metrics 开始。

查看应用程序的度量值

关于运行中的应用程序,有很多有趣而且有用的信息。举个例子,了解应用程序的内存情况(可用或空闲)有助于决定给 JVM 分配多少内存。对 Web 应用程序而言,不用查看 Web 服务器日志,如果请求失败或者是耗时太长,就可以大概知道内存的情况了。

运行中的应用程序有诸多计数器和度量器,/metrics 端点提供了这些东西的快照。代码清单7-6是 /metrics 端点输出内容的示例。

代码清单7-6 /metrics端点提供了很多有用的运行时数据
{
  mem: 198144,
  mem.free: 144029,
  processors: 8,
  uptime: 1887794,
  instance.uptime: 1871237,
  systemload.average: 1.33251953125,
  heap.committed: 198144,
  heap.init: 131072,
  heap.used: 54114,
  heap: 1864192,
  threads.peak: 21,
  threads.daemon: 19,
  threads: 21,
  classes: 9749,
  classes.loaded: 9749,
  classes.unloaded: 0,
  gc.ps_scavenge.count: 22,
  gc.ps_scavenge.time: 122,
  gc.ps_marksweep.count: 2,
  gc.ps_marksweep.time: 156,
  httpsessions.max: -1,
  httpsessions.active: 1,
  datasource.primary.active: 0,
  datasource.primary.usage: 0,
  counter.status.200.beans: 1,
  counter.status.200.env: 1,
  counter.status.200.login: 3,
  counter.status.200.metrics: 2,
  counter.status.200.root: 6,
  counter.status.200.star-star: 9,
  counter.status.302.login: 3,
  counter.status.302.logout: 1,
  counter.status.302.root: 5,
  gauge.response.beans: 169,
  gauge.response.env: 165,
  gauge.response.login: 3,
  gauge.response.logout: 0,
  gauge.response.metrics: 2,
  gauge.response.root: 11,
  gauge.response.star-star: 2
}

如你所见,/metrics 端点提供了很多信息,逐行查看这些度量值太麻烦。表7-2根据所提供信息的类型对它们做了个分类。

image 2024 03 22 16 33 27 477
Figure 2. 表7-2 /metrics端点报告的度量值和计数器

请注意,这里的一些度量值,比如数据源和 Tomcat 会话,仅在应用程序中运行特定组件时才有数据。你还可以注册自己的度量信息,7.4.3节里会提到这一点。HTTP 的计数器和度量值需要做一点说明。counter.status 后的值是 HTTP 状态码,随后是所请求的路径。举个例子,counter.status.200.metrics 表明 /metrics 端点返回 200(OK)状态码的次数。

HTTP 的度量信息在结构上也差不多,却在报告另一类信息。它们全部以 gauge.response 开头,表明这是 HTTP 响应的度量信息。前缀后是对应的路径。度量值是以毫秒为单位的时间,反映了最近处理该路径请求的耗时。举个例子,代码清单7-6里的 gauge.response.beans 说明上一次请求耗时 169 毫秒。

这里还有几个特殊的值需要注意。root 路径指向的是根路径或 /。star-star 代表了那些 Spring 认为是静态资源的路径,包括图片、JavaScript 和样式表,其中还包含了那些找不到的资源。这就是为什么你经常会看到 counter.status.404.star-star,这是返回了 HTTP 404 (NOT FOUND) 状态的请求数。

/metrics 端点会返回所有的可用度量值,但你也可能只对某个值感兴趣。要获取单个值,请求时可以在 URL 后加上对应的键名。例如,要查看空闲内存大小,可以向 /metrics/mem.free 发一个 GET 请求:

$ curl localhost:8080/metrics/mem.free
144029

要知道,虽然响应里的 Content-Type 头设置为 application/json; charset=UTF-8,但实际 /metrics/{name} 的结果是文本格式的。因此,如果需要的话,你也可以把它视为 JSON 来处理。

追踪Web请求

尽管 /metrics 端点提供了一些针对 Web 请求的基本计数器和计时器,但那些度量值缺少详细信息。知道所处理请求的更多信息是很有帮助的,尤其是在调试时,所以就有了 /trace 这个端点。

/trace 端点能报告所有 Web 请求的详细信息,包括请求方法、路径、时间戳以及请求和响应的头信息。代码清单7-7是 /trace 输出的一个片段,其中包含了整个请求跟踪项。

代码清单7-7 /trace端点会记录下Web请求的细节
[
  ...
  {
    "timestamp": 1426378239775,
    "info": {
      "method": "GET",
      "path": "/metrics",
      "headers": {
        "request": {
            "accept": "*/*",
            "host": "localhost:8080",
            "user-agent": "curl/7.37.1"
        },
        "response": {
            "X-Content-Type-Options": "nosniff",
            "X-XSS-Protection": "1; mode=block",
            "Cache-Control":
                      "no-cache, no-store, max-age=0, must-revalidate",
            "Pragma": "no-cache",
            "Expires": "0",
            "X-Frame-Options": "DENY",
            "X-Application-Context": "application",
            "Content-Type": "application/json; charset=UTF-8",
            "Transfer-Encoding": "chunked",
            "Date": "Sun, 15 Mar 2015 00:10:39 GMT",
            "status": "200"
        }
      }
    }
  }
]

正如 method 和 path 属性所示,你可以看到这个跟踪项是一个针对 /metrics 的请求。timestamp 属性(以及响应中的 Date 头)告诉了你请求的处理时间。headers 属性的内容是请求和响应中所携带的头信息。

虽然代码清单7-7里只显示了一条跟踪项,但 /trace 端点实际能显示最近 100 个请求的信息,包含对 /trace 自己的请求。它在内存里维护了一个跟踪库。稍后在7.4.4节里,你会看到如何创建一个自定义的跟踪库实现,以便将请求的跟踪持久化。

导出线程活动

在确认应用程序运行情况时,除了跟踪请求,了解线程活动也会很有帮助。/dump 端点会生成当前线程活动的快照。

完整的线程导出报告里会包含应用程序的每个线程。为了节省空间,代码清单7-8里只放了一个线程的内容片段。如你所见,其中包含很多线程的特定信息,还有线程相关的阻塞和锁状态。本例中,还有一个跟踪栈(stack trace),表明这是一个 Tomcat 容器线程。

代码清单7-8 /dump端点提供了应用程序线程的快照
[
  {
    "threadName": "container-0",
    "threadId": 19,
    "blockedTime": -1,
    "blockedCount": 0,
    "waitedTime": -1,
    "waitedCount": 64,
    "lockName": null,
    "lockOwnerId": -1,
    "lockOwnerName": null,
    "inNative": false,
    "suspended": false,
    "threadState": "TIMED_WAITING",
    "stackTrace": [
      {
        "className": "java.lang.Thread",
        "fileName": "Thread.java",
        "lineNumber": -2,
        "methodName": "sleep",
        "nativeMethod": true
      },
      {
        "className": "org.springframework.boot.context.embedded.
                              tomcat.TomcatEmbeddedServletContainer$1",
        "fileName": "TomcatEmbeddedServletContainer.java",
        "lineNumber": 139,
        "methodName": "run",
        "nativeMethod": false
      }
    ],
    "lockedMonitors": [],
    "lockedSynchronizers": [],
    "lockInfo": null
  },
  ...
]

监控应用程序健康情况

如果你想知道自己的应用程序是否在运行,可以直接访问 /health 端点。在最简单的情况下,该端点会显示一个简单的 JSON,内容如下:

{"status":"UP"}

status 属性显示了应用程序在运行中。当然,它的确在运行,此处的响应无关紧要,任何输出都说明这个应用程序在运行。但 /health 端点可以输出的信息远远不止简单的 UP 状态。

/health 端点输出的某些信息可能涉及内容,因此对未经授权的请求只能提供简单的健康状态。如果经过身份验证(比如你已经登录了),则可以提供更多信息。下面是阅读列表应用程序一些健康信息的示例:

{
  "status":"UP",
  "diskSpace": {
    "status":"UP",
    "free":377423302656,
    "threshold":10485760
  },
  "db":{
    "status":"UP",
    "database":"H2",
    "hello":1
  }
}

除了基本的健康状态,可用的磁盘空间以及应用程序正在使用的数据库状态也可以看到。

/health 端点所提供的所有信息都是由一个或多个健康指示器提供的。表7-3列出了 Spring Boot 自带的健康指示器。

image 2024 03 22 16 40 56 582
Figure 3. 表7-3 Spring Boot自带的健康指示器

这些健康指示器会按需自动配置。举例来说,如果 Classpath 里有 javax.sql.DataSource,则会自动配置 DataSourceHealthIndicator。ApplicationHealthIndicator 和 Disk-SpaceHealthIndicator 则会一直配置着。

除了这些自带的健康指示器,你还会在7.4.5节里看到如何创建自定义健康指示器。

关闭应用程序

假设你要关闭运行中的应用程序。比方说,在微服务架构中,你有多个微服务应用的实例运行在云上,其中某个实例有问题了,你决定关闭该实例并让云服务提供商为你重启这个有问题的应用程序。在这个场景中,Actuator 的 /shutdown 端点就很有用了。

为了关闭应用程序,你要往 /shutdown 发送一个 POST 请求。例如,可以用命令行工具 curl 来关闭应用程序:

$ curl -X POST http://localhost:8080/shutdown

很显然,关闭运行中的应用程序是件危险的事情,因此这个端点默认是关闭的。如果没有显式地开启这个功能,那么 POST 请求的结果是这样的:

{"message":"This endpoint is disabled"}

要开启该端点,可以将 endpoints.shutdown.enabled 设置为 true。举例来说,可以把如下内容加入 application.yml,借此开启 /shutdown 端点:

endpoints:
  shutdown:
    enabled: true

打开 /shutdown 端点后,你要确保并非任何人都能关闭应用程序。这时应该保护 /shutdown 端点,只有经过授权的用户能关闭应用程序。在7.5节里你将看到如何保护 Actuator 端点。

获取应用信息

Spring Boot Actuator 还有一个有用的端点。/info 端点能展示各种你希望发布的应用信息。针对该端点的 GET 请求的默认响应是这样的:

{}

很显然,一个空的 JSON 对象没什么用。但你可以通过配置带有 info 前缀的属性向 /info 端点的响应添加内容。例如,你希望在响应中添加联系邮箱。可以在 application.yml 里设置名为 info.contactEmail 的属性:

info:
  contactEmail: support@myreadinglist.com

现在再访问 /info 端点,就能得到如下响应:

{
  "contactEmail":"support@myreadinglist.com"
}

这里的属性也可以是嵌套的。例如,假设你希望提供联系邮箱和电话。在 application.yml 里可以配置如下属性:

info:
  contact:
    email: support@myreadinglist.com
    phone: 1-888-555-1971

/info 端点返回的 JSON 会包含一个 contact 属性,其中有 email 和 phone 属性:

{
  "contact":{
    "email":"support@myreadinglist.com",
    "phone":"1-888-555-1971"
  }
}

向 /info 端点添加属性只是定制 Actuator 行为的众多方式之一。稍后在7.4节里,我们还会看到其他配置与扩展 Actuator 的方式。但现在,先让我们来看看如何保护 Actuator 的端点。