第4篇 Spring Boot 应用监控:Micrometer 与 JVM 指标

在企业级应用的技术选型中,Spring Boot 已稳居主流架构之列。微服务拆分与容器化部署的大规模落地,带来了服务实例数量与调用链路的指数级增长,也带来了一个无法回避的工程命题------如何洞察运行时的真实状态。JVM 的堆内存波动、GC 停顿、线程阻塞,任何一项指标的异常都可能演变为线上故障。可观测性不再是锦上添花,而是生产环境的刚性需求。Micrometer 作为 Spring Boot 默认的指标门面,与 Actuator 配合,将 JVM 及业务指标以标准化格式暴露给 Prometheus 等监控系统,几乎成为每个 Spring Boot 项目的标配能力。本篇将从 Actuator 端点配置入手,逐步拆解 Spring Boot 应用的监控体系搭建。

一、Spring Boot 应用集成 Actuator 暴露 /actuator/prometheus 端点

下文基于版本: org.springframework.boot:spring-boot-starter-parent:3.5.3 ,内置 tomcat 10

1、 pom.xml 添加依赖

bash 复制代码
<!-- Actuator:生产级监控端点 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Prometheus 指标 registry:提供 /actuator/prometheus 端点 -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

2、暴露监控端点

bash 复制代码
server:
  tomcat:
    mbeanregistry:
      enabled: true

management:
  endpoints:
    web:
      exposure:
        # 按需暴露:health、env 详情 + prometheus 指标(父 POM 已引入 micrometer-registry-prometheus)
        include: health,env,info,prometheus
  endpoint:
    health:
      # 展示健康检查详情(包含各 health indicator 的明细)
      show-details: always
      # 自定义状态聚合顺序 + HTTP 状态码映射。
      # Spring Boot 默认顺序为 DOWN > OUT_OF_SERVICE > UP > UNKNOWN,
      # 这会导致:仅 manual=UNKNOWN、其余=UP 时,整体被聚合为 UP → HTTP 200(语义错误)。
      # 此处把 UNKNOWN 提到 UP 之前,并显式声明 HTTP 映射,
      # 确保 /actuator/health 的整体状态与 HTTP 状态码严格跟随 ManualHealthIndicator。
      status:
        order: DOWN,OUT_OF_SERVICE,UNKNOWN,UP
        http-mapping:
          UP: 200
          DOWN: 503
          OUT_OF_SERVICE: 503
          UNKNOWN: 503
    env:
      # 展示环境属性的实际值(默认脱敏为******,此处按需开放详情)
      # 注意:会暴露数据库密码等敏感配置,生产环境请改回 never 或配合 sanitization 过滤
      show-values: always
  info:
    env:
      # 暴露环境信息到 /actuator/info
      enabled: true

4、增加依赖组件健康指标

通过 /actuator/health 可直接查看依赖组件健康状态,但默认没有暴露成 prometheus 指标。因此,可参考如下代码实现指标暴露。

bash 复制代码
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.actuate.health.*;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.util.concurrent.*;

/**
 * 不受以下配置影响,即不配置,/prometheus 也有 health_status 指标
 * management.endpoint.health.show-details: always
 */
@Component
public class HealthMetricsExportBinder {

    // 指标名称
    private final String Health_Metric_Name = "health_status";

    // 独立线程池,隔离健康检查IO,不占用tomcat/http-nio线程
    private final ExecutorService healthCheckPool = Executors.newFixedThreadPool(6);
    private static final int CHECK_TIMEOUT_MILLISEC = 200;

    private final HealthContributorRegistry healthContributorRegistry;
    private final HealthEndpoint healthEndpoint;
    private final MeterRegistry meterRegistry;

