Spring Boot 中级实战:基于 Actuator、Micrometer 与 Prometheus 搭建应用监控与告警体系
很多团队的服务上线后,日志打得不少,但真正出问题时,大家第一反应还是“先上机器看看”。这说明一件事:日志能解释发生了什么,监控才能告诉我们系统正在变坏。
这篇文章我会带你从一个 Spring Boot 应用出发,串起 Actuator、Micrometer、Prometheus 这三件套,搭建一个能落地的监控基础方案。目标不是讲概念大全,而是做出一个中级开发者在项目里能直接复用的最小闭环:
- 应用暴露健康检查和指标
- 自定义业务指标
- Prometheus 抓取指标
- 基于规则做告警
- 避开常见坑,控制安全与性能风险
如果你已经会写 Spring Boot 接口,但对监控体系还停留在“开个 /actuator 看看”的阶段,这篇会比较合适。
一、背景与问题
在没有监控体系时,Spring Boot 服务通常有这些典型痛点:
- 接口变慢了,但没人第一时间知道
- JVM 内存抖动、线程池打满,只能靠报警电话后排查
- 数据库连接池耗尽时,日志一堆,但没有趋势图
- 线上故障发生后,很难回答“从什么时候开始恶化”
- 业务指标缺失,比如下单成功率、消息消费堆积量,没有统一观测入口
很多人会把健康检查、日志、监控混在一起。实际上它们解决的问题不同:
- Actuator:给你暴露应用管理端点,比如健康状态、环境信息、指标端点
- Micrometer:给你统一的指标埋点 API,相当于 Spring 生态里的“度量门面”
- Prometheus:负责定时抓取指标、存储时序数据、执行告警规则
可以把它们理解成这样:
- Spring Boot 应用负责“产出指标”
- Prometheus 负责“采集和判断异常”
- 告警系统负责“通知人”
二、前置知识与环境准备
1. 适合的技术版本
本文示例基于以下组合,实践中比较常见:
- JDK 8+
- Spring Boot 2.x
- Maven 3.6+
- Prometheus 2.x
如果你使用的是 Spring Boot 3.x,整体思路不变,但个别配置项命名和依赖版本需要注意兼容性。
2. 需要的依赖
最小依赖就三类:
spring-boot-starter-webspring-boot-starter-actuatormicrometer-registry-prometheus
三、核心原理
先别急着写代码,先把链路想清楚。很多配置问题,本质上是因为没理解数据怎么流动。
1. 整体架构
flowchart LR
A[Spring Boot 应用] --> B[Actuator 端点]
A --> C[Micrometer 指标注册]
C --> D[/actuator/prometheus]
E[Prometheus] -->|定时抓取| D
E --> F[告警规则 Alert Rules]
F --> G[Alertmanager/通知系统]
这张图里最关键的一点是:
Prometheus 不是被应用“推送”的,而是主动“拉取”的。
所以应用要做的是:
- 通过 Micrometer 生成指标
- 通过 Actuator 暴露 Prometheus 可识别的文本格式指标
2. 指标从哪里来
在 Spring Boot 中,指标通常有三类来源:
(1)框架自动提供的系统指标
例如:
- JVM 内存、GC、线程
- CPU 使用
- HTTP 请求次数、耗时
- 数据源连接池
- Tomcat/Undertow 等容器指标
(2)组件集成后的指标
比如你接了:
- Redis
- Kafka
- 连接池 HikariCP
- Executor 线程池
很多组件已经和 Micrometer 打通,开箱即用。
(3)业务自定义指标
这是最有价值、也最容易被忽略的一类,比如:
- 订单创建成功次数
- 支付失败次数
- 用户注册耗时
- 消息消费积压量
- 第三方接口调用失败率
3. Micrometer 的常见指标类型
Micrometer 中最常见的几种指标要分清:
- Counter:只增不减,适合统计总次数
- Gauge:某一时刻的瞬时值,适合库存、队列长度、线程数
- Timer:统计耗时和调用次数
- DistributionSummary:统计数值分布,如消息大小、订单金额
我自己在项目里最常用的是:Counter + Timer + Gauge。这三种足够覆盖大部分业务监控。
4. 一次请求的指标流转过程
sequenceDiagram
participant U as 用户请求
participant S as Spring Boot
participant M as Micrometer
participant A as Actuator
participant P as Prometheus
U->>S: 调用 /orders/create
S->>M: 记录请求耗时、成功/失败计数
M-->>A: 注册并维护指标
P->>A: 定时 GET /actuator/prometheus
A-->>P: 返回指标文本
P->>P: 存储时序数据并评估告警规则
四、实战代码(可运行)
下面我们搭一个最小可运行示例:一个订单接口,暴露系统指标和业务指标,并让 Prometheus 抓取。
4.1 创建项目与依赖
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>boot-monitor-demo</artifactId>
<version>1.0.0</version>
<name>boot-monitor-demo</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<properties>
<java.version>8</java.version>
</properties>
<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>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.2 配置 Actuator 与 Prometheus 暴露端点
src/main/resources/application.yml:
server:
port: 8080
spring:
application:
name: boot-monitor-demo
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: when_authorized
metrics:
tags:
application: ${spring.application.name}
这里有几个重点:
include显式暴露prometheus端点- 给所有指标打一个统一标签
application health细节不要默认全开,后面会讲安全问题
4.3 启动类
src/main/java/com/example/demo/BootMonitorDemoApplication.java:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BootMonitorDemoApplication {
public static void main(String[] args) {
SpringApplication.run(BootMonitorDemoApplication.class, args);
}
}
4.4 编写业务指标埋点
我们做一个订单服务,统计:
- 下单成功总数
- 下单失败总数
- 下单耗时
- 当前处理中订单数(模拟中的 Gauge)
1. 服务类
src/main/java/com/example/demo/service/OrderService.java:
package com.example.demo.service;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Service
public class OrderService {
private final MeterRegistry meterRegistry;
private Counter orderSuccessCounter;
private Counter orderFailedCounter;
private Timer orderCreateTimer;
private final AtomicInteger processingOrders = new AtomicInteger(0);
public OrderService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@PostConstruct
public void initMetrics() {
orderSuccessCounter = Counter.builder("biz_order_create_success_total")
.description("订单创建成功次数")
.tag("service", "order")
.register(meterRegistry);
orderFailedCounter = Counter.builder("biz_order_create_failed_total")
.description("订单创建失败次数")
.tag("service", "order")
.register(meterRegistry);
orderCreateTimer = Timer.builder("biz_order_create_duration")
.description("订单创建耗时")
.tag("service", "order")
.publishPercentileHistogram()
.register(meterRegistry);
Gauge.builder("biz_order_processing_count", processingOrders, AtomicInteger::get)
.description("当前处理中订单数量")
.tag("service", "order")
.register(meterRegistry);
}
public String createOrder() {
long start = System.nanoTime();
processingOrders.incrementAndGet();
try {
mockBusiness();
orderSuccessCounter.increment();
return "create order success";
} catch (RuntimeException ex) {
orderFailedCounter.increment();
throw ex;
} finally {
long end = System.nanoTime();
orderCreateTimer.record(end - start, TimeUnit.NANOSECONDS);
processingOrders.decrementAndGet();
}
}
private void mockBusiness() {
try {
Thread.sleep(100 + new Random().nextInt(400));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
if (new Random().nextInt(10) < 2) {
throw new RuntimeException("mock order create failed");
}
}
}
2. Controller
src/main/java/com/example/demo/controller/OrderController.java:
package com.example.demo.controller;
import com.example.demo.service.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/orders/create")
public String create() {
return orderService.createOrder();
}
}
3. 全局异常处理,避免失败请求返回 500 时看不懂
src/main/java/com/example/demo/controller/GlobalExceptionHandler.java:
package com.example.demo.controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(RuntimeException ex) {
return "create order failed: " + ex.getMessage();
}
}
4.5 运行与验证
启动应用后,先访问:
curl http://localhost:8080/orders/create
多调用几次,再看指标端点:
curl http://localhost:8080/actuator/prometheus
你会看到类似输出:
# HELP biz_order_create_success_total 订单创建成功次数
# TYPE biz_order_create_success_total counter
biz_order_create_success_total{application="boot-monitor-demo",service="order",} 8.0
# HELP biz_order_create_failed_total 订单创建失败次数
# TYPE biz_order_create_failed_total counter
biz_order_create_failed_total{application="boot-monitor-demo",service="order",} 2.0
此外还会看到很多系统指标,例如:
jvm_memory_used_bytessystem_cpu_usagehttp_server_requests_seconds_count
这说明应用监控数据已经“产出来了”。
4.6 配置 Prometheus 抓取
新建 prometheus.yml:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'spring-boot-app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['localhost:8080']
如果你本地安装了 Prometheus,可直接启动:
prometheus --config.file=prometheus.yml
默认访问:
http://localhost:9090
然后在 Prometheus UI 中尝试查询:
biz_order_create_success_total
或者看过去 1 分钟失败增量:
increase(biz_order_create_failed_total[1m])
4.7 增加告警规则
一个能落地的监控方案,不能只停在“能看图”,还要能提醒。
比如我们定义两个最基础的告警:
- 最近 2 分钟有订单创建失败
- 应用实例不可用
alert-rules.yml:
groups:
- name: spring-boot-alerts
rules:
- alert: OrderCreateFailuresDetected
expr: increase(biz_order_create_failed_total[2m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "订单创建出现失败"
description: "最近 2 分钟内检测到订单创建失败"
- alert: SpringBootAppDown
expr: up{job="spring-boot-app"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Spring Boot 应用不可达"
description: "Prometheus 无法抓取 spring-boot-app 指标"
把规则文件加入 prometheus.yml:
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "alert-rules.yml"
scrape_configs:
- job_name: 'spring-boot-app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['localhost:8080']
完整告警链路如下:
flowchart TD
A[业务请求异常] --> B[Micrometer 指标变化]
B --> C[Prometheus 抓取到异常指标]
C --> D[Alert Rule 命中]
D --> E[Alertmanager]
E --> F[邮件/钉钉/企业微信/短信]
本文重点放在应用侧与 Prometheus 侧,Alertmanager 的通知模板与路由配置可以在后续单独展开。
五、逐步验证清单
这一部分非常实用,我建议你在本地按顺序验证,避免“一把梭全配完,不知道哪一步错了”。
第 1 步:确认 Actuator 已启用
访问:
curl http://localhost:8080/actuator
若返回可用端点列表,说明管理端点正常。
第 2 步:确认 Prometheus 端点暴露成功
访问:
curl http://localhost:8080/actuator/prometheus
如果返回文本格式指标,说明 Micrometer + Prometheus Registry 工作正常。
第 3 步:触发业务指标变化
连续调用:
for i in {1..10}; do curl http://localhost:8080/orders/create; echo; done
再搜索指标:
curl http://localhost:8080/actuator/prometheus | grep biz_order
第 4 步:确认 Prometheus 抓取状态
打开 Prometheus 页面:
http://localhost:9090/targets
看到 target 为 UP 才算采集成功。
第 5 步:验证 PromQL
尝试这些表达式:
up
biz_order_create_success_total
rate(http_server_requests_seconds_count[1m])
increase(biz_order_create_failed_total[5m])
六、常见坑与排查
这一段我尽量写得接地气一点,因为这些问题真的是大家最常踩的。
6.1 /actuator/prometheus 访问 404
常见原因
- 没引入
micrometer-registry-prometheus - 没暴露
prometheus端点 - Spring Boot 版本和依赖版本不兼容
排查方式
先看依赖是否存在,再看配置:
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
如果还是不行,建议查看启动日志中与 actuator、metrics 相关的自动配置输出。
6.2 Prometheus target 一直是 DOWN
常见原因
metrics_path配错了- 目标机器端口不通
- 应用绑定在
127.0.0.1,Prometheus 不在同一机器 - 容器环境下把
localhost当成宿主机地址了
排查思路
先在 Prometheus 所在机器执行:
curl http://目标地址:8080/actuator/prometheus
如果 curl 不通,就不是 Prometheus 配置问题,而是网络或服务暴露问题。
6.3 自定义指标有了名字,但值一直不变
常见原因
- 指标对象每次请求都重新创建
Gauge引用对象被 GC 回收- 实际代码路径没执行到埋点逻辑
我踩过的一个坑
很多人会在方法里直接这样写:
Counter.builder("demo_counter").register(registry).increment();
这不是绝对错,但如果你在高频路径里每次都注册一次,既浪费性能,也容易让代码变得混乱。更稳妥的做法是:
- 在初始化阶段注册指标
- 在业务执行时只做
increment()/record()
6.4 标签设计不当导致指标爆炸
这是生产环境里特别危险的问题。
错误示例
Counter.builder("order_create_total")
.tag("userId", userId)
.register(registry);
如果 userId 是高基数字段,Prometheus 的时序数量会迅速膨胀,内存和查询性能都会变差。
原则
不要把用户 ID、订单号、请求 ID 这类高基数值作为标签。
更适合做标签的是:
service=orderresult=success|failedregion=cn-eastmethod=POST
6.5 HTTP 指标很多,但看不出业务慢在哪
这是系统指标和业务指标脱节的问题。
例如你能看到:
http_server_requests_seconds
但看不到:
- 下单接口中,数据库耗时多少
- 调用第三方支付耗时多少
- 库存检查耗时多少
解决方式是:按关键业务步骤补充 Timer 指标。
例如:
Timer.builder("biz_payment_invoke_duration")
.tag("channel", "wechat")
.register(registry);
七、安全/性能最佳实践
监控系统本身也有成本,设计不好,可能先把自己监控挂了。
7.1 不要把所有 Actuator 端点都暴露到公网
很多示例为了方便,喜欢配置:
management:
endpoints:
web:
exposure:
include: "*"
开发环境可以临时这么干,生产环境不建议。
推荐做法
只暴露必要端点:
management:
endpoints:
web:
exposure:
include: health,info,prometheus
并结合以下措施:
- 通过网关或防火墙限制访问来源
- 管理端口和业务端口隔离
- 给敏感端点加认证授权
例如独立管理端口:
management:
server:
port: 8081
endpoints:
web:
exposure:
include: health,info,prometheus
7.2 控制指标标签维度
记住一句话:标签不是越多越好,而是越稳越好。
建议:
- 标签值枚举范围要小
- 标签语义要稳定
- 能不用动态值就不用动态值
好的标签设计
biz_order_create_total{service="order",result="success"}
不好的标签设计
biz_order_create_total{userId="10000001",orderId="OD20240821001"}
7.3 Timer 和百分位统计不要乱开
像这段配置:
Timer.builder("biz_order_create_duration")
.publishPercentileHistogram()
.register(meterRegistry);
很有用,但也会增加统计成本。
建议
只对关键路径开启:
- 核心交易接口
- 第三方依赖调用
- 容易成为瓶颈的慢操作
不要什么方法都开 histogram,否则指标量和内存占用会明显上升。
7.4 告警要“可执行”,不要“可吓人”
很多团队告警规则的问题是:
- 阈值太敏感,半夜疯狂误报
- 只有“异常了”,没有定位信息
- 没有分级,所有告警都像事故
更好的思路
-
先监控“可用性”
up == 0- 健康检查失败
-
再监控“错误率”
- 失败次数增长
- 5xx 比例升高
-
最后监控“性能劣化”
- P95/P99 响应时间升高
- 线程池、连接池接近饱和
告警内容建议包含
- 哪个服务
- 哪个环境
- 从什么时候开始
- 指标值是多少
- 建议先看哪块
7.5 管理端点与业务流量解耦
如果你的服务流量大,建议把管理端点单独考虑:
- 单独端口
- 单独 ingress 路由
- 单独访问控制
- 避免被业务网关限流策略误伤
八、监控指标设计建议:从“能看”到“有用”
很多团队接上 Prometheus 后,图很多,但对排障帮助不大。根源往往是没有指标分层设计。
我通常建议按三层来建:
1. 基础层:系统与运行时指标
关注服务是否“活着”:
- JVM 内存
- GC 次数/耗时
- CPU 使用率
- 线程数
- HTTP 请求量、响应时间、状态码
- 连接池活跃数
2. 组件层:依赖资源指标
关注服务是否“卡在外部”:
- 数据库连接池
- Redis 连接/命中率
- MQ 消费积压
- 线程池队列长度
- 外部接口调用耗时/失败率
3. 业务层:真正支持决策的指标
关注业务是否“受影响”:
- 下单成功率
- 支付失败率
- 库存扣减冲突次数
- 用户注册转化率
- 退款处理耗时
下面这张图可以帮助你建立指标分层意识:
classDiagram
class 监控体系 {
基础层指标
组件层指标
业务层指标
}
class 基础层指标 {
JVM
CPU
HTTP请求
线程
}
class 组件层指标 {
数据库连接池
Redis
MQ
线程池
}
class 业务层指标 {
下单成功率
支付失败率
核心接口耗时
}
监控体系 --> 基础层指标
监控体系 --> 组件层指标
监控体系 --> 业务层指标
九、一个更贴近生产的配置建议
如果你准备把这套东西放到测试环境或生产环境,建议从下面这个思路起步:
应用侧
- 开启
health、info、prometheus - 给所有指标补
application标签 - 为关键业务埋
Counter、Timer - 不暴露高风险端点
Prometheus 侧
- 抓取周期 15s 或 30s
- 对核心服务配置基础告警
- 先上少量高价值规则,不要一开始就几十条
告警侧
- critical:服务不可用、核心交易失败率过高
- warning:性能下降、少量失败、资源接近阈值
- 通知渠道按环境隔离,避免测试环境吵到生产值班
十、总结
到这里,你已经搭建了一个完整的基础监控闭环:
- 用 Actuator 暴露管理与指标端点
- 用 Micrometer 统一采集系统和业务指标
- 用 Prometheus 抓取、查询、评估告警规则
如果你只记住三件事,我建议是:
- 先把基础监控跑起来:
health + prometheus + JVM/HTTP - 再补关键业务指标:成功率、失败率、耗时
- 最后做告警收敛:先保可用性,再看错误率,再看性能
还有一个很实际的建议:
不要一上来追求“监控平台很大很全”,先从一个核心服务做透。把接口耗时、失败率、实例存活这些最关键的信号稳定下来,你的线上可观测性就已经比很多项目强不少了。
如果你正在做中型 Spring Boot 项目,这套方案完全可以作为团队监控基线。边跑边补业务指标,通常两三轮迭代后,排障效率会有非常明显的提升。