news 2026/10/15 2:12:51

Spring Boot应用监控实战:Actuator + Micrometer + Prometheus + Grafana

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot应用监控实战:Actuator + Micrometer + Prometheus + Grafana

做后端开发这些年,我有个特别深的感受:服务能跑起来和“知道它正在怎么跑”,完全是两回事。接口返回 200 不代表没有隐患,CPU 看似空闲不代表没有内存泄漏,很多问题都是等用户先发现,然后我们才开始手忙脚乱翻日志。所以这期按系列进度聊一聊应用监控,而且是 Spring Boot 项目最顺手的一整套组合:Actuator 做端点暴露,Micrometer 做指标采集,Prometheus 做存储与抓取,Grafana 做可视化展示。这套方案上手成本不高,收益却非常直接,适合正在维护微服务、或者想把单体服务状态摸清楚的同学参考。

先说个真实感受:我以前也以为监控就是“加个依赖,看几个数字”,后来真正排过几次线上问题才明白,监控系统要回答的本质问题是——服务现在处于什么状态?状态是否在恶化?如果出问题,能不能快速定位到方向?带着这几个问题去看 Actuator、指标、面板,才不会把监控做成只是“好看的大屏”。

这篇文章不会停留在“引入依赖、打开几个端点”的程度,我会把端点的含义、指标的分类、生产环境下的安全与性能坑都过一遍,尽量让你看完就能照着搭出一套能真正辅助定位问题的监控体系。

1. 应用监控到底要解决什么问题

1.1 从“服务还活着”到“服务状态可感知”

新手阶段,我们判断服务是否正常最常用的办法是 curl 一下接口,能返回数据就认为没问题。这种方式属于典型的“黑盒监控”:只关注外部表现,不知道服务内部发生了什么。问题是接口能通,可能只是 Web 容器线程还没被耗尽,但数据库连接池已经快被占满;可能 HTTP 请求处理正常,但 GC 频繁导致停顿飙升,只是还没达到超时阈值。

Spring Boot Actuator 解决的就是“白盒监控”的问题。它把应用内部的状态通过标准化端点暴露出来,比如健康状态、内存用量、线程状态、配置信息、日志级别等。我们可以通过这些信息感知到服务“活得怎么样”,而不仅仅是“是不是活着”。这是应用监控的第一个价值:把不可见的东西变成可观测的数据。

我自己搭建监控体系时,通常会先问自己一个问题:如果这个服务明天凌晨三点挂了,我希望监控系统在挂掉之前的几个小时能给我什么线索?答案通常是:内存是否持续增长、GC 是否异常频繁、线程数是否突破预期、连接池是否耗尽。而这些数据,Actuator 基本都能提供基础指标,Prometheus 负责记录趋势,Grafana 负责呈现曲线。

1.2 监控的三个层次:健康检查、指标采集、链路追踪

完整应用监控可以拆成三个层次,理解这三个层次,才能避免把监控做成“单点工具”。

第一层是健康检查,回答“服务活着吗”。这一层最基础,通过定期探测 Actuator 的 /actuator/health 端点,能知道进程是否存活、依赖的组件是否可用(比如数据库、Redis、消息队列)。很多容器编排平台(比如 Kubernetes)就是靠这个端点做存活和就绪探针的。

第二层是指标采集,回答“服务状态如何”。这就是 Micrometer + Prometheus 的强项了。除 JVM 基础指标外,还可以自定义业务指标,比如订单创建速率、积压的任务数量、外部接口调用耗时分布。有了时序数据,才能看到状态变化的趋势。

第三层是链路追踪,回答“一次请求经历了什么”。这个通常要引入分布式链路追踪系统,比如 OpenTelemetry + Zipkin/Jaeger 的组合,能还原一次调用在各个服务之间的耗时和依赖关系。这不是本文的重点,但你需要知道,监控体系里它是独立且重要的一块。

很多人做监控只盯着第二层,把大量精力花在“指标多不多”上,却忽视了第一层。我的建议是先把健康检查做扎实,再逐步完善指标,链路追踪按团队实际需要引入,不要一上来就铺大摊子。