    public HealthMetricsExportBinder(HealthContributorRegistry healthContributorRegistry,
                                     HealthEndpoint healthEndpoint,
                                     MeterRegistry meterRegistry) {
        this.healthContributorRegistry = healthContributorRegistry;
        this.healthEndpoint = healthEndpoint;
        this.meterRegistry = meterRegistry;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void registerHealthGauges() {
        // 注册各个子组件健康指标 db / redis / diskSpace / 自定义Indicator
        healthContributorRegistry.stream().forEach(namedContributor -> {
            String compName = namedContributor.getName();
            HealthContributor contributor = namedContributor.getContributor();
            if (contributor instanceof HealthIndicator hi) {
                Gauge.builder(Health_Metric_Name, () -> getComponentHealthValue(hi))
                        .tag("component", compName)
                        .register(meterRegistry);
            }
        });
    }

    /**
     * 获取单个组件状态,超时/异常直接返回0 DOWN
     */
    private double getComponentHealthValue(HealthIndicator healthIndicator) {
        try {
            Future<Health> future = healthCheckPool.submit(healthIndicator::health);
            Health health = future.get(CHECK_TIMEOUT_MILLISEC, TimeUnit.MILLISECONDS);
            return Status.UP.equals(health.getStatus()) ? 1.0D : 0.0D;
        } catch (TimeoutException te) {
            // 例如mysql挂掉,JDBC阻塞超时,返回0
            return 0.0D;
        } catch (Exception ex) {
            return 0.0D;
        }
    }

    /** 容器销毁关闭线程池 */
    @jakarta.annotation.PreDestroy
    public void destroy() {
        healthCheckPool.shutdownNow();
    }

}

5、验证

bash 复制代码
http://localhost:8080/actuator/prometheus

若按前文添加依赖组件健康指标,返回结果则可查到类似如下内容:

bash 复制代码
# HELP health_status  
# TYPE health_status gauge
health_status{component="ping",} 1.0
health_status{component="diskSpace",} 1.0
health_status{component="db",} 1.0

二、核心指标解析

指标分类

| # | 分类 | 前缀 | 监控内容 |
|-------|-------------|------------------------------------|----------------------------------------------------------------------------|---|
| 1 | HTTP Web 指标 | http_server_requests_seconds | MVC/WebFlux 接口 QPS、耗时、状态码、异常、URI | |
| 2 | 依赖组件指标 | health_status | 依赖组件健康状态,默认无该指标,需参考前文扩展 | |
| 3 | JVM 指标 | jvm_* | 堆 / 非堆内存、GC、线程、类加载、JIT 编译 | |
| 4 | Web 容器线程池 | tomcat_threads_* | Tomcat 容器 HttpWorker 工作线程资源,繁忙线程、总线程、最大线程、请求队列容量;Jetty 对应jetty_threads_* |
| 5 | 系统 / 进程指标 | process_*、system_* | 进程 CPU、文件句柄、进程运行时长、系统负载 | |
| 6 | 数据库连接池 | hikaricp_* | 活跃连接、空闲连接、等待连接数、连接创建耗时 | |
| 7 | 日志指标 | logback_events_total | info/warn/error 日志条数 | |
| 8 | 缓存指标 | cache_* | 缓存命中、未命中、淘汰数量 | |
| 9 | 定时任务 | task_executor_* | 线程池队列、活跃线程、任务执行次数 | |
| 10 | 应用启动 | application_started_time_seconds | 应用启动耗时 | |
| 11 | 业务自定义指标 | 自定义前缀 | 业务埋点,如订单创建数、支付失败数 | |

1、HTTP Web 指标

1)请求量与 QPS

主要指标:

Prometheus 指标名 指标类型 含义说明
http_server_requests_seconds_count Counter HTTP 请求总次数,用于计算 QPS

常用 PromQL:

bash 复制代码
# 总 QPS / QPS 趋势
sum(rate(http_server_requests_seconds_count[5m])) by (job, svc)

# 按接口路径 QPS
sum(rate(http_server_requests_seconds_count[5m])) by (uri, method)

# QPS 趋势
sum(rate(http_server_requests_seconds_count[5m])) by (job, svc)

# TOP 10 接口 QPS
topk(10, sum(rate(http_server_requests_seconds_count[5m])) by (job, svc, uri))

2)错误率

常用 PromQL

bash 复制代码
# 总错误率(5xx)
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (job) /
sum(rate(http_server_requests_seconds_count[5m])) by (job) * 100

# 按接口错误率
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (job, svc, uri) /
sum(rate(http_server_requests_seconds_count[5m])) by (job, svc, uri) * 100

# 4xx 客户端错误率
sum(rate(http_server_requests_seconds_count{status=~"4.."}[5m])) by (job) /
sum(rate(http_server_requests_seconds_count[5m])) by (job) * 100

# 按状态码分类统计
sum(rate(http_server_requests_seconds_count[5m])) by (job, status)

3)响应时间

注意:

Spring Boot 3 / Micrometer 默认将 http_server_requests_seconds 发布为 summary (仅含 _count 和 _sum),不包含 histogram _bucket,因此 histogram_quantile() 无法直接使用。

以下查询基于 summary 可用字段。若需分位数(P50/P95/P99),需在应用中配置 management.metrics.distribution.percentiles-histogram.http.server.requests=true 或使用 http.server.requests 的 percentile 直方图。但配置后,指标数量会大量增加,要慎用。

主要指标:

Prometheus 指标名 指标类型 含义说明
http_server_requests_seconds_sum Counter 所有请求累计耗时(单位:秒),用于计算平均响应时间
http_server_requests_seconds_bucket Histogram 响应时间直方图,le标签代表耗时阈值,用于计算 P50/P95/P99 分位耗时
http_server_requests_seconds_max Gauge 最近统计窗口内最大单次请求耗时 (秒),反映请求耗时峰值 / 长尾
http_server_requests_active_seconds_duration_sum Counter 当前正在处理中的请求累计耗时 (秒),所有在途请求耗时总和
http_server_requests_active_seconds_active_count Gauge 当前正在处理的活跃请求数量(在途请求数),瞬时值

