Actuator metrics 端点
一句话定位:Spring Boot Actuator 的
/actuator/metrics端点通过集成 Micrometer 计量库,将 JVM 内存、线程、HTTP 请求、数据源等运行时指标统一暴露为结构化数据,是连接应用与 Prometheus、Datadog 等监控系统的数据源头。
定义与作用
在传统 Java 应用中,获取运行时指标通常有两种途径:一是通过 JMX(如 jconsole 连接 MBean),二是开发者在业务代码中手动埋点计数。JMX 对运维不够友好,手动埋点则重复劳动多、命名不规范、难以聚合。
Spring Boot Actuator 引入 Micrometer 作为指标门面(Facade),自动采集以下维度的指标:
| 指标命名空间 | 采集内容 | 自动注册条件 |
|---|---|---|
jvm.memory.used | JVM 已使用内存(堆/非堆) | 始终 |
jvm.gc.pause | 垃圾回收暂停时间与次数 | 始终 |
jvm.threads.live | 当前存活线程数 | 始终 |
http.server.requests | HTTP 请求数、耗时、结果分布 | 存在 Web 环境 |
system.cpu.usage | 系统 CPU 使用率 | 始终 |
process.uptime | 进程运行时长 | 始终 |
jdbc.connections.active | 活跃数据库连接数 | 存在 DataSource |
cache.gets | 缓存命中/未命中次数 | 存在 CacheManager |
Micrometer 的定位:类似于 SLF4J 是日志门面,Micrometer 是指标门面。它定义了统一的指标 API(Counter、Timer、Gauge、DistributionSummary),底层可适配 Prometheus、InfluxDB、CloudWatch 等后端注册表。Spring Boot 2.x 默认将 Micrometer 与 Actuator 绑定,无需额外引入。
适用位置与常用属性
metrics 端点由 spring-boot-starter-actuator 自动引入,无需代码注解。但如要将指标推送/拉取到外部监控系统,需额外配置或引入桥接包。
常用配置属性
# application.yml
management:
server:
port: 8081
endpoints:
web:
exposure:
include: "health,info,metrics,prometheus"
endpoint:
metrics:
enabled: true
prometheus:
enabled: true
metrics:
tags:
application: ${spring.application.name:student-app} # 全局标签
distribution:
percentiles-histogram:
http.server.requests: true # 暴露 HTTP 请求耗时分位直方图
slo:
http.server.requests: 50ms,100ms,200ms,500ms,1s,5s # 服务等级目标
enable:
jvm: true
process: true
system: true
tomcat: true
| 属性 | 说明 | 典型值 |
|---|---|---|
management.endpoints.web.exposure.include | 暴露的 HTTP 端点 | metrics,prometheus |
management.metrics.tags.* | 为所有指标附加全局标签 | application=student-app |
management.metrics.distribution.percentiles-histogram.* | 开启分位直方图 | true |
management.metrics.distribution.slo.* | 定义服务等级目标边界 | 50ms,100ms,200ms |
management.metrics.enable.* | 按命名空间开关指标 | true / false |
Prometheus 格式:若引入
micrometer-registry-prometheus依赖,Actuator 会额外暴露/actuator/prometheus端点,返回 Prometheus 可抓取的文本格式。
核心原理
Micrometer 指标采集与暴露流程
流程解读:
- 应用启动时,
MeterRegistry(默认CompositeMeterRegistry)被创建。它可同时持有多个后端注册表(如 Simple、Prometheus)。 - Spring Boot 自动注册器(如
JvmMetrics、WebMvcMetricsFilter)向MeterRegistry注册各类Meter对象。 - 业务运行时,自动过滤器记录 HTTP 请求耗时,JVM 内部线程更新内存数据,自定义代码调用
counter.increment(),所有这些操作最终写入MeterRegistry。 - 当外部访问
/actuator/metrics时,MetricsEndpoint从MeterRegistry读取当前快照,格式化为 JSON 返回。
指标类型与数据结构
完整示例
场景说明
飞翔科技的学生成绩管理系统上线后,架构师白歌要求小崔监控以下运行时指标:JVM 内存使用、HTTP 接口的 QPS 与平均响应时间。小崔需要确认 Actuator 的 metrics 端点能正确暴露这些数据,并验证分位直方图功能。
操作前:未引入监控指标
# 操作前:application.yml
spring:
application:
name: student-app
server:
port: 8080
此时项目中没有 spring-boot-starter-actuator,访问任何 /actuator/* 均返回 404。小崔只能通过操作系统命令查看进程内存,无法精确获取 JVM 堆内存和 HTTP 请求指标。
使用该特性的完整代码
步骤一:在 pom.xml 中引入 Actuator(以及可选的 Prometheus 桥接):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 可选:Prometheus 格式输出 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
步骤二:在 application.yml 中配置指标暴露:
spring:
application:
name: student-app
server:
port: 8080
management:
server:
port: 8081
endpoints:
web:
exposure:
include: "health,info,metrics,prometheus"
metrics:
tags:
application: ${spring.application.name}
distribution:
percentiles-histogram:
http.server.requests: true
slo:
http.server.requests: 50ms,100ms,200ms,500ms
步骤三:提供一个简单的 Controller 产生 HTTP 请求样本(仅用于验证指标,非教程重点):
package com.feixiang.student.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StudentController {
@GetMapping("/students/{id}")
public String getStudent(@PathVariable Long id) {
return "学生 " + id + " 成绩查询成功";
}
}
操作后运行结果及分析
启动应用后,先产生若干 HTTP 请求:
$ curl http://localhost:8080/students/2024001
$ curl http://localhost:8080/students/2024002
$ curl http://localhost:8080/students/2024003
访问指标列表:
$ curl http://localhost:8081/actuator/metrics
返回结果(节选):
{
"names": [
"jvm.memory.used",
"jvm.memory.committed",
"jvm.gc.pause",
"jvm.threads.live",
"process.uptime",
"system.cpu.usage",
"http.server.requests",
"tomcat.sessions.active.current"
]
}
查询具体内存指标:
$ curl http://localhost:8081/actuator/metrics/jvm.memory.used
返回结果:
{
"name": "jvm.memory.used",
"description": "The amount of used memory",
"baseUnit": "bytes",
"measurements": [
{"statistic": "VALUE", "value": 68730384}
],
"availableTags": [
{"tag": "area", "values": ["heap", "nonheap"]},
{"tag": "id", "values": ["G1 Old Gen", "G1 Survivor Space", "CodeHeap 'non-nmethods'", "Metaspace", "CompressedClassSpace", "G1 Eden Space", "CodeHeap 'profiled methods'", "CodeHeap 'non-profiled methods'"]}
]
}
查询 HTTP 请求指标:
$ curl http://localhost:8081/actuator/metrics/http.server.requests
返回结果:
{
"name": "http.server.requests",
"description": null,
"baseUnit": "seconds",
"measurements": [
{"statistic": "COUNT", "value": 3},
{"statistic": "TOTAL_TIME", "value": 0.045}
],
"availableTags": [
{"tag": "exception", "values": ["None"]},
{"tag": "method", "values": ["GET"]},
{"tag": "outcome", "values": ["SUCCESS"]},
{"tag": "status", "values": ["200"]},
{"tag": "uri", "values": ["/students/{id}"]}
]
}
Prometheus 格式输出:
$ curl http://localhost:8081/actuator/prometheus | grep http_server_requests
返回结果:
http_server_requests_seconds_count{application="student-app",exception="None",method="GET",outcome="SUCCESS",status="200",uri="/students/{id}",} 3.0
http_server_requests_seconds_sum{application="student-app",exception="None",method="GET",outcome="SUCCESS",status="200",uri="/students/{id}",} 0.045
http_server_requests_seconds_bucket{application="student-app",exception="None",method="GET",outcome="SUCCESS",status="200",uri="/students/{id}",le="0.05",} 2.0
http_server_requests_seconds_bucket{application="student-app",exception="None",method="GET",outcome="SUCCESS",status="200",uri="/students/{id}",le="0.1",} 3.0
分析:
jvm.memory.used自动暴露:包含area(heap/nonheap)和id(各内存区域)标签,可精确到具体内存池。http.server.requests自动暴露:COUNT为请求总数,TOTAL_TIME为总耗时。availableTags包含method、status、uri等维度,支持 Prometheus 的多维聚合查询。percentiles-histogram生效:Prometheus 格式中出现了seconds_bucket直方图数据,可用于计算 P50、P95、P99 分位值。- 全局标签
application=student-app生效:所有指标自动携带应用名标签,在多应用混部环境中便于区分数据来源。
易错场景与面试考点
易错场景一:指标名称拼写错误导致 404
小崔想查看 JVM 线程数,误将指标名写错:
$ curl http://localhost:8081/actuator/metrics/jvm.thread.live
返回:
{
"timestamp": "2024-05-20T12:00:00",
"status": 404,
"error": "Not Found",
"message": "No metric named jvm.thread.live found"
}
原因:正确名称是 jvm.threads.live(threads 为复数),而非 jvm.thread.live。
正确做法:不确定指标名时,先访问 /actuator/metrics 获取完整列表,或使用通配思路搜索。Micrometer 的指标命名遵循规范(复数形式、点分隔),记住常见命名空间即可:jvm.*、process.*、system.*、http.server.requests、jdbc.connections.*。
易错场景二:未引入 Prometheus 桥接却访问 /actuator/prometheus
$ curl http://localhost:8081/actuator/prometheus
返回 404。小崔误以为 include: "prometheus" 配置就够了。
原因:prometheus 端点是额外依赖 micrometer-registry-prometheus 提供的,仅配置暴露列表不会自动创建该端点。
正确做法:POM 中引入对应桥接包:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
面试考点
Q:/actuator/metrics 和 /actuator/prometheus 有什么区别?
/actuator/metrics返回 JSON 格式的指标列表或单个指标的详细测量值,适合人阅读和调试。/actuator/prometheus返回 Prometheus 服务端可抓取的文本格式(含 HELP、TYPE、分桶数据),适合机器解析和时序数据库存储。
Q:Micrometer 的 Counter 和 Gauge 有什么区别?
Counter只能单调递增,记录事件总数(如请求数、错误数),支持increment()。Gauge记录瞬时值,可增可减(如内存使用量、队列长度),通常通过回调函数实时获取底层值。两者不可互相替代。
Q:Spring Boot 2.7.x 如何开启 HTTP 请求耗时直方图?
在
application.yml中设置management.metrics.distribution.percentiles-histogram.http.server.requests: true。开启后,Micrometer 会自动按桶边界(默认或自定义 SLO)记录分布,Prometheus 输出中会出现*_bucket系列数据。
Q:为什么 JVM 内存指标有 area 和 id 两个标签?
area区分大区域(heap堆内存 vsnonheap非堆内存),id精确到具体内存池(如G1 Eden Space、Metaspace)。这种层级标签设计让监控查询既可以看整体,也可以下钻到具体区域。