2. Spring Boot Actuator 核心端点拆解

2.1 引入依赖与最小配置

在 Spring Boot 项目中使用 Actuator,最直接的方式是添加 spring-boot-starter-actuator 依赖。以 Maven 为例:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>

如果你的项目是 Spring Boot 2.x 或 3.x,只要 spring-boot-starter-parent 管理好了版本号,这里不需要指定版本。需要注意的是,光加依赖还不能访问所有端点,因为 Actuator 默认只暴露 health 等少数几个端点。老版本曾经暴露得过宽,后来官方把默认策略改成了只暴露 health,这个改动非常正确,也提醒我们:暴露端点本身就是一种风险面,能少暴露就少暴露。

在 application.yml 里,我习惯做这样一份最小配置:

management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always metrics: tags: application: ${spring.application.name}

这里 include 配置了需要暴露的端点。如果用的是 Spring Boot 2.x,需要额外引入 micrometer-registry-prometheus 才能让 prometheus 端点生效,后面会讲。加 metrics 这个端点是为了开发时快速查看当前应用有哪些指标,生产环境看情况,建议还是在 Prometheus 里看。

启动应用后,访问 http://localhost:8080/actuator/health,正常情况会返回类似下面这样:

{ "status": "UP", "components": { "diskSpace": { "status": "UP", "details": { "total": 500107264000, "free": 322075525120, "threshold": 10485760, "exists": true } }, "ping": { "status": "UP" } } }

注意,这里的 status 不是指你的业务接口是否正常,而是指 Spring Boot 内部所有健康指示器的综合结果。只要有任何一个关键依赖处于 DOWN,整体状态就会变成 DOWN,这个设计很合理。

2.2 health 端点与自定义 HealthIndicator

Actuator 的健康检查是分组件判定的。数据库连接、Redis、Elasticsearch、RabbitMQ 等,只要 classpath 里有对应的客户端依赖,Spring Boot 就会自动注册相应的 HealthIndicator。这也是为什么健康检查能覆盖到“数据库是否可用”的原因。

但它只能检查“它认识的组件”,如果我们的服务强依赖某个外部接口或本地文件目录,就需要自己写健康检查逻辑。实现方式很简单:实现 HealthIndicator 接口,并在应用上下文中注册为 Bean。

@Component public class ExternalOrderServiceHealthIndicator implements HealthIndicator { private final RestTemplate restTemplate = new RestTemplate(); @Override public Health health() { try { ResponseEntity<String> response = restTemplate.exchange( "https://example-order-api/health", HttpMethod.GET, null, String.class ); if (response.getStatusCode().is2xxSuccessful()) { return Health.up() .withDetail("externalOrderService", "available") .build(); } return Health.down() .withDetail("externalOrderService", "unexpected status: " + response.getStatusCode()) .build(); } catch (Exception e) { return Health.down(e).build(); } } }

这样在 /actuator/health 的返回结果里就会多出一个 externalOrderService 节点,如果它挂了,整体状态会变成 DOWN,编排平台会据此重启实例或者摘除流量。这里有个很容易踩的坑:健康检查里的网络调用必须设置超时时间,否则一旦外部服务一直不响应,健康检查端点本身就会卡住,反而不健康了。RestTemplate 默认没有超时限制,建议显式配置连接超时和读取超时。

另外,Spring Boot 2.3+ 支持按“可用性组”暴露不同的健康信息,比如把 liveness 和 readiness 拆开,这个在容器环境特别实用,放到后面单独说。

2.3 常用的其他端点

除了 health,实战场上我经常用的端点还有这么几个:

端点路径主要作用使用场景
/actuator/info展示自定义的应用信息查看版本号、Git commit、构建时间
/actuator/metrics查看应用已采集的所有指标名排查某个指标是否存在
/actuator/metrics/{name}查看某个指标的详细数值查看具体 JVM 指标
/actuator/loggers查看和修改日志级别线上临时把某个包调成 DEBUG
/actuator/env查看环境配置确认某个配置项的生效值
/actuator/beans列出所有 Bean排查 Bean 冲突、确认是否注册
/actuator/threaddump导出线程快照排查线程阻塞、死锁问题
/actuator/heapdump导出堆内存快照分析内存泄漏