SpringBoot3 + MVC(Tomcat)原生不会自动暴露 http_server_requests_active_seconds_duration_sum、 http_server_requests_active_seconds_active_count。

常用 PromQL

bash 复制代码
# 平均响应时间(秒)
sum(rate(http_server_requests_seconds_sum[5m])) by (job) /
sum(rate(http_server_requests_seconds_count[5m])) by (job)

# 平均响应时间(毫秒)
sum(rate(http_server_requests_seconds_sum[5m])) by (job) /
sum(rate(http_server_requests_seconds_count[5m])) by (job) * 1000

# 最大响应时间(按接口)
http_server_requests_seconds_max

# 按接口平均响应时间(毫秒)
sum(rate(http_server_requests_seconds_sum[5m])) by (job, svc, uri) /
sum(rate(http_server_requests_seconds_count[5m])) by (job, svc, uri) * 1000

# 当前活跃请求持续时间
http_server_requests_active_seconds_duration_sum /
http_server_requests_active_seconds_active_count

# 慢请求统计(需配置 histogram,否则仅可用平均/最大值判断)
# 若已开启 percentiles-histogram:
# histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, job))

分析要点:

  • 平均响应时间 > 500ms 判定警告,> 1s 判定严重,但还需要根据应用特性调整阈值
  • http_server_requests_seconds_max 可识别极端慢请求
  • 若需 P95/P99 分位数,应用需配置 management.metrics.distribution.percentiles-histogram.http.server.requests=true
  • 活跃请求持续时间持续偏高 → 当前有慢请求正在处理,可能阻塞线程池
  • 对比各接口平均响应时间和 QPS,识别热点慢接口

2、依赖组件健康指标

主要指标:

Prometheus 指标名 指标类型 含义说明
health_status Gauge 依赖组件健康状态

常用 PromQL

bash 复制代码
# 查询异常的组件(health_status=0)
health_status == 0

# 查询正常的组件(health_status=1)
health_status == 1

# 统计异常组件个数(按实例聚合)
count(health_status == 0) by (instance,job)

# 布尔判断:只要有任意组件异常就触发告警(最常用告警表达式)
# 如果任意一个 component=0 → min 结果 = 0 → `0 !=1` → 返回 1,触发告警
min(health_status) by (job,instance) != 1

# 只要该实例存在至少 1 个健康组件失败,就触发告警。
count(health_status == 0) by (job,instance) > 0

分析要点:

  • 依赖组件不健康时,会引起应用本身也不正常

3、JVM 指标

1)内存

分堆内存和非堆内存:

  • area="heap":堆内存 ,存放对象实例、数组,GC 主要管理区域,受 -Xmx/-Xms 控制
  • area="nonheap":非堆内存,JVM 自身使用,不存放普通对象,不受 - Xmx 限制
area id 内存池名称 内存区域说明
heap G1 Eden Space / PS Eden Space Eden 伊甸园,新对象优先分配区域
G1 Survivor Space / PS Survivor Space Survivor 幸存者区,Eden 存活对象转移到此
G1 Old Gen / PS Old Gen / Tenured Gen 老年代,长期存活对象存放区域
nonheap Metaspace 元空间 (JDK8+),存放类元数据、方法信息、常量,替代 PermGen 永久代
Compressed Class Space 压缩类空间,64 位开启压缩指针时使用,属于元空间子区域
CodeCache / CodeHeap 代码缓存,JIT 编译后的机器指令存放处

注意:

  1. ZGC/Shenandoah 等低延迟 GC,heap 的 id 名称会变成 ZHeap 等,不要硬编码 id 写死面板查询;

  2. NIO Direct 直接内存不在 jvm_memory_used_bytes ,看指标 jvm_buffer_memory_used_bytes;

  3. Metaspace 的 jvm_memory_max_bytes 常为 -1(无上限),做使用率计算时需要过滤 >0。

主要指标:

Prometheus 指标名 指标类型 含义说明
jvm_memory_used_bytes Gauge JVM 内存已使用字节数;area=heap 堆内存,area=nonheap 非堆内存
jvm_memory_max_bytes Gauge JVM 内存最大可用字节数(Xmx / 非堆上限,-1 代表无上限)
jvm_memory_committed_bytes Gauge JVM 已向操作系统申请、已提交的内存字节数(OS 已分配)
jvm_memory_init_bytes Gauge JVM 启动初始化申请的内存大小

常用 PromQL:

bash 复制代码
# 堆内存使用量(按区域)
jvm_memory_used_bytes{area="heap"}

# 堆内存使用率
jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} * 100

# 各内存区域使用量
jvm_memory_used_bytes{area="heap", id=~"G1 Old Generation|G1 Eden Space|G1 Survivor Space|Par Eden Space|Par Survivor Space|CMS Old Gen|Metaspace"}
jvm_memory_committed_bytes{area="heap"}
jvm_memory_max_bytes{area="heap"}

