在企业级应用的技术选型中,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 编译后的机器指令存放处 |
注意:
ZGC/Shenandoah 等低延迟 GC,heap 的 id 名称会变成
ZHeap等,不要硬编码 id 写死面板查询;NIO Direct 直接内存不在 jvm_memory_used_bytes ,看指标
jvm_buffer_memory_used_bytes;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,定位慢调用与内存泄漏的根因。
后续优化方向大致有三条:
- 一是把关键业务指标自定义埋点,让监控从"系统层"下沉到"业务层";
- 二是引入链路追踪,与指标打通,实现从告警到根因的快速跳转;
- 三是将告警规则纳入版本管理,随应用迭代同步调整,避免监控体系与业务脱节。
监控不是一次性的配置工作,而是一项需要持续打磨的基础设施。