我最常用的是 loggers 和 threaddump。loggers 可以动态调整日志级别,不需要重启应用,排查问题时非常方便。相关用法是 POST 请求 /actuator/loggers/{package},请求体构造如下:

{ "configuredLevel": "DEBUG" }

threaddump 则适合在服务明显卡顿或 CPU 飙高时使用。导出的线程快照可以直接用分析工具查看各线程的状态和锁信息,能快速看出是否有线程长时间卡在某个锁上。

这里要提醒一点:不要把 /actuator/env 和 /actuator/threaddump 这类信息敏感或存在性能消耗的端点直接暴露到公网。env 端点可能泄露配置项,threaddump 短时间大量访问会带来额外开销。建议只在内网使用,或者通过 Spring Security 做访问控制。

3. 用 Micrometer 把指标做细

3.1 Micrometer 是谁,为什么选它

Spring Boot 的指标采集并不是直接写给 Prometheus 的,而是通过 Micrometer 这个门面层。打个比方,Micrometer 像是日志领域的 SLF4J,应用里只面向它写指标,至于指标最终是给 Prometheus、InfluxDB 还是云端监控,由具体的 registry 实现决定。

这个设计带来的好处是:你的业务代码不依赖某个具体的监控系统。以后如果团队从 Prometheus 换成其他时序数据库,业务层的指标代码不用改,只需要替换依赖和配置。

Micrometer 定义了四种最核心的计量器(Meter):Counter 计数、Gauge 当前值、Timer 耗时分布、DistributionSummary 数值分布。理解这四种类型,基本就能覆盖绝大多数业务监控需求。

  • Counter 只增不减,适合统计累计值,比如订单总数、请求总数。
  • Gauge 表示当前快照值,可以上下波动,比如当前线程数、当前队列大小。
  • Timer 用来记录事件耗时,同时统计次数和耗时分布,比如接口响应时间。
  • DistributionSummary 用来记录一组数值的分布,比如订单金额分布。

3.2 自定义业务指标:一个电商下单场景

理论讲再多,不如写一段代码。假设我们有个简单的电商下单服务,需要监控三个点:下单总次数、当前排队处理的订单数、一次下单全流程的耗时。

先注入 MeterRegistry,然后在服务中使用:

@Service public class OrderService { private final Counter orderCreateCounter; private final AtomicInteger pendingOrders = new AtomicInteger(0); private final Timer orderCreateTimer; public OrderService(MeterRegistry registry) { this.orderCreateCounter = Counter.builder("order.create.total") .description("Total number of created orders") .tag("channel", "api") .register(registry); this.pendingOrders = registry.gauge("order.pending", pendingOrders); this.orderCreateTimer = Timer.builder("order.create.duration") .description("Time taken to create an order") .publishPercentileHistogram() .register(registry); } public void createOrder(Order order) { // 模拟排队处理 pendingOrders.incrementAndGet(); Timer.Sample sample = Timer.start(); try { // 实际的创建订单逻辑 Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { sample.stop(orderCreateTimer); pendingOrders.decrementAndGet(); } orderCreateCounter.increment(); } }

这里有个细节值得展开:Gauge 的注册方式。我上面用的是registry.gauge("order.pending", pendingOrders),Micrometer 会直接通过反射拿到 AtomicInteger 的数值,每次 Prometheus 来抓取时获取的是“当时”的值。Gauge 不应该在业务代码里手动 set,因为它的语义就是“当前状态由外部对象决定”,而不是由你每次累加决定。

再来看 Timer。我使用了publishPercentileHistogram(),这会生成基于直方图的百分位数指标,Granafa 里可以用histogram_quantile计算出 P95、P99 耗时。如果不调用这个方法,Timer 默认只记录 count 和 totalTime,看不到耗时分布,监控价值大大降低。

启动应用并暴露 prometheus 端点后,我们就能在抓取结果里看到类似这样的指标:

order_create_total{application="order-service",channel="api"} 3.0 order_pending{application="order-service"} 2.0 order_create_duration_seconds_count{application="order-service"} 3.0 order_create_duration_seconds_sum{application="order-service"} 0.187

注意 Micrometer 会把驼峰命名的指标转换成下划线命名,为了统一规范,业务指标名最好从一开始就按下划线风格取,避免出现 order.create.duration 被翻译成 order_create_duration 这种被迫转换的情况,虽然能用,但读起来没那么顺。

3.3 命名与标签的艺术

指标系统里有一句话:“指标名是变量名,标签是维度。”好的标签设计能让监控从“发现异常”升级到“定位异常”。

举一个非常典型的反面案例:有人做过一个“按用户维度统计订单量”的指标,于是给 Counter 加了个 tag:userId。看起来很有道理,每个用户下单都能查到。但用户量有百万级,每个用户都是一个唯一的标签组合,这就形成了高基数(high cardinality)问题。Prometheus 这类时序数据库对标签组合的数量非常敏感,高基数指标会把内存和磁盘占用抬高几个数量级,最终查询变慢甚至集群不稳定。

正确做法是:把用户维度放到日志里,或者单独存到业务库做统计,而不是放进 Prometheus 指标。监控指标更适合聚合,不适合精确到某个业务对象。

另外一个常见问题是标签命名不一致。比如有的地方写app,有的写application,有的写service,导致 Grafana 查询时经常写错标签名。我的习惯是在项目初期就约定一套公共标签:application 表示服务名、instance 表示实例标识、env 表示环境(dev/test/prod)。通过配置统一注入:

management: metrics: tags: application: ${spring.application.name} env: ${spring.profiles.active:default}

这样所有指标都会自带 application 和 env 标签,在做跨服务对比时非常方便。

4. 与 Prometheus 和 Grafana 集成

4.1 接入 Prometheus 端点

Actuator 默认并不能直接输出 Prometheus 格式的数据,需要添加 Micrometer 的 Prometheus registry 依赖:

<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>

Spring Boot 检测到这个依赖后,会自动注册一个类型为 PrometheusMeterRegistry 的 Bean,并启用 /actuator/prometheus 端点。重启应用,访问这个端点,会看到一堆以 # HELP 和 # TYPE 开头的文本数据:

# HELP jvm_memory_used_bytes The amount of used memory # TYPE jvm_memory_used_bytes gauge jvm_memory_used_bytes{area="heap",id="PS Eden Space",} 2.58837064E8

这个文本格式就是 Prometheus 的抓取格式,每行一个样本,包含指标名、标签和时间戳(通常省略)。开发模式下,可以用 curl 快速确认端点是否正常:

curl -s http://localhost:8080/actuator/prometheus | grep -i order_create_total

如果能看到上面自定义的指标,说明整个链路已经通了。

4.2 配置 Prometheus 抓取

Prometheus 本身的部署不多赘述,重点是采集配置。一个最简的 prometheus.yml 片段长这样:

scrape_configs: - job_name: 'spring-boot-apps' metrics_path: '/actuator/prometheus' static_configs: - targets: - 'localhost:8080'

这里的 metrics_path 必须和 Spring Boot 暴露的路径保持一致。如果应用配置了management.endpoints.web.base-path,路径会相应变化。比如你设了:

management: endpoints: web: base-path: /manage

那么 Prometheus 里的 metrics_path 就要改成 /manage/prometheus,否则抓取会返回 404。这是集成时最容易犯的错,真遇到了别急着怀疑配置,先 curl 一下实际路径。

配置好之后,访问 Prometheus 的 Targets 页面,正常情况下能看到 Spring Boot 应用的实例状态为 UP。若状态是 DOWN,大概率是网络不通、端口未监听、或路径不对。看抓取报错时,Prometheus 页面会给出响应状态码和错误信息,排查效率会高很多。

4.3 在 Grafana 里搭一块面板

Prometheus 负责存储和查询,展示则交给 Grafana。建好 Prometheus 数据源之后,就可以写查询表达式做面板了。

最常用的是 JVM 内存曲线:

sum(jvm_memory_used_bytes) by (area)

这个表达式把所有内存区域的使用量按 area 聚合,能直观看到堆内存和非堆内存的变化。配合 jvm_memory_max_bytes 一起看,能发现内存是否接近上限。

接口响应耗时是另一个核心指标。Spring Boot 默认通过 Micrometer 采集 http.server.requests 指标,包含耗时和 HTTP 状态码。在 Grafana 里可以用 PromQL 计算 P95:

histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[5m])) by (le))