# 非堆内存(Metaspace)
jvm_memory_used_bytes{area="nonheap", id="Metaspace"}
jvm_memory_used_bytes{area="nonheap", id="Code Cache"}

# 内存变化速率(检测内存泄漏)
deriv(jvm_memory_used_bytes{area="heap", id=~".*Old.*|G1 Old Generation"}[1h])

分析要点:

  • 堆内存使用率持续 > 90% 有 OOM 风险
  • Old Gen 持续增长不回收,可能存在内存泄漏
  • Metaspace 增长需检查是否有动态类加载泄漏
  • 对比 used / committed / max 判断是否需要调整内存参数

2)GC(垃圾回收)

主要指标:

Prometheus 指标名 指标类型 含义说明
jvm_gc_pause_seconds Timer GC 停顿耗时(Timer),可计算 P50/P95/P99 停顿时间
jvm_gc_pause_seconds_count Counter GC 停顿总次数(Minor/Major GC 次数,用action区分)
jvm_gc_pause_seconds_sum Counter GC 停顿累计总耗时 (秒)
jvm_gc_pause_seconds_max Gauge 最近窗口内单次 GC 最大停顿耗时 (秒)
jvm_gc_memory_allocated_bytes_total Counter GC 周期内新分配内存总量(反映对象分配压力)
jvm_gc_memory_promoted_bytes_total Counter 晋升到老年代的内存总量(对象晋升速率,OOM 前兆观测)
jvm_gc_live_data_size_bytes Gauge GC 后存活对象大小(老年代存活数据量,评估堆占用基线)
jvm_gc_max_data_size_bytes Gauge 老年代最大容量(Xmx 减去预留区后的可用容量)

常用 PromQL:

bash 复制代码
# GC 频率(按类型)
rate(jvm_gc_pause_seconds_count[5m])

# GC 耗时
rate(jvm_gc_pause_seconds_sum[5m]) / rate(jvm_gc_pause_seconds_count[5m])

# Full GC 次数(过去1小时)
increase(jvm_gc_pause_seconds_count{action="end of major GC"}[1h])

# Full GC 耗时
increase(jvm_gc_pause_seconds_sum{action="end of major GC"}[1h])

# Young GC 频率
increase(jvm_gc_pause_seconds_count{action="end of minor GC"}[1h])

# GC 停顿时间占比
rate(jvm_gc_pause_seconds_sum[5m])

# GC 后内存回收量
jvm_gc_memory_allocated_bytes_total - jvm_gc_memory_promoted_bytes_total

标签含义参数:

标签 标签值 含义说明
action end of minor GC Minor GC(新生代 GC)完成,一次年轻代 GC 停顿事件结束;G1 里就是 Young GC
end of major GC Major/Full GC 完成,老年代 / 整堆 GC 停顿结束,STW 耗时通常更长
cause G1 Evacuation Pause G1 年轻代 GC,对象拷贝转移停顿;最常见的 Minor GC 原因,Eden 分配满触发
Allocation Failure 内存分配失败:新对象分配时对应内存区域没有足够空间,触发 GC(最常见)
Metadata GC Threshold 元空间(Metaspace)占用达到阈值,触发 GC 清理类元数据
System.gc() 代码显式调用 System.gc() 触发的 GC,尽量避免生产环境出现
Heap Dump Initiated GC 堆 Dump 操作触发的 GC,抓堆快照时产生
Ergonomics JVM 自适应策略自动触发 GC,Parallel GC 常见
G1 Humongous Allocation G1 分配大对象(超过 region 一半)直接在老年代,触发 GC
G1 Mixed GC G1 混合 GC,同时回收年轻代 + 部分老年代 region
Concurrent Mode Failure CMS 并发标记失败,降级 Full GC,属于严重 GC 告警信号
Promotion Failed 晋升失败:年轻代对象晋升到老年代,老年代空间不足,触发 Full GC
gc G1 Young Generation G1 收集器,年轻代 GC(对应 minor GC)
G1 Old Generation G1 收集器,老年代 / Full GC(major GC)
PS Scavenge Parallel Scavenge,并行新生代收集器
PS MarkSweep Parallel Old,并行老年代 Full GC 收集器
CMS CMS 并发收集器(老年代)
ZGC ZGC 低延迟收集器
Shenandoah Shenandoah 低延迟收集器

分析要点:

  • Full GC 频率 > 1 次/小时判定严重,需立即排查
  • 单次 Full GC 耗时 > 3 秒需检查堆大小和 GC 算法
  • Young GC 过于频繁需检查 Eden 区大小设置
  • GC 停顿时间占比 > 5% 影响应用吞吐
  • Full GC 后 Old Gen 回收不明显 → 内存泄漏嫌疑

3)线程

主要指标:

