news 2026/10/18 22:38:53

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

作者头像

张小明

前端开发工程师

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

不知道你有没有过这种经历:一个Spring Boot服务白天还跑得好好的,凌晨两点突然CPU飙升,等你看日志的时候进程已经挂了。这时候你才意识到,光把接口写对是不够的,还得知道它什么时候“不舒服”。今天这篇是Spring Boot系列的第40篇,我想认认真真聊一下Spring Boot应用监控——这块内容平时容易被忽略,但真正出问题时,它能帮你把排查时间从几小时压缩到几分钟。

用大白话说,应用监控就是给服务装上各种传感器:内存、CPU、线程数、HTTP请求量、接口耗时、数据库连接池占用……让这些数据以标准形式暴露出来,再通过采集、存储、展示、告警这条链路,让开发者能在服务真正崩掉之前发现问题。这篇文章适合所有正在写Spring Boot的开发者,不管你是刚学会写Controller的新手,还是已经在生产环境摸爬滚打的运维工程师,都可以照着下面的思路搭一套能落地的监控方案。

接下来我不打算堆一堆理论,直接分享我从一个只有health端点的小项目,一步步做到“看一眼面板就知道哪里有问题”的完整过程。

1. 用Actuator给应用穿上“体检服”:端点机制与暴露策略

1.1 从health端点说起

Spring Boot应用天生自带监控能力,核心就是spring-boot-starter-actuator。很多人可能只听说过/actuator/health,觉得它不就是用来探活吗?其实它的能力远不止如此。

我先给你看一个最简单的配置。在pom.xml里加依赖:

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

然后启动应用,访问http://localhost:8080/actuator/health,默认会返回:

{ "status": "UP" }

这个UP意味着应用能接受请求了。但注意,它只代表“进程活着”,不代表“服务健康”。比如数据库连接池满了,可能UP还是返回,但你的接口已经超时了。所以健康检查必须配合自定义状态。

1.2 端点暴露策略:别把全部家底晾在外头

Actuator有几十个内置端点,覆盖了信息、指标、日志、缓存、线程转储等。但Spring Boot出于安全考虑,默认只暴露health和info两个HTTP端点。你可以在application.yml里打开需要的端点:

management: endpoints: web: exposure: include: health,info,metrics,loggers,threaddump,heapdump

我建议不要用include: '*',虽然写起来省事,但生产环境一旦暴露了heapdump或shutdown,后果相当严重。shutdown端点默认禁用,这是一条红线,千万别在公网环境打开。即使在内网,也要配合Spring Security加一层认证。

这里有个我踩过的坑:某次为了让监控脚本能抓取数据,我把include设成了'*',结果内网的扫描器直接抓走了heapdump文件,里面全是JVM内存中的敏感信息,比如数据库密码、加密密钥。从那以后,我对端点暴露只有一个原则——按需开放,用啥开啥。

1.3 自定义HealthIndicator:健康检查要会说人话

健康检查如果只会返回UP和DOWN,其实价值有限。我见过很多项目把数据库、Redis、MQ的探活逻辑都揉进启动流程里,一旦某个组件挂了整个应用起不来。合理做法是通过HealthIndicator单独上报每个组件的状态。

比如我们有一个对接外部支付服务的模块,我就写过一个组件健康检查:

@Component public class PaymentServiceHealthIndicator implements HealthIndicator { @Override public Health health() { boolean reachable = checkPaymentService(); if (reachable) { return Health.up().withDetail("payment", "reachable").build(); } return Health.down().withDetail("payment", "timeout").build(); } }

这样配置中心就能看到具体的组件状态了:

{ "status": "DOWN", "components": { "paymentService": { "status": "DOWN", "details": { "payment": "timeout" } } } }

有了这个,报警时你能直接知道是支付回调没通,而不是一脸懵地去翻日志。健康检查不是给开发看“服务活着没”,而是给值班同学看“到底哪条链路断了”。

2. 指标数据的事故现场:从端点拉取到Prometheus落库

2.1 为什么我选了Prometheus而不是自己轮询

Actuator自带的metrics端点可以实时查询当前指标,但它只有一个“快照”,没有历史趋势。就像你看体温计能知道现在发烧了,但看不到过去几小时发烧曲线。要追查“什么时候开始异常”,就必须有存储系统。

我最早贪图简单,写了个定时任务每秒调用/actuator/metrics,把数据存到数据库里。结果数据量一大,监控查询和业务查询互相拖慢。后来换了Prometheus,才意识到专业采集器和自制采集脚本的差距。

引入Prometheus这套,只需要三步:

第一步,在pom.xml里加依赖:

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

第二步,保持prometheus端点是暴露状态:

management: endpoints: web: exposure: include: health,info,prometheus