这里依赖 http_server_requests_seconds_bucket 这个直方图桶数据。如果面板显示无数据,先确认指标名是否一致。不同 Spring Boot 版本和 Micrometer 版本之间,指标名的前缀可能不同,比如有的版本会带_seconds_*后缀,有的则不是,建议先在 Prometheus 的 Graph 页面敲一个 http_server 看看自动补全出来什么。

我自己的习惯是先在 Prometheus 手工查询确认指标存在、标签正确,再进 Grafana 做面板,不要在 Grafana 里反复试错。毕竟 Prometheus 的查询框能直接看到返回的原始样本,比 Grafana 的编辑器直观得多。

5. 监控落地时的常见坑

5.1 端点暴露安全控制

Actuator 好用,但也是攻击面。早些年很多团队图方便,直接把所有端点暴露出去,结果 /actuator/env 和 /actuator/heapdump 被恶意利用,带来了不少安全事件。生产环境我建议采用以下策略:

  • 通过 include 精确列出需要暴露的端点,而不是用通配符 *。
  • 所有端点不要直接暴露到公网,放在内网或通过网关统一管控。
  • 如果必须暴露到公网,用 Spring Security 做 Basic Auth 或更高级的认证。
  • 独立设置 management 端口,例如 9090,避免监控端口和业务端口混在一起。

配置示例:

management: server: port: 9090 endpoints: web: exposure: include: health,info,prometheus

这样监控端点只在 9090 端口上提供服务,与业务端口隔离,外部网络策略也容易针对单独端口做限制。注意管理端口独立后,Prometheus 的抓取 target 端口也要改成 9090。

5.2 高基数标签与指标爆炸

上一节提到过按 userId 打标签的案例,这里再展开讲一下“指标爆炸”的另一个表现:动态 tag 的使用。有的业务代码会从请求参数里拿出一个值直接塞进 tag,比如接口名,如果这个接口名本身是用户可控的,那就可能产生无限增长的标签组合,比固定标签危害更大。

应对思路并不复杂:

  • 先问这个标签有没有上限,持续增长率是多少。
  • 标签值承载的如果是反序列化后的对象 ID、随机字符串、URL 路径中的参数,基本都要排除。
  • 需要按动态维度统计时,优先考虑用固定枚举值(如状态码、渠道、类型)做标签。
  • 对于确实要追踪的高基数维度,另建一张表或使用日志链路,不在 Prometheus 里硬扛。

如果你的 Prometheus 内存已经告警,第一步不是加机器,而是排查哪些指标拥有不合理的标签组合。在 Prometheus 页面执行topk(10, count by (__name__) (up))这类查询,很难直接定位,更实用的办法是在 Grafana 里按指标名的样本数量排出 Top,先找到那些“变种”特别多的高基数源头。

5.3 容器环境里的健康检查探针

如果把应用部署到 Kubernetes 里,健康检查就不能只图“接口返回 200 就算过”。

K8s 的探针分为 livenessProbe(存活探针)和 readinessProbe(就绪探针)。liveness 探测失败会重启容器,readiness 探测失败会把 Pod 从 Service 的端点列表里摘除,不再转发新流量。

Spring Boot 2.3 开始支持把 health 端点分成两组:liveness 和 readiness,通过可用性组(Availability Groups)实现。配置如下:

management: endpoint: health: group: liveness: include: ping readiness: include: ping,diskSpace,db

然后在 K8s 部署文件里分别配置:

livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 120 periodSeconds: 30 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 periodSeconds: 5

为什么要区分这两类?因为有些依赖出问题,应用并不需要重启,而是不应该继续接收新流量。比如数据库连接池耗尽,重启应用未必能立刻恢复,更合适的做法是先把实例摘除,等依赖恢复后再重新接收流量。如果只用同一个 health 端点做 liveness,数据库抖动时容器会被频繁重启,反而加剧问题。

我自己还吃过一个教训:livenessProbe 的 initialDelaySeconds 太短。服务启动时如果初始化逻辑比较重,前几次探针没就绪就被判定失败,容器反复重启,形成“启动-被杀-再启动”的循环。所以宁可把初始延迟调大一点,也不要让探针过早介入。

5.4 多实例与全局监控思路

当服务从单实例扩展到多实例后,Prometheus 抓到的指标会带 instance 标签,同一个指标会有多条时间序列。比如 3 个实例的 order-service,jvm_memory_used_bytes 会有 3 条数据,分别对应不同 instance。

这带来的第一个问题是:看面板时如果不聚合,曲线会密密麻麻看不过来。此时应该先用 sum 或 avg 聚合,再按 application 分组展示:

sum(jvm_memory_used_bytes) by (application)

第二个问题是:单个实例的异常可能被聚合曲线平滑掉。比如 3 个实例中 1 个内存飙升,平均曲线可能只是缓慢上升。所以面板上既要保留总览视图,也要保留单实例视图,或者对单实例指标单独配置告警。

多实例场景下,Prometheus 的抓取配置也有两种思路:静态写死 targets,或通过服务发现动态发现实例。静态配置适合实例数量少且不变的环境,一旦实例动态扩缩容,就得靠服务发现(如 Kubernetes 的 pod 发现机制)来保证新实例自动被加入抓取范围。

6. 踩过这些坑之后的几点建议

如果让我带着现在的经验重新搭一套监控,我会把顺序调整成这样:先配好 health 端点和容器探针,保证“死没死”能被准确感知;再接入 Prometheus,确认 JVM 基础指标能抓到;然后才加业务指标,而且每次加新指标都要过一遍“标签枚举值有限吗、周期内样本量可预估吗”两个问题;最后再做 Grafana 面板和告警。

告警规则也别一开始就铺很多,先挑最核心的几类:进程健康状态、JVM 堆内存使用率、接口 P95 耗时、数据库连接池活跃数。告警的意义不是轰炸,而是在问题实际影响用户之前给出信号。建议每个指标都带上可执行的动作说明,比如收到”活跃连接数突增“,你第一时间应该去看慢查询、看线程栈,而不是先去开会。

还有一个常被忽略的细节:监控配置应该跟着服务走,纳入版本管理。application.yml 里那段 management 配置、prometheus 的抓取配置、Grafana 面板的 JSON,都是可维护的资产。我之前吃过亏,面板在测试环境手动调好了,上线前忘了同步配置,结果生产环境监控是“半瞎”状态,这个教训挺深刻的。

最后说一句体会:应用监控不是一个“做完就结束”的功能,它更像一套需要持续校准的仪表盘。指标会随业务变化而失去意义,面板会随架构调整而需要更新,但它给你带来的价值,往往体现在某次凌晨的告警里——你能早于用户发现问题,并且知道该往哪个方向查。这个感觉,比任何指标数字都更实在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 2:12:34

嵌入式GUI是库还是系统?FlyThings OS三层架构解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 2:12:28

Natron ClipTest 节点使用指南:用斑马纹精准检出画面颜色越界

音视频视频处理图形学桌面应用 【免费下载链接】Natron Open-source video compositing software. Node-graph based. Similar in functionalities to Adobe After Effects and Nuke by The Foundry. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/na/Natron 点击查看 …

作者头像 李华