Prometheus 指标名 指标类型 含义说明
jvm_threads_live_threads Gauge 当前存活的线程总数(含守护线程),JVM 全局线程水位
jvm_threads_daemon_threads Gauge 当前守护线程数(GC 线程、后台任务线程等)
jvm_threads_peak_threads Gauge JVM 启动以来线程数峰值,历史最高水位参考
jvm_threads_states_threads Gauge 按线程状态统计的线程数,带state标签:RUNNABLE/BLOCKED/WAITING/TIMED_WAITING/NEW/TERMINATED
jvm_threads_started_threads_total Counter JVM 启动以来累计创建线程总数(判断线程泄漏的关键)

常用 PromQL:

bash 复制代码
# 活跃线程数
jvm_threads_live_threads

# 峰值线程数
jvm_threads_peak_threads

# 守护线程数
jvm_threads_daemon_threads

# 线程状态分布
jvm_threads_states_threads

# 死锁线程数
jvm_threads_deadlocked_threads

# 线程数变化趋势
deriv(jvm_threads_live_threads[1h])

分析要点:

  • 线程数持续增长不释放需检查线程池配置
  • RUNNABLE 状态过多需检查是否有忙循环
  • BLOCKED / WAITING 线程过多需排查锁竞争
  • 死锁线程 > 0 需立即 dump 分析

4)类加载

主要指标:

Prometheus 指标名 指标类型 含义说明
jvm_classes_loaded_classes Gauge 当前已加载类总数(当前存活的已加载类数量)
jvm_classes_loaded_classes_total Counter 累计加载的类总次数(含被卸载后重新加载的类,单调递增)
jvm_classes_unloaded_classes_total Counter 累计卸载的类总次数(单调递增)

常用 PromQL:

bash 复制代码
# 已加载类数
jvm_classes_loaded_classes

# 类加载/卸载速率
rate(jvm_classes_loaded_classes_total[5m])
rate(jvm_classes_unloaded_classes_total[5m])

分析要点:

  • 类加载风暴 :rate(jvm_classes_loaded_classes_total[1m]) 突然飙升,常见原因:动态代理批量创建、反射调用频繁、热加载、动态生成类(如 CGLIB/ASM)。
  • Metaspace 关联 :类加载数量暴增常伴随 jvm_memory_used_bytes{area="nonheap"} 中 Metaspace 上涨,可联动 JVM 内存指标排查。
  • 类卸载异常 :长时间无卸载(jvm_classes_unloaded_classes_total 长期不变)可能意味着类加载器泄漏(如反复创建 ClassLoader 不释放)。

5)JIT 编译

JIT 编译全称"即时编译",是指在程序运行时将中间代码动态转换为机器码的技术 。它结合了解释执行和编译执行的优点,既能快速启动,又能保证运行效率。

主要指标:

Prometheus 指标名 指标类型 含义
jvm_compilation_time_seconds_total Counter JIT 编译器累计花费总时间(包含 C1、C2 所有编译),只增不减,实例重启清零

默认情况下没有该指标,需要额外依赖 micrometer-jvm-extras 或 通过 JMX Exporter 采集。

如果涉及动态编译、类动态加载,可考虑开放该指标。

4、Web容器线程池

不同 Web 容器的指标名称会有差异,本节以内置 Tomcat 10 为例说明。

SpringBoot3 + 内置 Tomcat10 默认不开启 MBean 注册 ,tomcat_threads_* 线程池指标不会暴露。参考如下方式开启:

bash 复制代码
server:
  tomcat:
    mbeanregistry:
      enabled: true

添加配置重启后,tomcat_threads_busy_threads / tomcat_threads_config_max_threads 即可出现在 /actuator/prometheus。

主要指标:

Prometheus 指标名 指标类型 含义
tomcat_threads_busy_threads Gauge 当前繁忙 HttpWorker 线程数,正在处理 HTTP 请求的线程
tomcat_threads_current_threads Gauge 线程池当前总线程数(空闲 + 繁忙)
tomcat_threads_config_max_threads Gauge - 配置的线程池最大线程数(server.tomcat.threads.max),- 超过之后不再新建 Worker 线程,新请求进入 accept 队列- 默认200,生产环境通常需要根据服务器配置调大
tomcat_threads_config_min_spare_threads Gauge 最小备用空闲线程数(server.tomcat.threads.min-spare)
tomcat_threads_queue_remaining_capacity Gauge Tomcat accept 队列剩余容量;值 = 0 代表队列打满,新请求排队 / 拒绝

micrometer 内置 Tomcat 绑定器,不会暴露 tomcat_threads_config_min_spare_threads、tomcat_threads_queue_remaining_capacity ,需要引入额外扩展指标。

分析要点:

  • tomcat_threads_busy_threads 高不一定是 CPU 高,大概率 IO 阻塞(DB、外部 HTTP、锁等待)
  • tomcat_threads_busy_threads 持续靠近 tomcat_threads_config_max_threads ,请求会进入 accept 队列排队
  • tomcat_threads_busy_threads / tomcat_threads_config_max_threads > 0.70 线程使用率过高
  • tomcat_threads_busy_threads / tomcat_threads_config_max_threads > 0.90 线程将帮忙,需立即处理