第三步,在Prometheus的scrape_configs里加job:

scrape_configs: - job_name: 'my-spring-boot-app' metrics_path: '/actuator/prometheus' scrape_interval: 15s static_configs: - targets: ['localhost:8080']

这套配置完成后,Prometheus每15秒来拉一次指标。为什么拉取间隔是15秒?太短了会频繁唤醒应用产生额外开销,太长了又看不出秒级抖动。对大多数后台服务来说,15秒是性价比比较高的选择;如果对实时性要求高,比如涉及秒杀或抢单,可以调到5秒,但一定要评估好目标实例的数量和集群负载。

2.2 先看懂那些默认指标再说优化

Prometheus接入后,真正能发挥价值的是理解指标含义。我经常看到有人把一堆指标堆在面板上,出了事根本不知道看哪个。

我先列几个最基础、也最实用的指标:

指标名称作用常见异常信号
system_cpu_usage整个系统CPU占用率持续高于80%
jvm_memory_used_bytesJVM堆内存使用量(按区域区分)老年代持续增长不回落
jvm_gc_pause_secondsGC暂停时间单次超过200ms甚至1s
http_server_requests_seconds_countHTTP请求次数(按路径和状态码区分)错误码请求突然增加
http_server_requests_seconds_maxHTTP请求最大耗时某接口逐步上升
hikaricp_connections_active活跃数据库连接数接近最大值

有一个容易忽略的重点:jvm_memory_used_bytes如果你不看area这个标签,会把它当成一个笼统的内存值,实际上JVM堆内部很复杂。排查内存问题,建议按area:heap和area:nonheap分开看,再加一层id:PS Old Gen或者G1 Old Gen看老年代。这样才能判断是对象泄漏还是GC频繁导致的内存膨胀。

有一次线上系统慢慢卡顿,我看全局内存曲线是平的,觉得没问题。后来拆到jvm_gc_pause_seconds才发现,每分钟Full GC超过20次,每次平均300ms,请求响应时间自然被拖垮。这提醒我:指标一定要带着标签维度去看,否则平均值会掩盖大问题。

3. 监控面板不是越华丽越好:Grafana仪表盘设计思路

3.1 画什么图才有用

Prometheus有了数据,下一步就是可视化。Grafana是绕不开的选择,它支持Prometheus数据源,也比较容易上手。但真相是:面板上的图不是越多越好,一张没用的图反而会分散注意力。

我做监控面板有一个原则:先列问题,再画图。比如业务方抱怨接口慢,那我就要画P95和P99响应时间趋势,而不是画一张每分钟请求总量的饼图。

拿HTTP接口监控举例,我最常用的是下面这几个面板:

  • 请求量总览:按路径展示QPS曲线
  • 接口延迟分布:P50、P95、P99三条线叠加
  • 错误码分布:5xx数量的柱状图
  • 线程池活跃线程数:判断线程是否打满

核心计算逻辑并不复杂。比如P99延迟,可以直接在Grafana里用histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri))。这条PromQL的意思是:统计最近5分钟内每个URI的请求耗时分布,在累计直方图上取第99百分位。第一次看到这种写法可能觉得头大,但你只需要知道,_bucket是Prometheus直方图累积桶,histogram_quantile就是从累积分布里反推分位数的函数,用几次就会习惯。

3.2 告警规则怎么写才不炸群

画完图之后,告警才是监控里最让人又爱又恨的部分。我见过两种极端团队:一种完全不配告警,出问题靠客服电话;另一种配了几十条告警,每天早上被无关通知轰炸,重要告警反而没人看。

我常用的告警规则模板是这样的:

groups: - name: springboot-app rules: - alert: InstanceDown expr: up == 0 for: 1m labels: severity: critical annotations: summary: "Instance {{ $labels.instance }} is down" - alert: HighErrorRate expr: sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) / sum(rate(http_server_requests_seconds_count[5m])) > 0.05 for: 5m labels: severity: warning annotations: summary: "Error rate above 5% for 5 minutes"

写告警规则时要注意两点。第一点是for参数,它表示“该条件持续多久才触发”。比如错误率只飙了一瞬间,可能是某个请求超时重试,不值得立刻打扰人。持续5分钟才告警,就能过滤掉很多瞬时的毛刺。第二点是表达式要保证分母不为零,否则会除以0导致没有数据,告警规则变成无效。

告警文案也要写清楚。我看到有人告警消息只写“HighErrorRate”,值班同学收到后还得自己去查实例、查路径,白白浪费抢救时间。正确做法是把实例地址、接口名、当前值、最近5分钟的变化趋势都放在annotations里,让值班同学一打开手机就知道问题在哪。

4. 业务监控的埋点艺术:自定义指标、事件与标签

