跳转到内容
123xiao | 无名键客

《Spring Boot 中基于 Actuator + Micrometer + Prometheus 的应用监控与告警实战》

字数: 0 阅读时长: 1 分钟

Spring Boot 中基于 Actuator + Micrometer + Prometheus 的应用监控与告警实战

做 Spring Boot 项目时,很多团队一开始对“监控”理解得比较朴素:服务能启动、接口能通,就算没问题。可一旦线上流量上来,问题就会变成另一种样子:

  • 接口偶发变慢,但日志里看不出规律
  • CPU 不高,用户却反馈超时
  • 某个下游抖动,线程池被打满
  • JVM 内存缓慢上涨,最后 OOM
  • 某个实例已经半死不活,但注册中心还没摘掉

这时候你会发现,日志适合排查“发生了什么”,指标更适合回答“系统现在怎么样”。而在 Spring Boot 生态里,Actuator + Micrometer + Prometheus 基本就是一条非常顺手的标准链路。

这篇文章我会带你从 0 到 1 搭出一套可运行的监控方案,并补上告警思路、踩坑点和上线建议。文章偏实战,不只讲概念。


背景与问题

先说结论:没有指标监控的服务,排障效率会随着系统复杂度上升而迅速下降。

很多中型项目在进入稳定运营阶段后,最先出现的问题通常不是“功能写不出来”,而是:

  1. 不知道哪里慢
  2. 不知道慢了多久
  3. 不知道是个别实例还是整体异常
  4. 不知道问题是否已经恢复
  5. 不知道是否该自动告警

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

    • 指标采集门面
    • 屏蔽底层监控系统差异
    • 你可以写 CounterTimerGauge,再导出给 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}

  1. 请求进入 Spring MVC
  2. Micrometer 自动记录 HTTP 请求次数、状态码、耗时等
  3. 这些指标被注册到 MeterRegistry
  4. Actuator 的 /actuator/prometheus 将指标按 Prometheus 格式输出
  5. Prometheus 周期性抓取
  6. 你可以查询:
    • 当前 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

这里有两个很关键的点:

  1. exposure.include 要把 prometheus 暴露出来
  2. 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=truefail=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 是否符合预期,比如 applicationinstanceuri

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=order
  • result=success|fail
  • channel=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: "*"

这在开发机还行,线上不建议。至少要做到:

  • 只暴露必要端点:healthprometheus
  • 管理端口与业务端口隔离
  • 放在内网或受限访问域
  • 配合网关白名单或 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. 监控与日志、链路追踪要配合使用

指标告诉你“哪里不对劲”,但通常不能单独回答“为什么”。

一个实用的排障顺序通常是:

  1. 看告警
  2. 看指标趋势
  3. 定位异常实例/接口
  4. 结合日志查错误详情
  5. 必要时结合 trace 找慢调用链
flowchart TD
    A[告警触发] --> B[查看 Prometheus 指标]
    B --> C{是整体问题还是单实例问题}
    C -->|整体| D[分析流量/下游/资源]
    C -->|单实例| E[查看实例日志与 JVM 指标]
    D --> F[结合 Trace 找慢链路]
    E --> F
    F --> G[修复并观察指标回落]

6. 为告警设置抑制和收敛策略

如果应用实例挂了,可能会同时触发:

  • ApplicationDown
  • 错误率过高
  • 延迟升高
  • 下游超时

如果不做收敛,值班同学会瞬间收到一堆重复告警。生产环境里建议搭配 Alertmanager 做:

  • 告警分组
  • 去重
  • 抑制
  • 静默窗口

进阶建议:哪些指标值得优先接入

如果你的服务刚开始建设监控,我建议优先覆盖这几类,性价比最高:

平台基础指标

  • up
  • process_cpu_usage
  • system_cpu_usage
  • jvm_memory_used_bytes
  • jvm_gc_pause_seconds
  • jvm_threads_live_threads

HTTP 指标

  • http_server_requests_seconds_count
  • http_server_requests_seconds_bucket
  • http_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、系统指标开箱即用
  • 自定义业务指标非常自然
  • 能从“看健康”升级到“看趋势、看性能、看告警”

落地时我建议你按这个顺序推进:

  1. 先接基础监控

    • health
    • JVM
    • HTTP QPS / 错误率 / P95
  2. 再补业务指标

    • 成功数、失败数、耗时、积压
  3. 最后完善告警

    • 可用性
    • 错误率
    • 延迟
    • 内存
    • 队列积压

同时记住几个边界条件:

  • 不要把高基数字段放进 tag
  • 不要把所有 Actuator 端点暴露到公网
  • 不要让告警没有持续时间
  • 不要指望单靠指标解决所有排障问题

如果你现在正在维护一个已经上线的 Spring Boot 服务,我很建议你今天就先做两件事:

  • 打开 /actuator/prometheus
  • 给核心接口补一个错误率和 P95 告警

这两步做完,你对线上状态的掌控力会立刻上一个台阶。


分享到:

上一篇
《从 Prompt 到生产环境:基于 RAG 的企业知识库问答系统设计与优化实战》
下一篇
《Java开发踩坑实战:排查并修复线程池误用导致的接口雪崩与内存飙升》