5、系统 / 进程指标

前缀: process_ 、`system_`

1)进程 CPU

主要指标:

Prometheus 指标名 指标类型 含义 备注
process_cpu_usage Gauge Java 进程最近 CPU 使用率,取值范围 0 ~ 1 1 = 1 个逻辑核打满;不是整机 CPU ,是该进程占用单个 vCPU 比例。常用除以system_cpu_count得到进程整体 CPU 占比
process_cpu_time_seconds_total Counter 进程累计消耗 CPU 总时长(秒),包含用户态 + 内核态 单调递增;increase(process_cpu_time_seconds_total[5m]) 看 5 分钟内 CPU 消耗

常用 PromQL:

bash 复制代码
# 进程占用整机CPU百分比(多核归一)
process_cpu_usage * 100

# 进程在所有逻辑核上总负载
process_cpu_usage / system_cpu_count

2)文件句柄

主要指标:

Prometheus 指标名 指标类型 含义 备注
process_files_open Gauge 当前进程打开的文件描述符数量 包含 socket 连接、文件句柄;高值警惕文件句柄泄漏
process_files_max Gauge 进程允许打开的最大文件描述符上限 对比 open 值,接近 max 就告警

常用 PromQL:

bash 复制代码
# 文件描述符使用率
process_files_open / process_files_max

3)进程运行时长

主要指标:

Prometheus 指标名 指标类型 含义 备注
process_uptime_seconds Gauge JVM 进程已运行时长(秒) 进程启动到现在多久,服务重启归零
process_start_time_seconds Gauge 进程启动时间戳(Unix 时间,秒) 固定不变,用于判断服务何时重启

常用 PromQL:

bash 复制代码
# 系统负载
process_start_time_seconds  # 启动时间

# 判断实例是否最近重启(启动时间大于当前时间就代表重启)
time() - process_start_time_seconds

4)系统负载

主要指标:

Prometheus 指标名 指标类型 含义
system_cpu_count Gauge JVM 可用逻辑 CPU 数量
system_cpu_usage Gauge 整机系统 CPU 使用率,范围 [0,1]
process_cpu_usage Gauge 当前 Java 进程 CPU 使用率,范围 [0,1]

6、数据库连接池

不同连接池,展示的指标名称也不相同。编写本文时全用的示例工程使用的是 Druid 连接池,下文针对 Duird 描述。

SpringBoot 原生 DataSource 自动绑定器只会生成通用 jdbc 前缀指标 jdbc.connections.* ,但 Druid 必须手动写代码注册 MeterBinder 或者使用 JMX Exporter 采集。

增加连接池指标:

参考如下代码暴露连接池指标,但仅能暴露少量指标,远不如 Druid 原生提供的监控页面。

maven 依赖

bash 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-3-starter</artifactId>
    <version>1.2.23</version>
</dependency>

代码示例

bash 复制代码
import com.alibaba.druid.pool.DruidDataSource;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.Collection;

@Component
public class DruidDataSourceMeterBinder implements MeterBinder {

    private final Collection<DataSource> dataSources;

    // 注入所有DataSource Bean(多数据源自动适配)
    public DruidDataSourceMeterBinder(Collection<DataSource> dataSources) {
        this.dataSources = dataSources;
    }

    @Override
    public void bindTo(MeterRegistry registry) {
        for (DataSource ds : dataSources) {
            if (ds instanceof DruidDataSource druidDs) {
                String dsName = druidDs.getName() == null ? "default" : druidDs.getName();

                // ========== 瞬时状态 Gauge ==========
                // 活跃连接 ActiveCount
                Gauge.builder("druid.connections.active", druidDs, DruidDataSource::getActiveCount)
                        .description("Druid active connections")
                        .tag("datasource", dsName)
                        .register(registry);

                // 空闲连接 PoolingCount
                Gauge.builder("druid.connections.idle", druidDs, DruidDataSource::getPoolingCount)
                        .description("Druid idle connections")
                        .tag("datasource", dsName)
                        .register(registry);

                // 等待线程 WaitThreadCount(核心告警指标)
                Gauge.builder("druid.connections.pending", druidDs, DruidDataSource::getWaitThreadCount)
                        .description("Druid pending wait threads for connection")
                        .tag("datasource", dsName)
                        .register(registry);

                // 最大连接 MaxActive
                Gauge.builder("druid.connections.max", druidDs, DruidDataSource::getMaxActive)
                        .description("Druid max active connection config")
                        .tag("datasource", dsName)
                        .register(registry);

                // 最小空闲 MinIdle
                Gauge.builder("druid.connections.min", druidDs, DruidDataSource::getMinIdle)
                        .description("Druid min idle connection config")
                        .tag("datasource", dsName)
                        .register(registry);
            }
        }
    }
}