4.1 Micrometer埋点:从业务指标到监控指标

框架自带的指标能覆盖系统层面,但真正能反映业务健康度的是自定义指标。比如“订单创建量”“支付成功率”“消息队列堆积数”,这些数据你用日志也能看到,但没法在监控面板上实时观察,更不能按照用户维度做趋势分析。

Micrometer是Spring Boot默认的指标门面,支持多种监控后端。我们可以在Service层埋点:

import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Counter; @Service public class OrderService { private final Counter orderCreatedCounter; private final Timer payoutTimer; public OrderService(MeterRegistry meterRegistry) { this.orderCreatedCounter = meterRegistry.counter("business.order.created", "channel", "default"); this.payoutTimer = meterRegistry.timer("business.payout.duration", "payment", "union"); } public void createOrder(Order order) { // 业务逻辑 orderCreatedCounter.increment(); } public void payout(Order order) { long start = System.currentTimeMillis(); try { // 支付逻辑 } finally { payoutTimer.record(System.currentTimeMillis() - start, TimeUnit.MILLISECONDS); } } }

这样的好处是,监控字段可以精确匹配到业务语义。比如我想看某个支付渠道的耗时,我可以在标签里加payment=wechat、payment=union,然后用sum by (payment)聚合对比。

4.2 埋点常见坑:标签爆炸、调高采样、线程安全

Micrometer虽然好用,但用不好也会带来坑。

第一个大坑叫“标签爆炸”。如果你把用户ID、订单ID这种高基数的值放进标签,Prometheus的内存会被撑爆。我有一个团队的同学曾经把商品ID放进标签里,每分钟产生几百万个时序数据,直接把Prometheus打挂了。标签只能放低基数的枚举值,比如渠道、机房、应用版本、状态码。高基数数据老老实实用日志或链路追踪,别往指标里塞。

第二个坑是Timer的采样率。Micrometer默认每个时间窗口会记录全部数据吗?不是的,它的Timer默认按照指数桶的方式保存分布,但每次record的开销非常小。真正常见的问题是开发者在finally里记录耗时,但执行业务代码抛异常时,System.currentTimeMillis()的起止点没有对应上,导致耗时数据出现负值或巨大值。我习惯用Timer.Sample来包裹:

Timer.Sample sample = Timer.start(meterRegistry); try { // 业务代码 } finally { sample.stop(payoutTimer); }

这样能自动处理开始时间和结束时间,不用自己手搓毫秒数。

第三个坑是线程安全。Counter和Timer都是线程安全的,但如果你自己封装了一个Map来聚合指标,务必小心并发问题。我见过有人为了统计“每个SKU的销量”写了个ConcurrentHashMap,然后用computeIfAbsent去操作,看起来没问题,实际上多线程下可能会重复创建对象。指标类组件平时不显山不露水,出事的时候并发压力下才暴露出问题。

5. 生产环境的监控血泪史:线程池、数据库连接池、慢接口定位

5.1 线程池指标:怎么看出线程不够用

Spring Boot默认的Tomcat线程池可以从tomcat.threads.busy和tomcat.threads.config.max两个指标看出利用率。如果busy长期等于config.max,说明线程池已经跑满,请求开始排队,响应时间自然飙升。这时候不是无脑加线程数,而是先看线程都在等什么。

我有个印象很深的案例:某次大促预热时,tomcat.threads.busy一直是200,config.max也是200,所有接口平均耗时超过3秒。排查后发现,线程全卡在一个第三方鉴权服务的HTTP调用上,那个服务本身RT很慢。这时候加线程只会让下游压力更大,正确做法是对第三方调用加超时和熔断,让线程池快速释放。

所以线程池监控的重点不仅是看繁忙数,还要结合线程状态、队列长度、下游耗时一起看。我把这几类指标放在同一个面板上:

指标判断依据
tomcat.threads.busy持续接近config.max则打满
tomcat.threads.current不等于繁忙数,说明有线程空闲
tomcat.global.request.max单接口最大耗时,判断长尾
tomcat.global.error异常次数
http_server_requests_seconds_max按URI拆分,定位慢接口

5.2 数据库连接池:大多数“数据库慢”其实是连接等待

连接池监控里,hikaricp.connections.active和hikaricp.connections.pending是最要紧的两个指标。pending是正在等待连接获取的线程数,如果这个数一直大于0,说明连接池不够用或者连接被占着不放。

常见误区是看到active很高,马上调大maximum-pool-size。实际上连接池的大小不是越大越好,数据库的连接数都有限。我们遇到过MySQL最大连接数只有1000,应用把连接池调到300,两台实例一上线直接把数据库冲挂了。

正确排查思路是:先看慢SQL,再看代码里有没有连接泄漏。我曾经用SQLSession执行完查询后不关闭连接,导致连接池被占满,表面上是“数据库连接不够”,实际上是代码问题。后来我给数据库访问层做了拦截器,统一在finally里关闭资源,pending才归零。

5.3 慢接口定位:从监控反推代码问题

监控的价值不只是看报警,更重要是能为问题复盘提供线索。我们有一个接口平时P99只要20ms,某天突然变成800ms。第一反应是看监控面板,发现它调用的下游Redis节点redis_commands_seconds_bucket中,get命令的P99从0.1ms涨到50ms,说明Redis节点有问题。于是找运维同事检查,发现那个节点发生了持久化重写,导致阻塞。

但更多时候,慢接口是代码自己搞出来的。比如在循环里逐条调用数据库查询,或者用parallelStream并行执行,但共享线程池被打满。遇到这种情况,普通的监控看不了那么细,我会结合链路追踪工具,把一次请求的完整调用链录下来。这一步虽然不在Spring Boot原生监控范围内,但却是监控体系里不可或缺的延伸。

有一次我负责的模块在监控里看到某个接口耗时出现明显的“阶梯式上升”。开始以为是缓存失效,后来通过调用链发现,代码里新增了一个数据结构拷贝的逻辑,每次请求都会复制一个巨大的对象图,复制耗时随数据规模线性增长。这种问题,不看调用链真的很难定位。

所以我的建议是:系统基础指标靠Actuator和Prometheus,业务状态靠自定义埋点,而每一次“慢”的根因,则需要结合日志、调用链、数据库慢查询记录来综合判断。监控不是最终答案,它只是帮你缩小问题范围的那盏灯。

最后再分享一点我个人的体会:搭建监控不是一劳永逸的事,告警规则要随业务迭代不断调整,面板也不是一次性设计完就永远适用。我习惯了每个月回顾一次生产环境的历史告警,把连续一个月没触发过的规则删掉,把值班同学普遍觉得冗余的通知合并掉。监控的意义不是让不看的人把面板丢在角落,而是让真正需要的人,在问题发生的十分钟内就能看到答案。希望这篇Spring Boot应用监控的实战记录,能给正在建设监控体系的你一点启发。

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

SQL报错注入底层原理与MySQL解析栈深度解析

1. 项目概述&#xff1a;这不是“刷题”&#xff0c;而是一次数据库底层逻辑的沉浸式解剖“sqllib闯关攻略_01”——光看标题&#xff0c;你可能以为这是某款CTF新手村的通关笔记&#xff0c;或是某培训机构发的SQL注入速成口诀。但在我带过的几十个模拟项目X中&#xff0c;凡是…

作者头像 李华
网站建设 2026/10/18 2:40:24

AI视频短片生产流水线:节奏控制与主体一致性的实战工作流

1. 这不是“AI视频课”&#xff0c;而是一套可立即上手的短片生产流水线你点开过多少标着“吊打付费”“保姆级”的AI视频教程&#xff1f;我试过不下二十个&#xff0c;最后真正能让我在下班后两小时内剪出一条完整短视频的&#xff0c;不到三成。原因很简单&#xff1a;它们要…

作者头像 李华
网站建设 2026/10/18 13:56:48

柔性作业车间调度与NSGA-II多目标优化:Matlab实现与实战解析

1. 问题拆解&#xff1a;柔性作业车间调度到底难在哪做车间调度研究的同学应该都有同感——传统的作业车间调度&#xff08;JSP&#xff09;已经够让人头疼了&#xff0c;柔性作业车间调度&#xff08;FJSP&#xff09;更是在它基础上再添了一把火。JSP里每道工序只能在一台固定…

作者头像 李华
网站建设 2026/10/19 8:39:43

PS5串流全攻略:局域网、远程、采集卡三方案实测

1. “AnyPS5”到底在解决什么问题PS5这台机器&#xff0c;买的时候很痛快&#xff0c;真正用起来却经常被“屏幕绑定”卡住。客厅的电视被家里人占了、出差住酒店想顺手清个日常任务、躺在床上不想开大灯、想在办公室午休时把昨晚没打完的Boss磨过去——这些场景里&#xff0c;…

作者头像 李华
网站建设 2026/10/19 0:52:46

单片机电容触摸按键设计实战:从原理到PCB与代码落地

简介&#xff1a;本资源是一份面向单片机初学者与家电维修爱好者的电容感应式触摸按键技术详解文档&#xff0c;聚焦STC系列单片机&#xff08;如STC12C4052AD&#xff09;的纯软件实现方案&#xff0c;解决专用芯片普及不足、维修配件难寻、原理理解模糊等实际问题。文档深入剖…

作者头像 李华