刚上手 Spring Boot 监控那会儿,我犯过一个特别典型的错:线上接口偶发超时,打开/actuator/health一看,状态稳稳妥妥的UP,但实际请求已经在超时边缘排队。后来才发现,健康检查的结果跟业务真正能不能用,中间隔了不止一层逻辑。这篇文章我就把我折腾 Spring Boot 健康检查和监控体系的完整思路写出来,从 Actuator 内部机制、Prometheus 接入、Grafana 看板,到生产环境踩过的那些坑,再到怎么基于 Spring Boot 自建一个轻量监控中心,都一次讲透。
1. 从“进程活着”到“业务可用”:健康检查到底查了什么
1.1 三层健康语义:别再把“能连通端口”当成“健康”
很多项目对健康检查的理解停留在“服务进程还在”这个层面。进程在、端口通、能响应 HTTP 请求,就觉得服务是健康的。但生产环境里,这套判断往往会在最要命的时候给你错误的安全感。
我习惯把健康语义拆成三层:
- 第一层:进程存活。进程没崩,端口还能连上,这是最基础的一层。
- 第二层:依赖可用。数据库能连、Redis 能 PING、消息队列能收发、磁盘空间足够,这一层才是大多数“假 UP”翻车的重灾区。
- 第三层:业务可用。核心接口能正常返回、关键业务链路能跑通、调用外部服务的延迟在可接受范围内。
Spring Boot Actuator 默认给你的是第二层的一部分,但远不是全部。说得直白点,/actuator/health返回UP,只能说明“这个 Spring Boot 应用认为它依赖的基础组件看起来正常”,至于你的业务有没有被连接池耗尽、线程池打满、外部服务响应慢拖垮,那得靠你继续往下做。
1.2 Actuator 健康检查的完整工作链路
要玩明白健康检查,得先搞清楚/actuator/health这个端点背后是怎么运转的。
首先引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>接下来访问http://localhost:8080/actuator/health,默认返回:
{ "status": "UP" }别小看这短短几行,里面藏着一条完整链路:
- 请求到达
HealthEndpoint,它是 Spring Boot 暴露出来的一个 HTTP 端点。 HealthEndpoint从HealthContributorRegistry里拿到所有注册好的HealthContributor。- 每个
HealthContributor里最核心的是HealthIndicator,它只有一个health()方法,返回一个Health对象。 Health对象里有基本状态UP/DOWN,还有带上的详情信息details。- 最后通过
StatusAggregator把所有组件的状态聚合起来,形成整体状态。
Spring Boot 2.2 之后,HealthIndicator接口被纳入了HealthContributor体系。现在HealthIndicator本身就是一个存活检查的具体实现,而CompositeHealthContributor负责把一批检查项组织成树状结构,所以你能在健康检查结果里看到嵌套的组件状态。
1.3 内置 HealthIndicator 清单与状态合并规则
Spring Boot 会根据当前 classpath 上的依赖自动装配一堆健康检查器。下面是我觉得生产上最常见的几个:
| HealthIndicator | 检查内容 | 对应依赖或场景 |
|---|---|---|
DataSourceHealthIndicator | 数据库是否能执行验证查询 | 存在DataSource时自动启用 |
RedisHealthIndicator | Redis 是否响应 PING | spring-data-redis |
MongoHealthIndicator | MongoDB 是否可访问 | spring-data-mongodb |
RabbitHealthIndicator | RabbitMQ 连接与声明是否正常 | spring-rabbit |
DiskSpaceHealthIndicator | 磁盘剩余空间是否低于阈值 | 默认检查/路径,阈值 10MB |
ElasticsearchHealthIndicator | Elasticsearch 集群是否可通信 | spring-data-elasticsearch |
PingHealthIndicator | 返回固定的UP,不发任何外部请求 | 常被用于存活探针 |
整体状态的合并规则:默认情况下,只要有一个组件返回DOWN,整体就是DOWN;如果没有任何DOWN但有组件是OUT_OF_SERVICE,整体就是OUT_OF_SERVICE;UNKNOWN不会拖累整体到DOWN。这个优先级顺序可以通过配置调整:
management: endpoint: health: status: order: DOWN, OUT_OF_SERVICE, UNKNOWN, UP还有一个容易忽略的地方是 HTTP 状态码映射。默认情况下/actuator/health无论UP还是DOWN,HTTP 状态码基本都是 200,这对负载均衡器的探活不友好。建议配置:
management: endpoint: health: status: http-mapping: DOWN: 503 OUT_OF_SERVICE: 503这样服务不健康时返回 503,Nginx、K8s、云负载均衡的 HTTP 探活就能直接根据状态码做判断。
2. 把健康检查调到能上生产:配置、自定义与探活姿势
2.1 一次性讲清 exposure、show-details 和 status 三条关键配置
Actuator 的配置习惯跟 Spring Boot 版本强相关,这里我用 Spring Boot 2.x/3.x 的配置方式来说,1.x 的management.security.enabled之类方案早就过时了,不要再碰。
最常改的其实是下面这段:
management: endpoints: web: exposure: include: health,info,prometheus,metrics endpoint: health: show-details: when-authorized show-components: alwaysexposure.include:决定哪些端点能被 HTTP 访问,生产环境建议显式声明,别用*。show-details:控制是否显示每个 HealthIndicator 的详细检查信息,三个选项分别是never、when-authorized、always。本地调试可以用always,生产建议when-authorized,配合 Spring Security 控制谁能看详情,否则健康检查接口就变成了内网信息泄露口。show-components:控制是否显示每个组件的单独状态,即使不显示 details,也能看到db、diskSpace、redis各自是UP还是DOWN,这个在生产排查时特别有用。
2.2 自定义 HealthIndicator:让健康状态响应真实依赖
Spring Boot 内置的健康检查在遇到复杂业务依赖时是不够用的。比如你的订单服务依赖另一个支付网关,网关响应缓慢但进程还活着,Spring Boot 根本感知不到。这种场景就需要自己写HealthIndicator。
下面是我在一个订单服务里写过的真实例子:
@Component public class PaymentGatewayHealthIndicator implements HealthIndicator { private final RestTemplate restTemplate; public PaymentGatewayHealthIndicator(RestTemplateBuilder builder) { this.restTemplate = builder .setConnectTimeout(Duration.ofSeconds(2)) .setReadTimeout(Duration.ofSeconds(2)) .build(); } @Override public Health health() { try { ResponseEntity<String> response = restTemplate.getForEntity( "http://payment-gateway:8080/internal/ping", String.class ); if (response.getStatusCode().is2xxSuccessful()) { return Health.up() .withDetail("pingPath", "/internal/ping") .withDetail("statusCode", response.getStatusCode().value()) .build(); } return Health.down() .withDetail("pingPath", "/internal/ping") .withDetail("statusCode", response.getStatusCode().value()) .build(); } catch (Exception ex) { return Health.down() .withDetail("error", ex.getMessage()) .build(); } } }这个自定义检查器有两点值得注意:
第一,RestTemplate 的读取超时我限制在 2 秒。健康检查接口通常是负载均衡器、监控系统高频调用的,如果它本身卡在外部依赖上迟迟不返回,就会拖垮整个探活链路。宁可快速返回DOWN,也不要等几十秒才给结果。
第二,检查外部依赖时尽量用它专门提供的内网探针接口,而不是随便打一个业务接口。业务接口可能本身依赖数据库,数据库挂了它会返回 500,但你的支付网关其实是健康的,这就会造成误报。
如果 HikariCP 连接池已经快打满了,默认的DataSourceHealthIndicator只会执行SELECT 1,它依然能通过,但业务早就响应不过来了。针对这种情况,可以写一个连接池专属的健康检查:
@Component public class DataSourcePoolHealthIndicator implements HealthIndicator { private final DataSource dataSource; public DataSourcePoolHealthIndicator(DataSource dataSource) { this.dataSource = dataSource; } @Override public Health health() { if (dataSource instanceof HikariDataSource) { HikariDataSource hikari = (HikariDataSource) dataSource; int active = hikari.getHikariPoolMXBean().getActiveConnections(); int idle = hikari.getHikariPoolMXBean().getIdleConnections(); int max = hikari.getMaximumPoolSize(); if (idle == 0 && active >= max * 0.9) { return Health.down() .withDetail("reason", "connection pool nearly exhausted") .withDetail("active", active) .withDetail("max", max) .build(); } return Health.up() .withDetail("active", active) .withDetail("idle", idle) .withDetail("max", max) .build(); } return Health.unknown().build(); } }提示:这种“业务层健康检查”不能无限堆,每一层依赖都写一个,健康检查接口本身就会变得很重。我最常建议的粒度是:数据库、缓存、消息队列、关键外部系统各一个,其他外围系统放到监控指标里看,不要全塞到健康状态里。
2.3 健康检查分组:给负载均衡看的和给 K8s 看的要分开
生产环境里,不同调用方对健康检查的语义要求不同。一个负载均衡器希望知道“这个实例还能不能接流量”,K8s 的存活探针希望知道“这个进程是不是死锁了,该不该重启”,而就绪探针想知道“依赖是不是都就绪了,能不能开始接收请求”。如果你把所有场景都指向同一个/actuator/health,必然会出现语义冲突。
Spring Boot 2.3 之后引入了探针组支持,我最常用的配置是这样:
management: endpoint: health: probes: enabled: true group: readiness: include: db, diskSpace, redis, paymentGateway liveness: include: ping配置之后会自动暴露两个端点:
/actuator/health/liveness:只包含一个ping检查,本质上是“进程活着吗”。只要 JVM 没挂,它就返回UP,适合作为 K8s 的 livenessProbe。/actuator/health/readiness:包含所有真实依赖检查,任何一个依赖挂了就返回DOWN,适合作为 readinessProbe。
K8s 里对应配置:
livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 periodSeconds: 10这样设计的好处是:依赖数据库抖动时,readiness 探针把实例从 Service 摘掉,流量不再进这个 Pod;但 liveness 探针不会因为这个把 Pod 杀掉重启,避免“数据库挂了 → 服务重启 → 重启后依然连不上 → 反复重启”的雪崩。
升级到 K8s 探针之后,别再用同一份/actuator/health指给两边,这是我踩过最深的一个坑,后面专门讲。
3. 从数值到指标:Actuator 如何接入 Prometheus 监控
3.1 先搞清楚 Micrometer 在监控链路里的位置
健康检查只是告诉你“现在好不好”,监控要解决的是“为什么不好、趋势是什么、什么时候开始不好的”。所以只靠健康检查是不够的,要把指标数据源源不断地送出去。
Spring Boot 的指标出口统一靠 Micrometer,它是个指标门面库,类似日志里的 SLF4J。Micrometer 负责定义指标 API,然后由不同的 registry 实现把数据发送到对应后端。Prometheus 的 registry 实现就是micrometer-registry-prometheus。
引入依赖时不需要手动指定版本,Spring Boot 的依赖管理会统一版本:
<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>装好依赖、打开暴露配置后,/actuator/prometheus端点就会输出 Prometheus 格式的指标文本。这个端点才是 Prometheus 真正要抓的地址。
3.2 三步接入 Prometheus
第一步:配置暴露端点。
management: endpoints: web: exposure: include: health,info,metrics,prometheus如果你跟我一样给管理端点开了独立端口,Prometheus 抓取时要记得抓管理端口而不是业务端口:
management: server: port: 8081 endpoints: web: base-path: /manage exposure: include: health,info,metrics,prometheus这时端点地址变成http://host:8081/manage/prometheus。
第二步:确认端点输出内容。浏览器访问/actuator/prometheus,会看到类似这样的文本:
# HELP jvm_memory_used_bytes The amount of used memory # TYPE jvm_memory_used_bytes gauge jvm_memory_used_bytes{area="heap",id="PS Old Gen",} 2.04590432E8 jvm_memory_used_bytes{area="nonheap",id="Metaspace",} 1.02278912E8 http_server_requests_seconds_count{method="GET",status="200",uri="/api/users",} 120.0第三步:在 Prometheus 的prometheus.yml里加抓取任务。
scrape_configs: - job_name: 'spring-boot-app' metrics_path: '/actuator/prometheus' scrape_interval: 15s static_configs: - targets: ['192.168.1.20:8080'] labels: service: 'order-service' env: 'prod'scrape_interval我一般设置 15 秒,太密了对省内存不友好,太疏了到排查问题时数据点又不够看。
3.3 生产上最值得盯的几个指标与 PromQL
Prometheus 接入只是开始,真正要紧的是知道看哪些指标。下面这组是我在多个 Spring Boot 项目里沉淀下来的核心清单,建议每一条都放进监控面板:
| 指标名 | 类型 | 含义 |
|---|---|---|
http_server_requests_seconds_count | Counter | HTTP 请求总数,按 method / status / uri 拆分 |
http_server_requests_seconds_sum | Counter | 请求总耗时,用于计算平均响应时间 |
http_server_requests_seconds_bucket | Histogram | 请求耗时桶,用于算 P95 / P99 |
jvm_memory_used_bytes | Gauge | JVM 内存使用量,按堆内/堆外拆分 |
jvm_gc_pause_seconds | Timer | GC 暂停时间 |
system_cpu_usage | Gauge | 整机 CPU 使用率 |
process_cpu_usage | Gauge | 当前进程 CPU 使用率 |
hikaricp_connections_active | Gauge | 活跃数据库连接数 |
hikaricp_connections_pending | Gauge | 等待获取数据库连接的线程数 |
logback_events_total | Counter | 日志事件数,按 level 拆分 |
几个常用 PromQL 查询,直接抄到 Grafana 或者 Prometheus 的 Graph 页面就能用。
请求 QPS:
sum(rate(http_server_requests_seconds_count[1m])) by (service)P99 响应时间:
histogram_quantile( 0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri) )5xx 错误率:
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (service) / sum(rate(http_server_requests_seconds_count[5m])) by (service)JVM 堆内存使用率:
sum(jvm_memory_used_bytes{area="heap"}) by (service) / sum(jvm_memory_max_bytes{area="heap"}) by (service)注意:
jvm_memory_max_bytes在某些资源受限容器里可能返回 0 或者很奇怪的值,这种除法会出现+Inf。遇到这种情况就先看原始值,别急着上告警。
4. 可视化与告警:让数字变成能叫醒人的提醒
4.1 Grafana 数据源与面板搭建
装了 Prometheus 之后,下一步是接 Grafana。数据源配置很简单,在 Grafana 里加一个 Prometheus 类型数据源,URL 填 Prometheus 地址即可。
面板我建议分两张思路来做:一张全局“服务总览”看所有服务状态,一张针对单个服务的“深入排查”看 JVM / 请求 / 数据库连接。社区里搜索Spring Boot或者JVM Micrometer能直接导入现成的仪表盘,但我不建议直接拿来就用。原因很简单:社区面板仪表盘信息量太大,变量配置也很复杂,生产环境真出问题时反而不容易一眼找到根因。
我自己的做法是自建几张极简面板,每张只放 6 到 8 个关键图表:
- 当前实例存活状态
- 每分钟请求数
- P95 和 P99 响应时间
- 5xx 错误数
- JVM 堆内存使用率
- GC 次数和 GC 暂停时间
- 活跃数据库连接数
- 等待连接线程数
面板不求大而全,求的是“出问题时 10 秒内能定位到大概方向”。
4.2 我常用的核心监控看板逻辑
每个服务我建议至少保留三类看板:请求看板、JVM 看板、依赖看板。
请求看板重点关注趋势,尤其是发布前后的对比。我经常在发布新版本后盯着http_server_requests_seconds_count的速率变化,如果 QPS 没掉但错误率突然从 0.1% 涨到 2%,基本就是新代码有问题。
JVM 看板用来区分“服务慢是因为资源不够还是因为代码有问题”。当请求响应时间上涨时,先看jvm_memory_used_bytes是不是接近max,再看 GC 暂停时间是不是飙升。如果 GC 频繁而且暂停时间很长,大概率是内存泄漏或者堆开太小,这时候加机器解决不了问题。
依赖看板专门看连接池、外部调用耗时这类指标。我最常用的是活跃数据库连接数和等待连接线程数这两个图,它们能提前暴露连接池不足的问题。等hikaricp_connections_pending开始上涨,说明已经有人在排队等连接了,这时候健康检查多半还显示UP,但业务已经肉眼可见地卡顿。
4.3 一套可以落地改写的告警规则
监控面板是给人看的,告警是让机器盯着的。告警规则的设计原则是“少而准”,不要一上来就加二十条规则,否则告警疲劳之后真正重要的告警反而被忽略了。
我目前在用的核心告警规则就五条:
服务离线:
up{job="spring-boot-app"} == 0持续 1 分钟就告警,这个最有价值。
请求错误率突增:
( sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (service) / sum(rate(http_server_requests_seconds_count[5m])) by (service) ) > 0.05持续 5 分钟触发,一般错误率持续高于 5% 已经说明问题比较严重了。
JVM 堆内存持续高位:
( sum(jvm_memory_used_bytes{area="heap"}) by (service) / sum(jvm_memory_max_bytes{area="heap"}) by (service) ) > 0.9持续 10 分钟触发,短时间冲高可能是正常业务高峰,持续高位才需要处理。
数据库连接池打满:
hikaricp_connections_pending > 0只要持续 5 分钟有 pending,说明连接池不够或数据库处理不过来,这条几乎每天都能派上用场。
GC 暂停时间过长:
max(jvm_gc_pause_seconds) by (service) > 0.5持续 5 分钟触发,0.5 秒以上的 GC 暂停对高并发服务已经非常严重了。
告警接收端走 Alertmanager,推给钉钉或企业微信机器人,这套流程网上有现成配置。需要特别提醒的是:告警消息里一定把service、instance、expr这些标签带上,否则半夜收到一条“错误率过高”的告警,你还得自己打开 Grafana 查是哪个服务。
5. 生产环境翻车记录:健康检查与监控最容易踩的五个坑
5.1 健康检查显示 UP,连接池却在排队
这是我最开始提到的那个场景。当时服务压测到 800 并发,QPS 上到一半突然整体变慢,接口响应时间从 50ms 飙到 3 秒。我打开/actuator/health,结果还是UP。
根因:默认的数据库健康检查只是简单执行一条验证 SQL,只要连接能拿得到它就认为数据库健康。连接池打满时,验证 SQL 本身也要排队,但 Spring Boot 的 HealthIndicator 有一个超时时间,在超时之前如果拿到连接执行成功,依然返回UP;即使拿不到连接,两个线程都在排队,健康检查超时之后才会标记DOWN,这个时间窗口足够让监控误判了。
解决方式就是前面写的DataSourcePoolHealthIndicator,把连接池活跃度纳入健康判断,同时把hikaricp_connections_pending接进 Prometheus 做趋势告警。从这以后我再没被“假 UP”骗过。
5.2 磁盘空间检查把服务直接判了死刑
DiskSpaceHealthIndicator会检查文件系统剩余空间,默认路径是/,默认阈值是 10MB。看起来合理,但碰上日志文件膨胀特别快的服务就出问题了。
有次一个服务的日志目录挂在独立数据盘上,业务数据导致主分区快满了,健康检查直接返回DOWN。问题在于,这个服务的正常业务核心根本不依赖主分区的空间,但磁盘空间一紧张,K8s readiness 探针把 Pod 摘掉了,服务瞬间处于不接流量的状态,反而把一个小问题放成了大故障。
解决方式:修改磁盘检查路径和阈值,让检查精准匹配实际影响:
management: health: diskspace: path: /data/app threshold: 2GB我的建议是,磁盘检查一定要指向应用真正写入数据的目录,别用默认的/。
5.3 独立管理端口和防火墙的相爱相杀
给 management 端口单独开 8081 是个好习惯,但很多人改了端口之后忘了改监控系统抓取地址。
我之前帮一个团队排查,Prometheus 一直抓不到指标,检查配置半天,最后发现 Prometheus 的 target 还是写的业务端口 8080,但管理端点已经挪到 8081 了,8080 端口上根本不存在/actuator/prometheus。
独立管理端口时需要注意三件事:
- Prometheus 抓取地址要改成
http://ip:8081/manage/prometheus。 - 防火墙、安全组要放行 8081,否则外网 Prometheus 根本连不进去。
- Spring Security 配置的路径放行规则要匹配新端口,否则要么 401,要么全部被拦截。
management: server: port: 8081 base-path: /manage这套配置下所有 Actuator 端点都挪到了 8081 端口的/manage下,不仅 Prometheus 要改,Nginx 探活、K8s 探针的地址也得跟着改。
5.4 端点暴露过度,监控接口变成了数据泄密口
include: '*'是最省事的写法,也是最危险的操作。它会把所有 Actuator 端点暴露出去,其中env端点会显示环境变量和配置项,heapdump端点能直接下载 JVM 堆转储文件。
堆转储文件里藏着什么?所有 JVM 内存里的字符串内容,包括数据库密码、Token、密钥,全都在这份文件里。曾经有团队在测试环境开了*,一堆关键配置直接挂在内网可访问的接口上,还认为自己内网足够安全。我的建议很明确:生产环境只暴露health,info,metrics,prometheus,其他一律不暴露。
如果确实需要临时排查问题,比如要看beans或者conditions,用运维通道临时打开,排查完立刻关掉,别留着过夜。
5.5 liveness、readiness 与健康检查的语义混淆
不少团队上了 K8s 之后,探针配置就是简单抄一下:
livenessProbe: httpGet: path: /actuator/healthReadiness 探针没配,或者两个探针都指向同一个/actuator/health。这样会出现一个非常尴尬的局面:数据库短暂不可用,/actuator/health返回DOWN,K8s 认为 Pod 不健康,直接杀掉重启。重启之后数据库恢复还需要时间,Pod 又处于 CrashLoopBackOff,整个服务的可用性被探针逻辑搞崩了。
正确做法就是前面 2.3 节说的,用健康检查分组:
management: endpoint: health: probes: enabled: true然后 liveness 探针指向/actuator/health/liveness,readiness 探针指向/actuator/health/readiness。依赖短暂故障时 Pod 只会被摘流,不会被反复重启。
Spring Boot 高版本里还有一个容易踩的点:旧项目从 2.2 升到 2.7 或 3.x,management.endpoints.web.exposure.include的默认行为变了,老配置里写management.endpoints.web.exposure.include: '*'在新版里可能默认不生效或者连带暴露一堆端点,升级完一定要回归验证/actuator下列出来的端点列表。
6. 更进一步:用 Spring Boot 自建轻量监控中心的思路
6.1 巡检任务的设计要点
聊完应用自身的健康检查和监控,再说一个更广的场景:当你要监控的不只是自己的 Spring Boot 服务,而是一批异构系统时,用 Spring Boot 搭一个轻量监控中心是特别常见的做法。
这个监控中心本质就是一个定时巡检器,核心代码如下:
@Component public class HealthPatrolTask { private final RestTemplate restTemplate; private final PatrolRecordRepository repository; @Scheduled(fixedDelay = 30000) public void patrol() { List<String> targets = loadTargetUrls(); for (String url : targets) { long start = System.currentTimeMillis(); try { ResponseEntity<JsonNode> response = restTemplate.getForEntity(url, JsonNode.class); long cost = System.currentTimeMillis() - start; JsonNode body = response.getBody(); String status = body == null ? "UNKNOWN" : body.path("status").asText("UNKNOWN"); saveRecord(url, response.getStatusCode().value(), status, cost); } catch (Exception e) { saveRecord(url, 0, "DOWN", 0); } } } }设计巡检任务时,我觉得最重要的一点是:巡检逻辑不能反过来影响业务。@Scheduled任务的线程池要单独配置,RestTemplate 的连接和读取超时都要设置,不能让一个不可达的目标把整个巡检线程拖死。
6.2 检查不同目标系统时用的不同姿势
监控中心面对的“检查目标”可能完全不是 Spring Boot 应用,比如热搜里常出现的冷库 PLC 设备、FTP 服务器、第三方平台数据。不同目标要用不同检查姿势:
| 目标类型 | 检查方式 | 判定标准 |
|---|---|---|
| Spring Boot 服务 | GET/actuator/health | 解析 JSON 里的status字段 |
| 普通 HTTP 接口 | GET 业务接口 | HTTP 状态码 2xx 且返回内容包含关键字段 |
| MySQL / PostgreSQL | 执行SELECT 1 | 查询正常返回 |
| Redis | PING | 返回PONG |
| FTP 服务器 | 登录并执行LIST | 登录成功且能列出目录 |
| PLC / 设备 | Modbus TCP 读寄存器 / PING | 能读到指定寄存器值 |
| 第三方平台 | 定时拉取接口,检查数据新鲜度 | 返回数据的时间戳在合理范围内 |
这里特别提一下“数据新鲜度”这个思路。对于像“抖音新作品监控助手”这类外部平台的监控,你不能只关心接口通不通,更重要的是数据有没有更新。比如每分钟拉一次某个账号的作品列表,发现最新作品时间戳停留在一小时前,大概率是采集逻辑出问题了,这种“业务层面的健康检查”往往比单纯的端口探活更有价值。
6.3 监控中心自身的可用性问题
最后想提醒大家一个被忽略的点:监控中心自己是单点。
如果监控中心挂在同一个集群里,部署它的节点挂了,整个监控链路就断了。我踩过一次这个坑:监控中心所在 Pod 被调度到一台故障机器上,结果整个巡检任务停摆,下游服务真的出问题时监控告警一条都没发出来。
轻量监控中心至少要做好三件事:
- 巡检任务加锁,避免多实例部署时重复执行。最简单的方式是用数据库行锁或者 ShedLock。
- 告警通道要有备用,比如主通道钉钉机器人,备通道短信或者邮件,防止单一网关平台异常导致告警发不出去。
- 监控中心自己的
/actuator/health也要接入外部监控,最好和应用巡检链路分开,不然只能靠人肉发现监控中心挂了。
这套自建方案虽然比不上 Zabbix、Prometheus + Alertmanager 那一整套专业方案,但在中小团队里胜在轻量、可控、和 Spring Boot 技术栈完全一致。我实际用过很长一段时间,稳定性和定制性都够用。
个人体会是,健康检查和监控从来不是“加个依赖、调个接口”就完事的事情。把 Actuator 的机制吃透,把健康检查的粒度调到和业务真实可用性一致,把指标监控的链路从端点打通到告警消息,每一步都需要结合自己项目的实际情况做取舍。最后再补一句实战建议:刚接触这个方向的朋友,先别急着把全套监控矩阵铺开,把/actuator/health的生命周期搞清楚,再把 Prometheus 抓通,最后慢慢加告警规则,这条路走下来最稳。