主要指标:

Prometheus 指标名 指标类型 数据源方法 含义说明
druid_connections_active Gauge getActiveCount() 活跃连接数
druid_connections_idle Gauge getPoolingCount() 空闲连接数
druid_connections_pending Gauge getWaitThreadCount() 等待连接线程数,核心告警
druid_connections_max Gauge getMaxActive() 最大连接数配置
druid_connections_min Gauge getMinIdle() 最小空闲连接配置

提示:所有指标自带datasource标签,多数据源时用来区分不同实例。

常用 PromQL:

bash 复制代码
# 连接池使用率
druid_connections_active / druid_connections_max

# 各应用 JDBC 连接池使用率排行
topk(10, druid_connections_active / druid_connections_max * 100)

分析要点:

  • 连接池使用率 > 80% 判定警告,> 90% 判定严重,新请求可能因获取不到连接而超时
  • active 持续接近 max → 连接池不足,需扩容或优化慢查询释放连接

7、日志指标

主要指标:

Prometheus 指标名 类型 含义说明
logback_events_total Counter 日志事件总计数,带level标签:ERROR/WARN/INFO/DEBUG/TRACE,统计各日志级别输出条数

常用 PromQL:

bash 复制代码
# 每秒ERROR日志产生速率
rate(logback_events_total{level="ERROR"}[1m])

# 每秒WARN日志产生速率
rate(logback_events_total{level="WARN"}[1m])

# 统计最近1分钟ERROR日志总量
increase(logback_events_total{level="ERROR"}[1m])

8、缓存指标

内置 Tomcat 默认只有以下指标,本节暂不扩散 Ehcache、Redis 等缓存监控。

bash 复制代码
# HELP tomcat_cache_access_total  
# TYPE tomcat_cache_access_total counter
tomcat_cache_access_total 0.0
# HELP tomcat_cache_hit_total  
# TYPE tomcat_cache_hit_total counter
tomcat_cache_hit_total 0.0

9、定时任务

SpringBoot3 自动配置 TaskExecutorMetricsAutoConfiguration,Bean 类型必须是 ThreadPoolTaskExecutor / ThreadPoolTaskScheduler ,才会自动绑定 ExecutorServiceMetrics,输出指标到 /actuator/prometheus,指标前缀是 executor_,带标签 name="bean名称" 。

指标示例:

bash 复制代码
# HELP executor_pool_max_threads The maximum allowed number of threads in the pool
# TYPE executor_pool_max_threads gauge
executor_pool_max_threads{name="myTaskExecutor"} 10.0

主要指标:

Prometheus 指标名 指标类型 含义说明
executor_active_threads Gauge 线程池活跃线程数,正在执行任务的线程数量
executor_pool_size_threads Gauge 线程池当前线程数,线程池中已创建的线程总数
executor_pool_core_threads Gauge 线程池核心线程数,配置的核心线程数量
executor_pool_max_threads Gauge 线程池最大线程数,配置的最大允许创建线程数量
executor_queued_tasks Gauge 线程池队列任务数,队列中排队等待执行的任务数量(积压监控核心指标)
executor_queue_remaining_tasks Gauge 线程池队列剩余容量,队列还能接收的任务数量
executor_completed_tasks_total Counter 线程池已完成任务数,累计执行完成的任务总量

分析要点:

  • executor_active_threads 接近 executor_pool_max_threads → 线程池接近饱和,需扩容
  • executor_queued_tasks 持续增长需调整池大小或优化处理逻辑
  • executor_queue_remaining_tasks 接近 0 → 队列即将满,可能拒绝新任务

10、应用启动

主要指标:

指标名 类型 触发事件 含义 特点 & 备注
process_uptime_seconds Gauge JVM 启动时刻开始计时 JVM 进程运行总时长 持续不断递增;JVM 重启归零。来源:Runtime.getRuntime().uptime(),属于 JVM 底层指标,和 Spring 无关
application_started_time_seconds Gauge ApplicationStartedEvent Spring 上下文启动耗时 固定值,进程生命周期内不会变 。含义:JVM 启动 → Spring 容器刷新完成;触发后执行CommandLineRunner / ApplicationRunner
application_ready_time_seconds Gauge ApplicationReadyEvent 应用就绪耗时(可接收流量) 固定值,进程生命周期内不会变。含义:started 事件之后,所有 Runner 执行完毕,应用正式就绪,可以对外处理请求;K8s 就绪探针此时生效

时间线顺序(非常关键)

bash 复制代码
JVM进程启动
    ↓
【process_uptime_seconds】开始持续累加
    ↓
Spring容器加载、Bean实例化、上下文刷新完成 → ApplicationStartedEvent
    → application_started_time_seconds 记录此刻耗时(固定不变)
    ↓
