Spring Boot 应用运行起来之后,如果只依赖启动日志和手工 curl 探测,很难在故障发生前捕捉到 JVM 内存缓慢增长、数据库连接池逐渐耗尽这类渐进式问题。Prometheus 作为一款时序数据库,配合 Grafana 的可视化能力,正好可以补上这一层可观测性。但要把这三者串起来,中间还隔着 Micrometer 这个关键组件,它负责把 Spring Boot 内部的各种运行指标转换成 Prometheus 能理解的格式。下面直接进入实操环节,不讨论那些空泛的概念。

集成 Micrometer 暴露指标端点
Spring Boot 2.x 之后,actuator 和 Micrometer 的整合已经非常成熟。你只需要在项目里引入两个依赖:spring-boot-starter-actuator 和 micrometer-registry-prometheus。前者提供端点管理能力,后者负责把指标格式化为 Prometheus 的文本格式。如果用的是 Maven,在 pom.xml 中加入以下内容:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
依赖加好之后,还需要在 application.yml 或 application.properties 中暴露 Prometheus 端点。默认情况下 actuator 只开放 health 端点,其他端点需要显式配置。把 management.endpoints.web.exposure.include 设置为 prometheus,health,info 就足够了。另外建议把 management.metrics.tags.application 设置成应用名称,这样所有指标都会带上 application 标签,在 Prometheus 里方便按应用过滤数据。配置项如下:
management:
endpoints:
web:
exposure:
include: prometheus,health,info
metrics:
tags:
application: ${spring.application.name}
启动应用后访问 /actuator/prometheus,如果能看到大量以 jvm_、http_server_requests_ 开头的指标行,说明端点已经正常暴露。这一步看似简单,但标签设计会直接影响后续 Grafana 面板的复用性。不要把所有环境(开发、测试、生产)混在同一个 namespace 里,尽量利用 Prometheus 的 job 或 instance 标签区分,否则告警规则很容易误伤。
配置 Prometheus 抓取与目标发现
Prometheus 的抓取配置核心在 prometheus.yml 文件里。假设 Spring Boot 应用运行在 10.0.0.5:8080,你可以在 scrape_configs 下新增一个 job,把 metrics_path 设为 /actuator/prometheus,scrape_interval 一般设为 15 秒到 30 秒即可。如果应用是多实例部署,不要手动为每个实例写一条 static_configs,而是结合服务发现(比如 Kubernetes 的 Pod 发现或 Consul)自动找到所有实例,否则实例扩容时配置会变得很难维护。
scrape_configs:
- job_name: 'spring-boot-app'
metrics_path: '/actuator/prometheus'
scrape_interval: 15s
static_configs:
- targets:
- '10.0.0.5:8080'
labels:
env: 'production'
app: 'order-service'
实际生产环境中,Prometheus 本身也可能部署在 Kubernetes 里,此时用 relabel_configs 可以动态替换地址和标签。比如根据 Pod 的注解来决定是否抓取,避免把所有 Pod 的端口都扫描一遍。另一个容易忽略的点是时间序列基数:不要给每个请求都生成带高基数的标签(如 userId、traceId),这会让 Prometheus 的内存暴涨。Micrometer 默认的 http_server_requests 指标已经按 method、status、uri 做了聚合,如果 URI 里包含路径参数(例如 /orders/{id}),Micrometer 会自动把它归一化,不会产生大量唯一的时间序列。
配置完成后重启 Prometheus,在 Targets 页面能看到该 job 的状态为 UP 才代表抓取成功。如果遇到 connection refused,先检查 Spring Boot 应用是否监听了正确的端口,以及防火墙是否放行了 Prometheus 到应用的网络。另外注意 /actuator/prometheus 端点默认对所有人开放,如果应用暴露在公网,至少要在安全组层面对该端点做 IP 白名单,否则指标信息也会泄露应用内部细节。
Grafana 数据源与面板设计
Grafana 安装好之后,第一步是在 Configuration → Data Sources 里添加 Prometheus 数据源,URL 指向 Prometheus 的 HTTP 地址(例如 http://prometheus:9090)。建议勾选“Default”选项,这样后续导入的 Dashboard 会自动使用该数据源。如果不清楚怎么查询指标,可以先用 Explore 功能直接输入 PromQL 调试。比如查看 JVM 堆内存使用量,输入 jvm_memory_used_bytes{area="heap"} 就能看到每个实例的时序曲线。这里要注意 area 标签区分了 heap 和 nonheap,很多现成的 Grafana 模板默认只显示 heap,如果堆外内存吃紧会被忽略。
对于刚接触 PromQL 的人来说,先不要急着手工打造面板,可以从 Grafana 官方模板库导入 Spring Boot 相关的 Dashboard(例如 ID 为 10280 的 JVM 监控模板)。导入后按需调整:把 application 变量改成你自己的应用名,把 instance 变量改成实际实例地址。模板虽然方便,但通常不会覆盖所有自研业务的指标,所以最好花一天时间理解几个关键查询。下面给出几个常用 PromQL 示例:
# 每秒 HTTP 请求数
sum(rate(http_server_requests_seconds_count[5m])) by (uri, method)
# 99 分位响应时间(秒)
histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri))
# 线程池活跃线程数
jvm_threads_live_threads
# 老年代 GC 累计耗时(秒)
increase(jvm_gc_pause_seconds_sum{action="end of major GC"}[1h])
Grafana 面板的类型选择也有讲究:对于速率类指标用 Stat 或 Time series 面板,对于分位数趋势用 Time series 加多个查询叠加,对于整体资源占用可以用 Gauge 或者 Bar gauge。不要把所有指标都堆在同一个面板上,否则打开页面时浏览器会卡顿。可以把系统指标、JVM 指标、HTTP 指标分成三个 Row,每个 Row 下再分组。另外设置好自动刷新间隔,一般生产环境用 1 分钟即可,频繁刷新会消耗 Prometheus 的查询资源。
告警规则建议在 Grafana 里配置而不是在 Prometheus 的 alertmanager 里硬编码,因为 Grafana 的 UI 更容易维护,而且可以基于面板阈值直接触发。例如当某个接口的 99 分位响应时间连续 5 分钟超过 2 秒就发通知。通知渠道可以对接企业微信、钉钉或者邮件,注意消息里带上 application 和 instance 标签,不然收到告警都不知道是哪台机器出问题。
自定义指标与常见坑位
除了内置的 JVM 和 HTTP 指标,业务上通常还需要监控订单处理速度、缓存命中率、消息队列消费延迟等。Spring Boot 中通过注入 MeterRegistry 可以很方便地注册自定义指标。举个例子,想统计订单创建的速率,可以用 Counter,想跟踪处理耗时可以用 Timer。不过要注意 Timer 内部是用 histogram 实现的,默认分位数是基于所有时间窗口的全局统计,跟 Prometheus 的 histogram 机制略有不同。更推荐使用 @Timed 注解配合 AOP,或者在代码里手动记录,但前提是不要在高频调用路径上做复杂的标签计算,否则 CPU 开销会很明显。
另一个常见的坑是标签值包含特殊字符或者中文,Prometheus 的标签值支持任意 UTF-8 字符串,但某些 Grafana 模板在构建变量过滤时可能出问题。建议标签值使用小写字母、数字、连字符和下划线,避免使用点号(点号在 PromQL 里是标签分隔符)。还有一点:如果应用在容器环境中运行,别忘了暴露容器本身的 CPU、内存指标(通过 cAdvisor 或其他 Node Exporter),否则 JVM 内存正常但容器被杀掉的情况很难定位。
最后提醒一下时区问题。Prometheus 默认使用 UTC 时间存储数据,Grafana 显示时会根据用户的时区进行转换,但 PromQL 里的时间范围函数(如 increase())不会自动适配,所以跨天的告警规则要格外小心。整体来说,这套方案已经在大量生产环境中得到验证,只要按照上述步骤配置并定期观察面板趋势,大部分 Spring Boot 应用的稳定性问题都能提前暴露。

