Spring Boot 中基于 Actuator + Micrometer + Prometheus 的应用监控实战与告警落地
做 Spring Boot 服务时,很多团队一开始都把“监控”理解成两件事:服务活着、日志能看。可一旦业务量上来,这两件事远远不够。
你会遇到这些很真实的问题:
- 接口明明没报错,但 RT 一直升高
- 某个实例 CPU 飙了,为什么?
- 线程池积压了多久?
- 数据库连接池是不是快耗尽了?
- 某个版本上线后,错误率是不是在缓慢变高?
- 服务还“活着”,但其实已经“不健康”了
这时候,应用指标监控就不是锦上添花,而是线上稳定性的基础设施。
这篇文章我会带你从一个可运行的 Spring Boot 示例出发,完成这样一条链路:
Spring Boot 应用 → Actuator 暴露指标 → Micrometer 统一采集 → Prometheus 抓取存储 → 告警规则落地
我会尽量按“带着你走一遍”的方式来写,重点不是背概念,而是把它真正跑起来,并知道常见坑怎么排。
背景与问题
在 Spring Boot 生态里,应用监控常见有三层:
- 健康检查:服务是否可用
- 指标采集:QPS、RT、错误率、JVM、线程池、连接池等
- 告警落地:指标异常时通知人
很多项目已经用了 spring-boot-starter-actuator,但仅停留在 /actuator/health。这只能回答“活没活”,却回答不了“跑得怎么样”。
而线上运维真正关心的是:
- 请求量是否突增?
- 延迟是否恶化?
- 错误是否集中爆发?
- 内存和 GC 是否异常?
- 某个依赖是否拖慢整体链路?
所以这篇文章要解决的问题是:
如何在 Spring Boot 中,用 Actuator + Micrometer + Prometheus 搭建一套可观测、可告警、可排查的应用监控体系?
前置知识与环境准备
本文示例环境:
- JDK 17
- Spring Boot 3.x
- Maven 3.8+
- Prometheus 2.x
建议你具备这些基础:
- 会写基本的 Spring Boot Web 接口
- 知道 Maven 依赖怎么加
- 对 Prometheus 的“拉取模式”有基础印象
本文示例目标
我们要实现以下内容:
- 暴露 Actuator 端点
- 输出 Prometheus 可识别的 metrics
- 自定义业务指标
- 接入线程池/接口耗时监控
- 编写 Prometheus 抓取配置
- 编写基础告警规则
- 理解常见问题和生产注意事项
核心原理
先别急着写代码,先把这三个角色分清楚。
1. Actuator 是“暴露管理与观测端点”的入口
Spring Boot Actuator 提供一组端点,比如:
/actuator/health/actuator/info/actuator/metrics/actuator/prometheus
它像是应用自带的“运维观察窗”。
2. Micrometer 是“指标抽象层”
Micrometer 可以理解为 Java 应用里的指标门面,它统一了这些指标概念:
- Counter:累计计数
- Gauge:瞬时值
- Timer:耗时统计
- DistributionSummary:分布统计
它不直接替代 Prometheus,而是屏蔽不同监控后端的差异。
你只需要针对 MeterRegistry 上报指标,后面接 Prometheus、InfluxDB 还是别的后端,代码风格都差不多。
3. Prometheus 是“抓取、存储、查询、告警”的后端
Prometheus 会定时拉取你的 /actuator/prometheus,把指标存下来。
然后通过 PromQL 做查询和告警判断。
一图看懂整体链路
flowchart LR
A[Spring Boot 应用] --> B[Actuator 端点]
B --> C[Micrometer 指标注册]
C --> D[/actuator/prometheus]
E[Prometheus] -->|定时抓取| D
E --> F[告警规则 Alert Rules]
F --> G[通知渠道]
指标从请求到告警的过程
sequenceDiagram
participant U as 用户请求
participant S as Spring Boot 服务
participant M as Micrometer
participant P as Prometheus
participant A as Alert Rule
U->>S: 调用接口 /order/create
S->>M: 记录请求次数、耗时、状态码
P->>S: 抓取 /actuator/prometheus
S-->>P: 返回指标文本
P->>A: 执行 PromQL 规则
A-->>P: 满足阈值则触发告警
核心指标怎么理解
在实践中,我建议你先抓住 4 类指标,不要一上来铺太多:
1. 流量指标
例如:
- 请求总数
- 每秒请求数(QPS)
- 按接口/状态码维度统计
2. 延迟指标
例如:
- 平均响应时间
- P95 / P99 延迟
- 某个接口耗时异常
3. 错误指标
例如:
- 5xx 次数
- 错误率
- 业务异常计数
4. 资源指标
例如:
- JVM 内存
- GC 次数与停顿时间
- CPU
- 线程池活跃线程数
- 数据库连接池使用率
实战代码(可运行)
下面直接搭一个最小可运行示例。
第一步:创建项目并添加依赖
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>monitor-demo</artifactId>
<version>1.0.0</version>
<name>monitor-demo</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Prometheus registry -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- 可选:校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
第二步:配置 Actuator 与指标暴露
src/main/resources/application.yml
server:
port: 8080
spring:
application:
name: monitor-demo
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
prometheus:
metrics:
export:
enabled: true
metrics:
tags:
application: ${spring.application.name}
distribution:
percentiles-histogram:
http.server.requests: true
percentiles:
http.server.requests: 0.5,0.95,0.99
这里有几个点很关键:
exposure.include:决定哪些端点可以通过 HTTP 访问prometheus enabled:开启 Prometheus 指标导出management.metrics.tags.application:给所有指标统一打上应用名标签percentiles-histogram:允许 Prometheus 统计接口延迟分位数
我自己在项目里经常漏掉最后这块,结果 PromQL 写 histogram_quantile 时发现数据不全,排查半天才发现直方图没开。
第三步:启动类
src/main/java/com/example/monitordemo/MonitorDemoApplication.java
package com.example.monitordemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MonitorDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MonitorDemoApplication.class, args);
}
}
第四步:写一个业务接口,并注入自定义指标
4.1 自定义指标服务
src/main/java/com/example/monitordemo/metrics/OrderMetricsService.java
package com.example.monitordemo.metrics;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class OrderMetricsService {
private final Counter successCounter;
private final Counter failedCounter;
private final Timer createOrderTimer;
public OrderMetricsService(MeterRegistry meterRegistry) {
this.successCounter = Counter.builder("biz_order_create_total")
.description("订单创建成功总次数")
.tag("biz", "order")
.tag("result", "success")
.register(meterRegistry);
this.failedCounter = Counter.builder("biz_order_create_total")
.description("订单创建失败总次数")
.tag("biz", "order")
.tag("result", "failed")
.register(meterRegistry);
this.createOrderTimer = Timer.builder("biz_order_create_duration")
.description("订单创建耗时")
.tag("biz", "order")
.publishPercentileHistogram()
.register(meterRegistry);
}
public void recordSuccess(long millis) {
successCounter.increment();
createOrderTimer.record(millis, TimeUnit.MILLISECONDS);
}
public void recordFailed(long millis) {
failedCounter.increment();
createOrderTimer.record(millis, TimeUnit.MILLISECONDS);
}
}
4.2 模拟业务接口
src/main/java/com/example/monitordemo/controller/OrderController.java
package com.example.monitordemo.controller;
import com.example.monitordemo.metrics.OrderMetricsService;
import jakarta.validation.constraints.Min;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ThreadLocalRandom;
@RestController
@Validated
public class OrderController {
private final OrderMetricsService orderMetricsService;
public OrderController(OrderMetricsService orderMetricsService) {
this.orderMetricsService = orderMetricsService;
}
@GetMapping("/order/create")
public String createOrder(@RequestParam(defaultValue = "100") @Min(1) int sleepMs,
@RequestParam(defaultValue = "false") boolean fail) throws InterruptedException {
long start = System.currentTimeMillis();
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(sleepMs, sleepMs + 50));
if (fail) {
throw new IllegalStateException("模拟订单创建失败");
}
long cost = System.currentTimeMillis() - start;
orderMetricsService.recordSuccess(cost);
return "ok";
} catch (Exception e) {
long cost = System.currentTimeMillis() - start;
orderMetricsService.recordFailed(cost);
throw e;
}
}
@GetMapping("/ping")
public String ping() {
return "pong";
}
}
第五步:给异常一个明确返回,方便观察状态码
src/main/java/com/example/monitordemo/controller/GlobalExceptionHandler.java
package com.example.monitordemo.controller;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Map<String, Object> handleException(Exception e) {
return Map.of(
"code", 500,
"message", e.getMessage()
);
}
}
第六步:运行并验证指标
启动项目后,依次访问:
健康检查
curl http://localhost:8080/actuator/health
Prometheus 指标出口
curl http://localhost:8080/actuator/prometheus
模拟成功请求
curl "http://localhost:8080/order/create?sleepMs=120"
模拟失败请求
curl "http://localhost:8080/order/create?sleepMs=200&fail=true"
再看 Prometheus 出口:
curl http://localhost:8080/actuator/prometheus | grep biz_order
你会看到类似输出:
biz_order_create_total{application="monitor-demo",biz="order",result="success",} 3.0
biz_order_create_total{application="monitor-demo",biz="order",result="failed",} 1.0
biz_order_create_duration_seconds_count{application="monitor-demo",biz="order",} 4.0
biz_order_create_duration_seconds_sum{application="monitor-demo",biz="order",} 0.587
第七步:接入线程池指标
业务里线程池往往是问题高发区。
如果任务积压、活跃线程打满、队列堆积,接口慢是必然的。
下面演示如何注册线程池指标。
src/main/java/com/example/monitordemo/config/ThreadPoolConfig.java
package com.example.monitordemo.config;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Configuration
public class ThreadPoolConfig {
@Bean(destroyMethod = "shutdown")
public ThreadPoolExecutor orderExecutor(MeterRegistry meterRegistry) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
4,
8,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100)
);
ExecutorServiceMetrics.monitor(meterRegistry, executor, "order_executor");
return executor;
}
}
然后写个接口模拟异步任务。
src/main/java/com/example/monitordemo/controller/AsyncController.java
package com.example.monitordemo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.ThreadPoolExecutor;
@RestController
public class AsyncController {
private final ThreadPoolExecutor orderExecutor;
public AsyncController(ThreadPoolExecutor orderExecutor) {
this.orderExecutor = orderExecutor;
}
@GetMapping("/async/task")
public String submitTask() {
orderExecutor.submit(() -> {
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(500, 1500));
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
});
return "submitted";
}
}
触发几次后,你可以在 /actuator/prometheus 中搜这些指标:
executor_active_threadsexecutor_pool_size_threadsexecutor_queued_tasksexecutor_completed_tasks_total
第八步:配置 Prometheus 抓取
新建 prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'spring-boot-demo'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['localhost:8080']
labels:
app: 'monitor-demo'
如果你本地已经有 Prometheus,可直接启动:
prometheus --config.file=prometheus.yml
启动后访问:
http://localhost:9090
在 Prometheus 中试几个查询。
查询 HTTP 请求总数
http_server_requests_seconds_count
查询最近 1 分钟订单成功速率
rate(biz_order_create_total{result="success"}[1m])
查询最近 5 分钟订单失败速率
rate(biz_order_create_total{result="failed"}[5m])
查询接口 P95 延迟
histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri, method))
这里提醒一句:
uri 标签在高动态路径场景要特别小心,比如 /order/123456、/order/987654 这种。如果路径模板没归一,标签基数会炸,Prometheus 很容易吃不消。
告警落地:从“有指标”到“能通知”
很多文章写到 Prometheus 查询就结束了,但真正落地监控,最关键的是告警。
先写一份基础规则。
alert-rules.yml
groups:
- name: spring-boot-alerts
rules:
- alert: InstanceDown
expr: up{job="spring-boot-demo"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "实例不可用"
description: "Prometheus 连续 1 分钟抓取失败"
- alert: HighHttp5xxErrorRate
expr: |
(
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
/
sum(rate(http_server_requests_seconds_count[5m]))
) > 0.05
for: 3m
labels:
severity: warning
annotations:
summary: "HTTP 5xx 错误率过高"
description: "最近 5 分钟 5xx 错误率超过 5%"
- alert: HighApiLatencyP95
expr: |
histogram_quantile(
0.95,
sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri, method)
) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "接口 P95 延迟过高"
description: "最近 5 分钟 P95 延迟超过 1 秒"
- alert: ThreadPoolQueueBacklog
expr: executor_queued_tasks{executor="order_executor"} > 50
for: 2m
labels:
severity: warning
annotations:
summary: "线程池队列积压"
description: "order_executor 排队任务超过 50"
然后在 prometheus.yml 中引入:
rule_files:
- alert-rules.yml
告警设计思路:不要只盯“阈值”,还要看“持续时间”
我建议你设计告警规则时遵守两个原则:
1. 阈值 + 持续时间
不要因为一个瞬时抖动就报警。
比如 P95 延迟偶发升到 1.2 秒,但只持续 10 秒,不一定值得半夜把人叫醒。
所以一般要加:
expr: 超过阈值for: 持续多久再报警
2. 分级
最简单可以分成:
critical:实例挂了、核心接口不可用、错误率极高warning:RT 高、线程池积压、连接池偏高
这样团队处理起来更有优先级。
监控对象分类图
classDiagram
class 应用监控 {
HTTP请求指标
JVM指标
线程池指标
业务指标
依赖资源指标
}
class HTTP请求指标 {
QPS
错误率
P95/P99
}
class JVM指标 {
Heap使用量
GC次数
线程数
}
class 线程池指标 {
活跃线程
队列长度
完成任务数
}
class 业务指标 {
下单成功数
下单失败数
支付超时数
}
应用监控 --> HTTP请求指标
应用监控 --> JVM指标
应用监控 --> 线程池指标
应用监控 --> 业务指标
逐步验证清单
如果你是第一次搭这套链路,建议按下面顺序验证,不要一步到位。
应用侧
-
/actuator/health可访问 -
/actuator/prometheus可访问 - 自定义指标出现在输出里
- 接口访问后
http_server_requests_seconds_*有数据 - 线程池指标可见
Prometheus 侧
-
Status -> Targets中目标状态为UP - 能查询
up - 能查询
http_server_requests_seconds_count - 能查询你的自定义业务指标
- 告警规则加载成功
告警侧
- 故意制造 5xx
- 故意制造高延迟
- 观察告警是否按
for时间触发 - 恢复后观察告警是否自动消除
常见坑与排查
这部分很重要,我自己在项目里踩过不少。
1. /actuator/prometheus 访问不到
现象
返回 404 或 403。
排查
先看配置是否包含:
management:
endpoints:
web:
exposure:
include: prometheus
还要确认依赖是否加了:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
如果用了 Spring Security,通常还要放行 Actuator 端点。
2. Prometheus 抓不到目标
现象
Prometheus Targets 页面显示 DOWN。
排查思路
- 应用是否真的启动在
8080 metrics_path是否写对- 容器或服务器网络是否可达
- 防火墙/安全组是否放行
- 是否用了独立 management 端口却还在抓业务端口
如果配置了:
management:
server:
port: 8081
那 Prometheus 就应该抓 8081/actuator/prometheus,不是 8080。
3. 指标有了,但 PromQL 查不到分位数
现象
histogram_quantile 没结果,或者结果不稳定。
原因
大概率是没有开启 Histogram。
检查:
management:
metrics:
distribution:
percentiles-histogram:
http.server.requests: true
对于自定义 Timer,也要确保调用了:
.publishPercentileHistogram()
4. 标签过多,Prometheus 内存上涨
现象
Prometheus 查询变慢,内存持续变大。
典型原因
高基数标签。
比如这些都要尽量避免:
- 用户 ID
- 订单 ID
- 请求唯一流水号
- 原始 URL 动态路径
错误示例:
Counter.builder("biz_request_total")
.tag("userId", userId)
.register(meterRegistry);
这是监控系统里非常危险的写法。
监控不是日志,不能把每个请求实例都塞成标签。
5. 告警太多,团队开始“免疫”
现象
告警群天天响,但真正的问题反而没人看。
建议
- 先从少量高价值规则开始
- 严格区分 warning / critical
- 业务低峰期做规则压测
- 给每条告警写明确处理手册
- 避免“单实例偶发毛刺”级别的告警直接通知全员
我见过最典型的坑就是“任何 5xx 都报警”,结果一个短暂重试失败就开始刷屏。
最后大家都把群静音,真正故障来时也没人理。
6. 自定义指标重复注册
现象
启动时报 meter already exists,或者指标行为异常。
原因
同名指标 + 相同标签组合重复注册。
建议
- 在单例 Bean 中初始化指标
- 避免在请求方法里动态
register - 标签维度保持稳定
错误姿势通常像这样:
@GetMapping("/bad")
public String bad(MeterRegistry registry) {
Counter.builder("test_counter").register(registry).increment();
return "ok";
}
这会在每次请求时尝试注册,早晚出问题。
安全/性能最佳实践
监控系统本身也会带来成本,所以生产上要做约束。
1. 不要把所有 Actuator 端点都暴露到公网
推荐只暴露必要端点:
healthinfoprometheus
并且尽量通过以下方式保护:
- 内网访问
- 网关鉴权
- Spring Security
- Service Mesh / 网络策略
示例:
management:
endpoints:
web:
exposure:
include: health,info,prometheus
2. 生产环境优先使用独立 management 端口
这样做有几个好处:
- 业务流量和管理流量隔离
- 安全策略更容易控制
- 抓指标时不影响业务端口策略
示例:
management:
server:
port: 8081
3. 控制标签基数
这个我愿意多强调一遍:标签设计决定 Prometheus 能不能稳定跑下去。
适合当标签的维度通常是:
- 应用名
- 实例名
- 环境
- 接口模板
- 状态码
- 业务类型枚举
不适合当标签的维度:
- 用户 ID
- 商品 ID
- 订单号
- Trace ID
- 请求时间戳
4. 只采集“对行动有帮助”的指标
监控不是越多越好,而是要能支持决策。
比如这些指标通常非常值得保留:
- HTTP 请求量、错误率、延迟
- JVM 内存与 GC
- 线程池队列长度与活跃线程
- 数据库连接池活跃连接
- 核心业务成功/失败次数
而一些“看起来很多,但没人会处理”的指标,可以先不采。
5. 抓取间隔要平衡实时性与成本
Prometheus 不是抓得越频繁越好。
常见建议:
- 普通业务服务:
15s或30s - 非核心服务:
30s或60s - 超高频抓取要确认指标量和存储压力
如果你的服务实例很多,指标又多,5s 抓一次很快就会把后端压住。
6. 给关键业务补充自定义指标
默认 JVM 和 HTTP 指标只能告诉你“系统层面怎么了”,
但很多时候你真正想知道的是“业务层面怎么了”。
比如电商系统里更有价值的可能是:
- 下单成功率
- 支付失败数
- 库存扣减冲突数
- 优惠券核销失败数
这些指标才更贴近业务 SLA。
一套实用的监控落地建议
如果你准备把这套方案放进项目,我建议分三步推进:
第 1 阶段:先把基础指标接起来
至少要有:
- health
- prometheus
- HTTP 请求指标
- JVM 指标
- 线程池指标
第 2 阶段:补齐业务指标
选择 2~3 个关键业务动作:
- 成功数
- 失败数
- 耗时
- 重试数
第 3 阶段:做告警收敛
优先上这些告警:
- 实例不可用
- 核心接口 5xx 错误率
- 核心接口 P95 延迟
- 线程池积压
- 连接池耗尽风险
这样最容易形成闭环,而不是“监控搭了很多,看的人很少”。
总结
用一句话概括这套方案:
Actuator 负责暴露观测入口,Micrometer 负责统一指标模型,Prometheus 负责抓取、查询与告警。
落到 Spring Boot 实战里,你真正要做的事情其实不复杂:
- 引入
actuator和micrometer-registry-prometheus - 暴露
/actuator/prometheus - 利用 Micrometer 补充业务指标
- 用 Prometheus 抓取并写 PromQL
- 把高价值告警规则落地
如果你现在正准备在项目里上监控,我建议你先别追求“大而全”,先把下面 4 件事做稳:
- 看得到 HTTP 请求量、错误率、P95
- 看得到 JVM 和线程池
- 有 2~3 个核心业务指标
- 有少量但可靠的告警规则
当这四件事跑顺了,你的应用监控体系就已经不再是“摆设”,而是真能在故障来临前给你信号。
如果要给一个边界条件建议,那就是:
- 小项目、低流量内部服务,不一定一开始就要上很复杂的监控大盘
- 但只要服务开始承载业务责任,最基本的指标和告警一定要有
- 监控建设的重点不是“采多少”,而是“出了问题能不能快速判断和行动”
这才是 Actuator + Micrometer + Prometheus 这套组合真正的价值。