专栏:《Java后端工程师进阶之路》从CRUD到AI工程师的完整跃迁路径(Day 20/ 90):从"服务挂了才发现"到"服务还没挂就知道",差的不是运气,是一套像样的监控。
场景再现:"线上交易系统响应超时,CPU飙到90%。jstack抓了一堆线程快,最后发现是一个第三方回调接口挂了,线程池全堵在那儿等超时。
答案很扎心——系统没有暴露任何可观测的指标。就像一个黑箱,外面的人只能通过"坏了没"来判断好坏。
后来全面接入了 Spring Boot Actuator,配合 Prometheus + Grafana 搭了一套监控体系。从那以后,服务还没挂,钉钉上就先弹告警了。
二、Actuator 到底是个什么东西
Actuator 是 Spring Boot 官方提供的生产级监控模块。它用一句话概括就是:给你的 Spring Boot 应用装上各种"传感器",把 JVM、Web 容器、数据库连接、自定义业务指标全部暴露出来,供外部监控系统消费。
一句话记住它的定位:Actuator 不是监控系统,它是被监控的那一端。它负责"生产数据",Prometheus 负责"采集数据",Grafana 负责"展示数据"。
2.1 快速接入
Spring Boot 2.x / 3.x 项目,加一个依赖就够了:
<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>什么配置都不写,启动项目,访问 http://localhost:8080/actuator,你会看到所有默认暴露的端点:
{ "_links": { "self": { "href": "http://localhost:8080/actuator", "templated": false }, "health": { "href": "http://localhost:8080/actuator/health", "templated": false }, "health-path": { "href": "http://localhost:8080/actuator/health/{*path}", "templated": true } } }默认只暴露了health这一个端点——这是 Spring Boot 的安全策略,细节信息不能随便给人看。
2.2 端点全家福
Actuator 自带了 20+ 个内置端点,我挑几个你一定会用到的:
| 端点 | 作用 | 一句话讲清楚 |
|---|---|---|
/health | 健康检查 | 服务还活着吗?数据库连得上吗?Redis通吗? |
/metrics | 指标体系 | JVM内存用了多少?HTTP请求QPS多少? |
/info | 应用信息 | 版本号、构建时间、Git Commit Hash |
/env | 环境变量 | 当前生效的配置项有哪些,值是多少 |
/loggers | 日志级别 | 不重启就能调日志级别 |
/threaddump | 线程转储 | 等同于jstack,在线看线程状态 |
/heapdump | 堆转储 | 等同于jmap,下载.hprof文件分析 |
/beans | Bean清单 | 所有Spring Bean以及它们的依赖关系 |
/mappings | 请求映射 | 所有@RequestMapping的路由信息 |
这些端点咋一看像是"调试工具合集",但当你把它们接入监控系统之后,价值就完全不一样了。
三、实战一:健康检查——不只是"活没活着"
很多团队把/actuator/health配在 K8s 的 Liveness Probe 上,返回 200 就认为服务健康。但这远远不够。
看一个真实场景:你的服务启动正常,HTTP 端口也在监听,但 Redis 连接池因为网络抖动全部断开。这时候/health如果只返回{"status": "UP"},你的负载均衡器会继续把流量打过来,然后所有需要 Redis 的请求全部报错。
正确的做法:启用详细健康检查。
# application.yml management: endpoint: health: show-details: always # 生产环境建议用 when-authorized show-components: always endpoints: web: exposure: include: health,metrics,prometheus然后在项目中引入对应的 HealthIndicator:
// 这是 Spring Boot 自带的,引入 starter-data-redis 后自动注册 // 你也可以自定义: import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @Component public class PaymentGatewayHealthIndicator implements HealthIndicator { @Override public Health health() { // 模拟检查第三方支付网关连通性 boolean reachable = checkPaymentGateway(); if (reachable) { return Health.up() .withDetail("gateway", "alipay") .withDetail("latency_ms", 120) .build(); } return Health.down() .withDetail("gateway", "alipay") .withDetail("error", "Connection timeout after 5000ms") .build(); } private boolean checkPaymentGateway() { // 实际项目中,这里发一个 HTTP HEAD 请求探测 return true; } }接入后,/actuator/health的返回就会是这样:
{ "status": "UP", "components": { "db": { "status": "UP", "details": { "database": "MySQL", "validationQuery": "isValid()" } }, "redis": { "status": "UP", "details": { "version": "7.0.15" } }, "paymentGateway": { "status": "UP", "details": { "gateway": "alipay", "latency_ms": 120 } }, "diskSpace": { "status": "UP", "details": { "total": 500123123712, "free": 320456789012 } } } }这下运维同事就不用来问你"Redis 是不是挂了"——看一眼健康检查页面,哪个组件出了问题一目了然。
关键知识点:
show-details: always在生产环境有安全风险(暴露内部组件细节),建议用when-authorized,配合 Spring Security 做身份校验- 自定义 HealthIndicator 一定要快速返回(< 1秒),不要在健康检查里做耗时操作
- K8s Readiness Probe 比 Liveness Probe 更适合依赖健康检查——服务"活着",但暂时不能接流量
四、实战二:Metrics 指标——给系统装上仪表盘
/actuator/metrics是 Actuator 最值钱的功能。它基于Micrometer这个门面框架,屏蔽了下游监控系统的差异——你用 Prometheus、InfluxDB、Datadog 还是 Graphite,代码完全不用改。
4.1 看一眼默认指标
访问/actuator/metrics会列出所有指标名称(不直接显示值)。要查看具体某个指标,需要加名字:
GET /actuator/metrics/jvm.memory.used返回:
{ "name": "jvm.memory.used", "measurements": [ { "statistic": "VALUE", "value": 2.3846912E8 } ], "availableTags": [ { "tag": "area", "values": ["heap", "nonheap"] }, { "tag": "id", "values": ["G1 Survivor Space", "G1 Eden Space", ...] } ] }这里插一句:JVM 领域的几个核心指标你心里要有数:
| 指标前缀 | 关注什么 | 告警阈值建议 |
|---|---|---|
jvm.memory.used/jvm.memory.max | 堆内存使用率 | > 85% 告警 |
jvm.gc.pause | GC 暂停时间 | P99 > 200ms 告警 |
jvm.threads.live | 活跃线程数 | 持续增长 > 500 排查 |
http.server.requests | HTTP 请求延迟/错误率 | P99 > 1s 或错误率 > 1% |
tomcat.threads.busy | Tomcat 工作线程繁忙度 | > 80% 准备扩容 |
4.2 自定义业务指标
系统指标看完,你自己的业务指标更重要。比如支付接口的调用量、成功率、耗时。
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import org.springframework.web.bind.annotation.*; import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/api/payment") public class PaymentController { private final MeterRegistry meterRegistry; private final Timer paymentTimer; private final Counter paymentSuccessCounter; private final Counter paymentFailCounter; public PaymentController(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; // Timer 自动记录调用次数、总耗时、最大耗时 this.paymentTimer = Timer.builder("business.payment.request") .description("支付接口请求耗时") .publishPercentileHistogram(true) // 启用直方图,支持 P50/P90/P99 .register(meterRegistry); // Counter 只增不减 this.paymentSuccessCounter = Counter.builder("business.payment.result") .tag("status", "success") .register(meterRegistry); this.paymentFailCounter = Counter.builder("business.payment.result") .tag("status", "fail") .register(meterRegistry); } @PostMapping("/pay") public String pay(@RequestBody PayRequest request) { return paymentTimer.record(() -> { try { // 业务逻辑... String result = doActualPayment(request); paymentSuccessCounter.increment(); return result; } catch (Exception e) { paymentFailCounter.increment(); throw e; } }); } private String doActualPayment(PayRequest request) { // 实际支付逻辑 return "success"; } }这里有三个常用的 Micrometer 指标类型你必须掌握:
- Counter:只增不减的计数器。适合计次——请求量、错误数、成功数
- Gauge:瞬时值,可升可降。当前连接数、队列长度、缓存命中率
- Timer:记录耗时 + 调用次数。接口延迟、数据库查询耗时
// Gauge 示例:监控线程池队列长度 Gauge.builder("threadpool.queue.size", threadPoolExecutor, ThreadPoolExecutor::getQueueSize) .register(meterRegistry);4.3 关键配置:别让指标成为性能瓶颈
Actuator 默认配置不一定适合生产环境,下面这个配置我每个项目都会调:
management: metrics: export: prometheus: enabled: true step: 30s # 指标推送间隔,默认1分钟 distribution: percentiles-histogram: http.server.requests: true # 开启 HTTP 请求延迟直方图 slo: http.server.requests: 50ms, 100ms, 200ms, 500ms, 1s # 自定义延迟分桶 endpoints: web: exposure: include: health,metrics,prometheus几个踩过的坑:
percentiles-histogram会大幅度增加指标数据的基数(每个分桶值都产生一套数据),Prometheus 存储压力会上去。如果指标量太大,建议用slo指定有限分桶,而不是全开step设太短(如 5s),Prometheus 抓取频率跟不上会导致数据重复或丢失- 不要把 Actuator 端口直接暴露到公网,至少加一层 Spring Security 或独立管理端口
五、实战三:Prometheus + Grafana 构建监控大屏
Actuator 暴露指标只是第一步,真正让数据"活起来"的是 Prometheus + Grafana。
5.1 接入 Prometheus
<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>然后访问/actuator/prometheus,你会看到标准 Prometheus 格式的指标数据:
# HELP jvm_memory_used_bytes The number of used bytes # TYPE jvm_memory_used_bytes gauge jvm_memory_used_bytes{area="heap",id="G1 Eden Space",} 1.2345678E8 jvm_memory_used_bytes{area="heap",id="G1 Survivor Space",} 1.048576E7 ... # HELP business_payment_request_seconds_max # TYPE business_payment_request_seconds_max gauge business_payment_request_seconds_max{application="payment-service",} 0.5235.2 Prometheus 抓取配置
# prometheus.yml scrape_configs: - job_name: 'payment-service' metrics_path: '/actuator/prometheus' scrape_interval: 30s static_configs: - targets: ['localhost:8080'] labels: application: 'payment-service' env: 'production'5.3 Grafana 大屏
Grafana 里导入 JVM 监控模板(Dashboard ID: 4701 或者 12856),10分钟就能搭出这样的效果(用文字描述):
- 左上:服务实例数、运行时长、CPU使用率、堆内存使用率(数字面板)
- 中间:GC 暂停时间趋势图、GC 次数/频率
- 下排:HTTP QPS + P99延迟曲线、错误率趋势
- 右侧:自定义业务指标(支付量、成功率)
5.4 告警规则
光看不够,还要配告警。Prometheus 告警规则示例:
# alerts.yml groups: - name: spring-boot-alerts rules: - alert: HighHeapMemoryUsage expr: sum(jvm_memory_used_bytes{area="heap"}) / sum(jvm_memory_max_bytes{area="heap"}) > 0.85 for: 5m labels: severity: warning annotations: summary: "堆内存使用率超过85%" - alert: HighErrorRate expr: rate(http_server_requests_seconds_count{status=~"5.."}[5m]) / rate(http_server_requests_seconds_count[5m]) > 0.01 for: 3m labels: severity: critical annotations: summary: "HTTP 5xx 错误率超过1%"再配合 Alertmanager 把告警推到钉钉/企业微信/飞书,这样凌晨三点的电话就换成了一条消息推送。
六、实战四:自定义 Endpoint——暴露你想要的任何信息
Actuator 不仅提供了 20+ 个内置端点,还允许你自定义端点。比如你想暴露当前系统的业务配置快照:
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.stereotype.Component; import java.util.Map; @Component @Endpoint(id = "businessConfig") public class BusinessConfigEndpoint { @ReadOperation public Map<String, Object> getConfig() { return Map.of( "maxOrderAmount", 50000, "supportedPaymentMethods", new String[]{"alipay", "wechat", "unionpay"}, "maintenanceMode", false, "currentVersion", "3.2.1" ); } }别忘了在配置里暴露它:
management: endpoints: web: exposure: include: health,metrics,prometheus,businessConfig现在访问/actuator/businessConfig,直接拿到业务配置快照。这个能力在排查问题时特别有用——不用翻代码、不用查数据库,一个 HTTP 请求就知道当前系统以什么参数在运行。
@Endpoint还支持@WriteOperation和@DeleteOperation,可以实现运行时动态调整参数。比如你做了一个特性开关,线上出了问题需要紧急关闭某个功能,直接调端点就行,不用重启。
七、生产环境 Checklist
这套监控体系上线前,对照下面这个清单逐项检查:
1. 安全防护
- 生产环境不要用
show-details: always,改用when-authorized - Actuator 端点加 Spring Security 认证,或者配置独立管理端口
management.server.port: 8081 /heapdump和/threaddump生产环境单独鉴权,不要和/health混在一起
2. 性能考量
percentiles-histogram按需开启,不要为了"好看"全开——Prometheus 的时序数据量会暴涨- 自定义 HealthIndicator 的方法必须快速返回(< 1s),不要在里面做 HTTP 调用等阻塞操作。如果必须检查远程服务,用异步 + 缓存结果
- 检查
/actuator/metrics返回的指标列表,删除不需要的指标以减少内存占用
3. 运维配套
- Grafana 大屏至少包含:JVM 堆内存、GC 暂停、HTTP QPS/P99、5xx 错误率、自定义核心业务指标
- 告警规则至少覆盖:堆内存 > 85%、错误率 > 1%、实例宕机
- 编写运维 Runbook:每个告警对应的排查步骤(好比"堆内存高 →
jmap -histo查大对象 → 定位代码")
很多团队把监控当成"锦上添花"的东西,项目赶进度的时候最先砍掉的就是它。但我的经验是:你花在监控上的每一分钟,都会在凌晨三点加倍还给你。
Actuator 的接入成本极低——加一个依赖、写几行配置、挂一个 Grafana 模板,半天就能搞定。但它带来的价值是持续性的:你不再靠"用户投诉"来发现故障,不再靠"猜"来定位问题。你看得到系统的心跳、呼吸、每一次抖动。
这就是"可观测性"的本质:不是出了问题才知道,而是提前看到风险在酝酿。
金句:好的监控不会让你的系统不挂,但会让你在它挂之前就知道它快撑不住了。
下篇预告(Day 21):《Spring事件机制:观察者模式在企业级解耦中的应用》——你会发现,原来不用 MQ、不用 RPC,Spring 自己就藏着一个优雅的组件解耦利器。明天见。