执行 CommandLineRunner / ApplicationRunner(预热任务、初始化逻辑)
    ↓
Runner全部执行完成 → ApplicationReadyEvent
    → application_ready_time_seconds 记录就绪耗时(固定不变)
    ↓
应用可以接收请求,readiness探针成功

差值:application_ready_time_seconds - application_started_time_seconds = Runner 预热任务耗时,这个差值大就是启动慢的常见元凶(预热加载大量数据、远程调用)

11、业务自定义指标

强依赖应用业务属性,本篇暂不扩散。

三、告警规则参考

日常工作中涉及的主要规则参考。

# 分类 告警名称 (alert) 级别 for 触发条件 / 阈值 说明
1 HTTP 响应时间 AppHighAvgResponseTime warning 5m 平均响应时间 > 500ms(按实际调整) 平均响应时间过高,按应用特性调整阈值
2 HTTP 响应时间 AppCriticalAvgResponseTime critical 5m 平均响应时间 > 1s(文按实际调整) 平均响应严重超标,需立即排查
3 HTTP 错误率 AppHigh5xxErrorRate critical 5m 5xx 错误率 > 1%(建议值) 服务端错误,直接影响可用性
4 HTTP 错误率 AppHigh4xxErrorRate warning 5m 4xx 错误率 > 5%(建议值) 客户端错误突增,多为上游或接口变更
5 HTTP 流量 AppQpsAnomaly warning 5m QPS 环比波动 > 50%(建议值) 流量异常(暴涨/暴跌)
6 依赖组件健康 AppComponentUnhealthy critical 1m min(health_status) by (job, instance) != 1 任意依赖组件不健康,最常用告警表达式
7 JVM 内存 AppHeapUsageHigh warning 5m 堆内存使用率 > 80%(建议值) 接近 OOM 风险区
8 JVM 内存 AppHeapUsageCritical critical 5m 堆内存使用率持续 > 90%(建议值) 存在 OOM 风险
9 Tomcat 线程池 AppTomcatThreadsHigh warning 5m busy / max > 0.70(建议值) 线程使用率过高;busy 高多为 IO 阻塞而非 CPU
10 Tomcat 线程池 AppTomcatThreadsCritical critical 5m busy / max > 0.90(建议值) 请求进入 accept 队列排队,需立即处理
11 数据库连接池 AppDruidPoolUsageHigh warning 5m active / max > 0.80(建议值) 多数据源按 datasource 标签区分
12 数据库连接池 AppDruidPoolUsageCritical critical 5m active / max > 0.90(建议值) 新请求可能获取不到连接而超时

四、小结

回到日常运维本身,指标再多,真正需要盯住的其实就那么几个:总 QPS、5xx 错误率、堆内存使用率与 Full GC 次数,以及数据库连接池的活跃与等待情况。这五类指标基本覆盖了流量、可用性、内存与数据库四条命脉,一旦其中一项出现异常,其他指标往往会迅速联动,形成连锁反应。

落地建议上,Actuator 端点按需暴露,生产环境务必结合 Spring Security 做好权限隔离,避免敏感信息外泄;Micrometer 搭配 Prometheus 做指标采集,Grafana 负责可视化与告警配置,把阈值设在业务可感知的临界点之前,而非等服务雪崩后再补救。JVM 层面可以进一步接入持续 profiling,定位慢调用与内存泄漏的根因。

后续优化方向大致有三条:

  1. 一是把关键业务指标自定义埋点,让监控从"系统层"下沉到"业务层";
  2. 二是引入链路追踪,与指标打通,实现从告警到根因的快速跳转;
  3. 三是将告警规则纳入版本管理,随应用迭代同步调整,避免监控体系与业务脱节。

监控不是一次性的配置工作,而是一项需要持续打磨的基础设施。

相关推荐
鸿观工坊2 小时前
第1篇 Prometheus 监控体系全景与基础环境搭建
监控
苏渡苇8 天前
Spring Insight 里如何把 Span 画成瀑布时间线
后端·spring·spring cloud·springboot·监控·apm
行百里er8 天前
轻量级 Spring 监测工具——Spring Insight 发布了
spring boot·后端·监控
宋均浩9 天前
告警规则 243 砍到 27:Prometheus 降噪实战,日均打扰 47 次 → 3 次
云原生·监控·devops
小小ken10 天前
水星摄像头(MIPC552W)双摄版和乔安摄像头(JA-A12,JA-w1)接入easynvr具体步骤
监控·摄像头·nvr·easynvr·水星·乔安
行百里er10 天前
Spring Insight 里如何把 Span 画成瀑布时间线
spring boot·后端·监控
行百里er10 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring cloud·监控
北京盛世宏博12 天前
老旧档案库房环境恒温恒湿自动化改造:不破坏原有库房结构的轻量化集成方案
监控·档案温湿度
行百里er12 天前
Spring Insight 里如何画出服务拓扑
spring boot·spring cloud·监控