Spring Boot 中基于 Actuator + Micrometer + Prometheus 的应用监控与告警实战
做 Spring Boot 项目时,很多团队一开始对“监控”理解得比较朴素:服务能启动、接口能通,就算没问题。可一旦线上流量上来,问题就会变成另一种样子:
- 接口偶发变慢,但日志里看不出规律
- CPU 不高,用户却反馈超时
- 某个下游抖动,线程池被打满
- JVM 内存缓慢上涨,最后 OOM
- 某个实例已经半死不活,但注册中心还没摘掉
这时候你会发现,日志适合排查“发生了什么”,指标更适合回答“系统现在怎么样”。而在 Spring Boot 生态里,Actuator + Micrometer + Prometheus 基本就是一条非常顺手的标准链路。
这篇文章我会带你从 0 到 1 搭出一套可运行的监控方案,并补上告警思路、踩坑点和上线建议。文章偏实战,不只讲概念。
背景与问题
先说结论:没有指标监控的服务,排障效率会随着系统复杂度上升而迅速下降。
很多中型项目在进入稳定运营阶段后,最先出现的问题通常不是“功能写不出来”,而是:
- 不知道哪里慢
- 不知道慢了多久
- 不知道是个别实例还是整体异常
- 不知道问题是否已经恢复
- 不知道是否该自动告警
Spring Boot 自带 Actuator,可以暴露健康检查、线程、Bean、配置、指标等信息;Micrometer 负责把应用内部的指标统一抽象出来;Prometheus 则定时抓取这些指标并用于查询、聚合和告警。
这三者配合起来,能比较自然地解决如下问题:
- 看应用健康状态:
/actuator/health - 看 JVM、HTTP、线程池、GC 等指标:
/actuator/prometheus - 统计接口耗时、QPS、错误率
- 给业务事件打自定义指标
- 做阈值告警,比如:
- 5 分钟错误率 > 5%
- P95 响应时间 > 800ms
- JVM 堆使用率 > 85%
- 实例不可用持续 1 分钟
前置知识与环境准备
为了保证示例可运行,我这里用一套比较常见的组合:
- JDK 17
- Spring Boot 3.2+
- Maven 3.9+
- Prometheus 2.x
- 可选:Grafana 10.x(本文重点放在监控与告警,Grafana 仅顺带提一下)
目录目标:
- 一个 Spring Boot 应用,暴露 Actuator 和 Prometheus 指标
- 一个 Prometheus 配置,能抓到应用指标
- 若干自定义业务指标
- 一组基础告警规则
核心原理
这一节先把链路讲清楚。理解了链路,后面你配置起来不会只是“照抄”。
1. 三者分别负责什么
-
Actuator
- 提供 Spring Boot 应用管理端点
- 比如
/actuator/health、/actuator/metrics、/actuator/prometheus
-
Micrometer
- 指标采集门面
- 屏蔽底层监控系统差异
- 你可以写
Counter、Timer、Gauge,再导出给 Prometheus、Datadog、InfluxDB 等
-
Prometheus
- 采用 pull 模式定时抓取指标
- 内置时间序列存储
- 支持 PromQL 查询和 Alertmanager 告警链路
2. 数据流转关系
flowchart LR
A[Spring Boot 应用] --> B[Actuator 端点]
B --> C[Micrometer 指标注册表]
C --> D[/actuator/prometheus]
E[Prometheus] -->|定时抓取 scrape| D
E --> F[PromQL 查询]
E --> G[告警规则]
G --> H[Alertmanager/通知系统]
3. 一次请求的监控路径
假设用户调用 /api/orders/{id}:
- 请求进入 Spring MVC
- Micrometer 自动记录 HTTP 请求次数、状态码、耗时等
- 这些指标被注册到 MeterRegistry
- Actuator 的
/actuator/prometheus将指标按 Prometheus 格式输出 - Prometheus 周期性抓取
- 你可以查询:
- 当前 QPS
- 5xx 错误率
- P95/P99 延迟
- 某实例是否 down
sequenceDiagram
participant U as 用户
participant S as Spring Boot
participant M as Micrometer
participant A as Actuator
participant P as Prometheus
U->>S: HTTP 请求 /api/orders/1
S->>M: 记录 count/timer/status
M-->>S: 更新指标
P->>A: GET /actuator/prometheus
A->>M: 读取指标
M-->>A: 返回当前指标快照
A-->>P: Prometheus 文本格式
P-->>P: 存储时间序列并评估告警
4. 常见指标类型
Micrometer 里最常见的几个:
- Counter:只增不减,适合记录请求数、异常数、消息消费数
- Timer:统计耗时和次数,适合接口响应时间
- Gauge:反映当前值,适合队列积压、缓存大小、在线连接数
- DistributionSummary:统计分布值,适合记录请求体大小、订单金额分布等
我个人经验是:业务指标里优先用 Counter 和 Timer,Gauge 要谨慎,因为它依赖“取样时刻”的当前状态,解释起来有时没 Counter 直观。
实战代码(可运行)
下面我们从 0 开始搭一个最小可用项目。
第一步:创建项目并添加依赖
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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>boot-monitor-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot-monitor-demo</name>
<description>Spring Boot monitoring demo</description>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
第二步:配置 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: 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要把prometheus暴露出来- 对
http.server.requests开启 histogram 和 percentile,后面 Prometheus 才能更方便做 P95、P99 统计
第三步:启动类
src/main/java/com/example/demo/DemoApplication.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
第四步:写一个业务接口,制造一些可观察性数据
src/main/java/com/example/demo/web/OrderController.java
package com.example.demo.web;
import com.example.demo.service.OrderService;
import jakarta.validation.constraints.Min;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/orders")
@Validated
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/{id}")
public Map<String, Object> getOrder(@PathVariable @Min(1) Long id,
@RequestParam(defaultValue = "false") boolean slow,
@RequestParam(defaultValue = "false") boolean fail) {
return orderService.getOrder(id, slow, fail);
}
}
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.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@Service
public class OrderService {
private final Counter orderQueryCounter;
private final Counter orderFailureCounter;
private final Timer orderQueryTimer;
public OrderService(MeterRegistry registry) {
this.orderQueryCounter = Counter.builder("biz_order_query_total")
.description("订单查询总次数")
.tag("biz", "order")
.register(registry);
this.orderFailureCounter = Counter.builder("biz_order_query_failure_total")
.description("订单查询失败次数")
.tag("biz", "order")
.register(registry);
this.orderQueryTimer = Timer.builder("biz_order_query_duration")
.description("订单查询耗时")
.tag("biz", "order")
.publishPercentileHistogram()
.register(registry);
}
public Map<String, Object> getOrder(Long id, boolean slow, boolean fail) {
return orderQueryTimer.record(() -> {
orderQueryCounter.increment();
if (slow) {
sleepRandom(300, 1200);
} else {
sleepRandom(20, 80);
}
if (fail) {
orderFailureCounter.increment();
throw new IllegalStateException("模拟订单查询失败");
}
return Map.of(
"id", id,
"status", "PAID",
"amount", 199.00
);
});
}
private void sleepRandom(int minMs, int maxMs) {
int sleep = ThreadLocalRandom.current().nextInt(minMs, maxMs + 1);
try {
TimeUnit.MILLISECONDS.sleep(sleep);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
这个例子里做了两件事:
- 使用
Counter统计查询次数和失败次数 - 使用
Timer统计业务方法耗时
同时,通过 slow=true 和 fail=true 模拟线上“偶发慢”和“偶发错”的场景。
第五步:增加一个 Gauge 监控队列积压
很多同学一上来只关心 HTTP 指标,但实际业务中,队列积压、缓存长度、活动任务数也非常重要。
src/main/java/com/example/demo/metrics/QueueMetrics.java
package com.example.demo.metrics;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class QueueMetrics {
private final MeterRegistry registry;
private final AtomicInteger queueSize = new AtomicInteger(0);
public QueueMetrics(MeterRegistry registry) {
this.registry = registry;
}
@PostConstruct
public void init() {
Gauge.builder("biz_queue_size", queueSize, AtomicInteger::get)
.description("模拟业务队列长度")
.tag("queue", "order-sync")
.register(registry);
}
public void increase() {
queueSize.incrementAndGet();
}
public void decrease() {
queueSize.updateAndGet(v -> Math.max(0, v - 1));
}
public int current() {
return queueSize.get();
}
}
src/main/java/com/example/demo/web/QueueController.java
package com.example.demo.web;
import com.example.demo.metrics.QueueMetrics;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/queue")
public class QueueController {
private final QueueMetrics queueMetrics;
public QueueController(QueueMetrics queueMetrics) {
this.queueMetrics = queueMetrics;
}
@PostMapping("/increase")
public Map<String, Object> increase() {
queueMetrics.increase();
return Map.of("queueSize", queueMetrics.current());
}
@PostMapping("/decrease")
public Map<String, Object> decrease() {
queueMetrics.decrease();
return Map.of("queueSize", queueMetrics.current());
}
@GetMapping
public Map<String, Object> current() {
return Map.of("queueSize", queueMetrics.current());
}
}
第六步:启动并验证指标输出
启动应用后,先访问几个接口:
curl "http://localhost:8080/api/orders/1"
curl "http://localhost:8080/api/orders/2?slow=true"
curl "http://localhost:8080/api/orders/3?fail=true"
curl -X POST "http://localhost:8080/api/queue/increase"
curl -X POST "http://localhost:8080/api/queue/increase"
curl "http://localhost:8080/actuator/health"
curl "http://localhost:8080/actuator/prometheus"
你应该能在 /actuator/prometheus 中看到类似内容:
# HELP http_server_requests_seconds
# TYPE http_server_requests_seconds histogram
http_server_requests_seconds_count{application="boot-monitor-demo",error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="/api/orders/{id}",...} 2.0
# HELP biz_order_query_total 订单查询总次数
# TYPE biz_order_query_total counter
biz_order_query_total{biz="order",} 3.0
# HELP biz_order_query_failure_total 订单查询失败次数
# TYPE biz_order_query_failure_total counter
biz_order_query_failure_total{biz="order",} 1.0
# HELP biz_queue_size 模拟业务队列长度
# TYPE biz_queue_size gauge
biz_queue_size{queue="order-sync",} 2.0
如果这里看不到数据,先别急着改代码,优先检查依赖和配置,后面“常见坑与排查”里我会专门讲。
第七步:接入 Prometheus
prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "alert-rules.yml"
scrape_configs:
- job_name: "boot-monitor-demo"
metrics_path: "/actuator/prometheus"
static_configs:
- targets: ["host.docker.internal:8080"]
labels:
app: "boot-monitor-demo"
如果你不是用 Docker 运行 Prometheus,而是直接本机启动,可以把
host.docker.internal:8080改成localhost:8080。
使用 Docker 启动 Prometheus
docker run -d \
--name prometheus \
-p 9090:9090 \
-v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
-v $(pwd)/alert-rules.yml:/etc/prometheus/alert-rules.yml \
prom/prometheus
启动后打开:
- Prometheus UI:
http://localhost:9090 - Targets:
http://localhost:9090/targets
如果 target 状态为 UP,说明抓取链路已经通了。
第八步:编写 PromQL 查询
有了指标后,最重要的是会查。
1. 应用实例是否存活
up{job="boot-monitor-demo"}
2. 每秒请求量(QPS)
sum(rate(http_server_requests_seconds_count{application="boot-monitor-demo"}[1m]))
3. 5xx 错误率
sum(rate(http_server_requests_seconds_count{application="boot-monitor-demo",status=~"5.."}[5m]))
/
sum(rate(http_server_requests_seconds_count{application="boot-monitor-demo"}[5m]))
4. 接口 P95 延迟
histogram_quantile(
0.95,
sum by (le, uri) (
rate(http_server_requests_seconds_bucket{application="boot-monitor-demo"}[5m])
)
)
5. JVM 堆内存使用率
sum(jvm_memory_used_bytes{application="boot-monitor-demo",area="heap"})
/
sum(jvm_memory_max_bytes{application="boot-monitor-demo",area="heap"})
6. 业务失败率
rate(biz_order_query_failure_total[5m])
/
rate(biz_order_query_total[5m])
7. 队列积压
biz_queue_size{queue="order-sync"}
第九步:配置告警规则
监控只是看到问题,告警才是“别让人一直盯着屏幕”。
alert-rules.yml
groups:
- name: spring-boot-alerts
rules:
- alert: ApplicationDown
expr: up{job="boot-monitor-demo"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "应用实例不可用"
description: "boot-monitor-demo 已连续 1 分钟抓取失败"
- alert: HighErrorRate
expr: |
(
sum(rate(http_server_requests_seconds_count{job="boot-monitor-demo",status=~"5.."}[5m]))
/
sum(rate(http_server_requests_seconds_count{job="boot-monitor-demo"}[5m]))
) > 0.05
for: 2m
labels:
severity: warning
annotations:
summary: "服务错误率过高"
description: "5 分钟错误率超过 5%"
- alert: HighP95Latency
expr: |
histogram_quantile(
0.95,
sum by (le) (
rate(http_server_requests_seconds_bucket{job="boot-monitor-demo"}[5m])
)
) > 0.8
for: 3m
labels:
severity: warning
annotations:
summary: "接口 P95 延迟过高"
description: "过去 5 分钟 P95 响应时间超过 800ms"
- alert: HighJvmHeapUsage
expr: |
(
sum(jvm_memory_used_bytes{job="boot-monitor-demo",area="heap"})
/
sum(jvm_memory_max_bytes{job="boot-monitor-demo",area="heap"})
) > 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "JVM 堆内存使用率过高"
description: "堆内存使用率连续 5 分钟超过 85%"
- alert: QueueBacklogTooHigh
expr: biz_queue_size{job="boot-monitor-demo",queue="order-sync"} > 100
for: 2m
labels:
severity: warning
annotations:
summary: "业务队列积压过高"
description: "order-sync 队列长度超过 100 持续 2 分钟"
这里我建议你记住一个原则:
告警不要只配阈值,要配持续时间
for。
因为瞬时尖峰很多,尤其是延迟和错误率。如果不加 for,很容易把告警系统搞成“噪音制造机”。
逐步验证清单
实际落地时,我通常按这个顺序验证,效率很高。
1. 应用侧验证
- 应用是否正常启动
/actuator/health是否可访问/actuator/prometheus是否输出指标- 是否能看到
http_server_requests_seconds_* - 自定义指标是否存在:
biz_order_query_total
2. Prometheus 侧验证
- target 是否为
UP - 查询
up{job="boot-monitor-demo"} - 查询 QPS、错误率、P95 是否有值
- label 是否符合预期,比如
application、instance、uri
3. 告警侧验证
- 人工制造
fail=true,看错误率是否上涨 - 人工制造
slow=true,看 P95 是否升高 - 停掉应用实例,看
ApplicationDown是否触发
常见坑与排查
这一节非常关键。很多问题不是不会配,而是配了以后“为什么没数据”“为什么数据不准”。
1. /actuator/prometheus 返回 404
常见原因
- 没引入
micrometer-registry-prometheus - 没暴露
prometheus端点 - Actuator 路径被自定义改掉了
排查方式
先看依赖,再看配置:
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
再访问:
curl http://localhost:8080/actuator
如果返回的 links 里没有 prometheus,一般就是端点没暴露或相关依赖缺失。
2. Prometheus target 一直是 DOWN
常见原因
metrics_path写错- target 地址写错
- 容器里访问不到宿主机
localhost - 服务有鉴权、TLS、网关拦截
排查思路
在 Prometheus 所在机器或容器里直接 curl:
curl http://host.docker.internal:8080/actuator/prometheus
如果这里都不通,就先不要看 PromQL,先把网络打通。
我之前就踩过一个很典型的坑:应用在本机,Prometheus 在 Docker 容器里,配置写的是
localhost:8080。结果对容器来说,localhost指向的是它自己,不是宿主机。
3. P95 查询不出来
常见原因
- 没开启 histogram
- 指标名写错
- Prometheus 抓取频率太低,样本不足
关键配置
management:
metrics:
distribution:
percentiles-histogram:
http.server.requests: true
如果没这个配置,http_server_requests_seconds_bucket 可能就不完整,histogram_quantile 也不好算。
4. 自定义指标 label 爆炸
这是线上很危险、但又很常见的问题。
错误示例
把用户 ID、订单号、完整 URL 作为 tag:
Counter.builder("biz_order_total")
.tag("userId", userId.toString())
.tag("orderId", orderId.toString())
.register(registry);
这样会导致**高基数(high cardinality)**问题,Prometheus 时间序列数暴涨,内存和查询性能都会出问题。
正确思路
tag 只放有限枚举值,比如:
biz=orderresult=success|failchannel=app|h5|miniapp
不要放:
- 用户 ID
- 订单号
- 手机号
- traceId
- 完整动态路径参数
5. 接口 URI 指标异常离散
如果你没有正确聚合路径,Prometheus 里可能出现:
/api/orders/1/api/orders/2/api/orders/3
这会让监控图表和告警都很难看。
Spring Boot 通常会把路径归一成模板形式 /api/orders/{id},但前提是框架链路和路由映射正常。如果你用了特殊网关、过滤器、非标准 handler,最好确认一下最终导出的 uri 标签值。
查询示例:
sum by (uri) (
rate(http_server_requests_seconds_count[5m])
)
看看是否被正确聚合。
6. 健康检查暴露太多细节
开发环境看到完整 health details 很方便,但线上要谨慎。像数据库地址、磁盘详情、Redis 状态,有时会泄露过多内部信息。
如果需要按环境区分,可以这样做:
management:
endpoint:
health:
show-details: when_authorized
安全/性能最佳实践
这一节我想讲得稍微务实一点:监控系统本身也会影响系统,而且暴露管理端点也有安全面。
1. 不要把所有 Actuator 端点都暴露到公网
很多人图省事,直接:
management:
endpoints:
web:
exposure:
include: "*"
这在开发机还行,线上不建议。至少要做到:
- 只暴露必要端点:
health、prometheus - 管理端口与业务端口隔离
- 放在内网或受限访问域
- 配合网关白名单或 Spring Security
例如:
server:
port: 8080
management:
server:
port: 9091
endpoints:
web:
exposure:
include: health,prometheus
这样业务服务走 8080,监控端点走 9091,更容易做网络隔离。
2. 控制标签基数
这是 Prometheus 落地里最重要的性能原则之一。
建议
- tag 维度只保留业务分析所需字段
- 尽量使用枚举型、低基数字段
- 避免把请求参数、用户标识、traceId 放进标签
- 定期观察 TSDB 序列数量和 scrape 开销
可以把这条当成硬规则:“能不用动态 tag,就别用。”
3. 告警要分级,不要全是 critical
告警系统真正可用,不是因为它“能发消息”,而是因为收到消息的人知道该不该立刻处理。
推荐至少分三级:
info:趋势提醒,不要求立刻响应warning:需要人工关注critical:影响核心功能,需要尽快处理
例如:
- 单实例 down:warning
- 全部实例 down:critical
- P95 偶发超阈:warning
- 错误率持续飙升:critical
4. histogram 不是越多越好
开启 histogram 会带来更丰富的延迟分析能力,但也会增加时间序列数量。
建议边界
适合开 histogram 的:
- 核心 HTTP 接口
- 少量关键业务方法
- 网关、订单、支付等核心链路
不建议一股脑全开:
- 所有自定义 Timer
- 高维标签组合下的细粒度业务操作
否则 Prometheus 存储压力会明显上升。
5. 监控与日志、链路追踪要配合使用
指标告诉你“哪里不对劲”,但通常不能单独回答“为什么”。
一个实用的排障顺序通常是:
- 看告警
- 看指标趋势
- 定位异常实例/接口
- 结合日志查错误详情
- 必要时结合 trace 找慢调用链
flowchart TD
A[告警触发] --> B[查看 Prometheus 指标]
B --> C{是整体问题还是单实例问题}
C -->|整体| D[分析流量/下游/资源]
C -->|单实例| E[查看实例日志与 JVM 指标]
D --> F[结合 Trace 找慢链路]
E --> F
F --> G[修复并观察指标回落]
6. 为告警设置抑制和收敛策略
如果应用实例挂了,可能会同时触发:
ApplicationDown- 错误率过高
- 延迟升高
- 下游超时
如果不做收敛,值班同学会瞬间收到一堆重复告警。生产环境里建议搭配 Alertmanager 做:
- 告警分组
- 去重
- 抑制
- 静默窗口
进阶建议:哪些指标值得优先接入
如果你的服务刚开始建设监控,我建议优先覆盖这几类,性价比最高:
平台基础指标
upprocess_cpu_usagesystem_cpu_usagejvm_memory_used_bytesjvm_gc_pause_secondsjvm_threads_live_threads
HTTP 指标
http_server_requests_seconds_counthttp_server_requests_seconds_buckethttp_server_requests_seconds_sum
关注三件事:
- 流量:QPS
- 质量:错误率
- 体验:P95/P99 延迟
业务指标
- 核心接口调用次数
- 核心业务失败次数
- 消息消费成功/失败
- 队列积压
- 订单创建/支付/退款数量
资源与依赖
- 数据库连接池使用情况
- Redis 连接/超时
- 线程池活跃线程数、队列长度、拒绝次数
一个更贴近生产的配置示例
如果你准备上线,我建议以“最小暴露 + 基础统计 + 管理端口隔离”为起点。
application-prod.yml
server:
port: 8080
spring:
application:
name: boot-monitor-demo
management:
server:
port: 9091
endpoints:
web:
exposure:
include: health,prometheus
endpoint:
health:
show-details: when_authorized
metrics:
tags:
application: ${spring.application.name}
env: prod
distribution:
percentiles-histogram:
http.server.requests: true
slo:
http.server.requests: 100ms,300ms,500ms,1s,2s
这里额外用了 slo 分桶。这样你在 Prometheus 里做“超过 500ms 的比例”之类查询会更顺手。
总结
如果把整条链路压缩成一句话,那就是:
Actuator 负责暴露,Micrometer 负责采集与建模,Prometheus 负责抓取、查询与告警。
这套方案为什么值得在 Spring Boot 项目里优先采用?
- 生态成熟,接入成本低
- JVM、HTTP、系统指标开箱即用
- 自定义业务指标非常自然
- 能从“看健康”升级到“看趋势、看性能、看告警”
落地时我建议你按这个顺序推进:
-
先接基础监控
- health
- JVM
- HTTP QPS / 错误率 / P95
-
再补业务指标
- 成功数、失败数、耗时、积压
-
最后完善告警
- 可用性
- 错误率
- 延迟
- 内存
- 队列积压
同时记住几个边界条件:
- 不要把高基数字段放进 tag
- 不要把所有 Actuator 端点暴露到公网
- 不要让告警没有持续时间
- 不要指望单靠指标解决所有排障问题
如果你现在正在维护一个已经上线的 Spring Boot 服务,我很建议你今天就先做两件事:
- 打开
/actuator/prometheus - 给核心接口补一个错误率和 P95 告警
这两步做完,你对线上状态的掌控力会立刻上一个台阶。