Actuator 与健康检查
这一课你将能做到
- 启用 Actuator 并暴露常用端点,读懂 /actuator/health 的聚合检查结果
- 自定义 HealthIndicator,把「自己的关键依赖」纳入健康判定
- 建立生产环境暴露端点的安全边界(白名单 + 独立端口思路)
为什么要学
应用在服务器上跑着,你怎么知道它「还活着」?数据库连不上了、磁盘满了、线程池打爆了——这些问题用户先发现还是你先发现,决定了你是「值班工程师」还是「救火队长」。
Actuator 是 Spring Boot 内置的运维窗口:一组开箱即用的端点,汇报健康状态、运行指标、环境配置。Kubernetes 的存活探针、Nginx 的健康检查、告警系统,全部对接这些端点。生产事故里最常见的第一动作就是 curl /actuator/health——这一课让你具备这个动作背后的全部知识。
先建立图像
┌──── /actuator/health 应用活着吗?依赖都通吗?
运维/监控/K8s ────→ ├──── /actuator/metrics 内存、线程、QPS 多少?
├──── /actuator/env 当前生效的配置是什么?
└──── /actuator/loggers 日志级别能不能运行时改?
↑
Actuator = 应用的「体检报告 + 仪表盘」
默认只开两个端点:health(健康)和 info(应用信息)。其余端点默认关闭是有意设计——env、heapdump 这类端点能泄露配置甚至内存快照,历史上多次成为安全事件主角。暴露原则先记住一句话:开发全开无妨,生产白名单 + 防护。
| 常用端点 | 作用 | 生产建议 |
|---|
| /health | 聚合健康状态(DB、Redis、磁盘…) | 暴露,供探针 |
| /info | 应用版本、构建信息 | 暴露 |
| /metrics | 各类运行指标(只列名,不吐值) | 仅内网 |
| /env | 环境变量与配置(含密码脱敏) | 禁止外网 |
| /loggers | 查询/修改日志级别 | 仅内网,紧急调级神器 |
| /threaddump、/heapdump | 线程/内存快照 | 禁止外网,排查卡死用 |
跟着做
步骤 1:引入依赖,体验默认行为
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
重启后:
curl http://localhost:8080/actuator/health
# {"status":"UP"}
UP 表示应用与所有已注册的健康检查全部通过。再访问 /actuator/metrics 会 404——没暴露,这就是默认的保守姿态。
步骤 2:暴露端点与查看健康细节
management:
endpoints:
web:
exposure:
include: health,info,metrics,loggers # 白名单式暴露,勿用 include: '*'
endpoint:
health:
show-details: always # 显示各组件细节;生产常配 show-components + 角色限制
show-components: always
info:
env:
enabled: true
{
"status": "UP",
"components": {
"db": { "status": "UP", "details": { "database": "H2", "validationQuery": "isValid()" } },
"diskSpace": { "status": "UP", "details": { "total": 512110190592, "free": 214748364800 } },
"ping": { "status": "UP" }
}
}
自动聚合机制:类路径上有 DataSource,db 检查自动加入;有 Redis 客户端,redis 检查自动加入——又是第 2 课自动配置的条件装配套路。任一组件 DOWN,整体 status 变 DOWN,探针立刻能发现。
⚠️ 细节安全提示:show-details: always 会暴露库名等内部信息,生产更常见做法是 when-authorized(需认证才显示细节)。
步骤 3:自定义 HealthIndicator
场景:你的服务依赖一个第三方「文件转码服务」,它挂了应用整体应报 DOWN:
package com.example.taskflow.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import java.net.HttpURLConnection;
import java.net.URI;
@Component("transcode") // Bean 名去掉 HealthIndicator 后缀 = 端点里的组件名
public class TranscodeHealthIndicator implements HealthIndicator {
@Override
public Health health() {
try {
HttpURLConnection conn = (HttpURLConnection)
URI.create("http://localhost:9999/ping").toURL().openConnection();
conn.setConnectTimeout(500);
conn.setReadTimeout(500);
if (conn.getResponseCode() == 200) {
return Health.up().withDetail("provider", "transcode-v1").build();
}
return Health.down().withDetail("httpCode", conn.getResponseCode()).build();
} catch (Exception e) {
// 依赖不可达:DOWN + 原因,报警系统靠 status 聚合发现它
return Health.down(e).build();
}
}
}
访问 /actuator/health,components 里多出 "transcode"。把这个依赖停掉再访问:
{
"status": "DOWN",
"components": {
"db": { "status": "UP" },
"transcode": { "status": "DOWN" }
}
}
一个非关键依赖把整个应用标成 DOWN 是否合理? 不一定——它不该导致 K8s 重启你的 Pod。进阶做法:用 StatusAggregator 自定义聚合策略,或关键存活性检查(liveness)与非关键就绪检查(readiness)分开(Boot 3.1+ 的探针端点 /actuator/health/liveness、/readiness 已内置这套概念)。
步骤 4:用 metrics 观察运行状态
curl http://localhost:8080/actuator/metrics # 指标名列表
curl http://localhost:8080/actuator/metrics/jvm.memory.used
curl http://localhost:8080/actuator/metrics/http.server.requests
{
"name": "http.server.requests",
"measurements": [
{ "statistic": "COUNT", "value": 42.0 },
{ "statistic": "TOTAL_TIME", "value": 3.86 },
{ "statistic": "MAX", "value": 0.21 }
],
"availableTags": [
{ "tag": "uri", "values": ["/api/tasks", "/api/auth/login"] },
{ "tag": "status", "values": ["200", "400", "401"] }
]
}
COUNT/TOTAL_TIME 一除就是平均耗时,按 tag 下钻可定位慢接口。底层是 Micrometer(指标门面):同样这些指标换个导出器就能进 Prometheus + Grafana——「微服务监控三件套」的地基就是本课内容。自定义业务指标一行代码:
private final Counter taskCreateCounter;
public TaskService(MeterRegistry registry, TaskMapper taskMapper) {
this.taskCreateCounter = registry.counter("taskflow.task.created");
this.taskMapper = taskMapper;
}
// 业务方法里:taskCreateCounter.increment();
步骤 5:loggers 端点——不重启改日志级别
线上排查问题需要把某包临时调成 DEBUG,不想重启(会中断服务):
# 查当前级别
curl http://localhost:8080/actuator/loggers/com.example.taskflow
# {"configuredLevel":"INFO","effectiveLevel":"INFO"}
# 运行时改为 DEBUG
curl -X POST http://localhost:8080/actuator/loggers/com.example.taskflow \
-H "Content-Type: application/json" -d '{"configuredLevel":"DEBUG"}'
# 排查完改回来
curl -X POST http://localhost:8080/actuator/loggers/com.example.taskflow \
-H "Content-Type: application/json" -d '{"configuredLevel":"INFO"}'
这是运维高频操作:日志级别运行时可调,且改回只需一个请求。
步骤 6:生产暴露的安全边界
management:
server:
port: 9090 # 管理端口与业务端口分离,防火墙只对内网开放 9090
endpoints:
web:
exposure:
include: health,info # 生产白名单:最少暴露
四层防线按序落:①最少暴露(只开 health/info)→ ②端口分离(9090 不对公网)→ ③网关/防火墙限制来源 IP → ④如需公网可达则挂认证(Security 放行 /actuator/health 其余拦截)。经典反面教材:env 端点直接暴露公网 + show-details 全开 = 配置(即使脱敏)与内部结构对外可见。检查本站生产环境的 actuator 配置,你会看到同样的白名单思路。
讲清楚要点
- 健康检查的价值在「自动化发现」:人盯日志是抢救,探针轮询 /health 是预防。聚合语义很简单——任一组件 DOWN 则整体 DOWN,监控体系只看这一个字段。
- 端点暴露是安全决策不是技术决策:include 白名单逐个评估,env/heapdump/threaddump 默认不出内网。历史上大量数据泄露事故来自「actuator 全开 + 无认证」。
- Micrometer 是指标的事实标准:Actuator 的 metrics 只是 Micrometer 的一层出口;写业务指标用
MeterRegistry,未来无论接 Prometheus 还是 CloudWatch 都不用改代码。
- liveness 与 readiness 语义分离(K8s 必备认知):liveness 回答「进程死了要不要重启」(只查进程自身),readiness 回答「能不能接流量」(可以包含依赖)。把非关键依赖混进 liveness 会造成「依赖抖动 → 无限重启」的生产事故。
- info 端点适合放版本与构建元数据:配置
spring-boot-maven-plugin 的 build-info 目标后,/actuator/info 返回版本、构建时间——「线上跑的到底是哪个版本」从此一查便知。
你可能会懵的地方
-
为什么默认只开 health 和 info?
其余端点会暴露配置、线程栈、内存转储,属于敏感信息。Actuator 的默认哲学是「安全优先」,按需白名单暴露。
-
/health 显示 UP 就代表业务正常吗?
只代表「进程活着、注册过的依赖可达」。业务异常(如某个接口全 500)要看 metrics 的错误率与日志。健康检查是「心电图」,不是「全身 CT」。
-
DOWN 状态会导致什么连锁反应?
取决于谁在读:K8s liveness 探针会重启 Pod,负载均衡会摘除节点,告警系统会通知人。因此「哪些依赖进 health、进哪个探针」必须谨慎设计——上一条的三件套意义正在于此。
-
生产上想看 env 但不能暴露端点怎么办?
通过运维通道:跳板机内网 curl、或日志里打关键配置摘要。原则不变——敏感配置的可见性要收敛到有权限的人。
-
Actuator 和 Prometheus/Grafana 什么关系?
Actuator 提供指标数据(Micrometer 格式),Prometheus 定时抓取(/actuator/prometheus 需引 micrometer-registry-prometheus 依赖)存储,Grafana 查询展示与告警。三层分工:产生数据 → 收集存储 → 可视化告警。
错法对照
| 错法 | 问题 | 正解 |
|---|
| include: '*' 暴露所有端点到公网 | env/heapdump 泄露配置与内存 | 生产白名单 + 内网端口 |
| health 的 show-details: always 且无认证 | 库名、组件细节对外可见 | when-authorized 或生产收敛 |
| 非关键依赖纳入 liveness 检查 | 依赖抖动导致 Pod 无限重启 | 关键依赖进 liveness,其余进 readiness |
| 自定义 HealthIndicator 里做慢查询 | /health 被拖慢,探针超时误判 DOWN | 缓存结果或轻量探测 |
| 想接监控就自己写 /metrics 接口 | 重复造轮子且格式不统一 | Micrometer + 官方 registry 依赖 |
卡点急救
- /actuator/health 返回 404:actuator 依赖没引入;或 context-path/管理端口配置导致路径变了(
management.endpoints.web.base-path 默认 /actuator);或 Security 拦截了——把 health 加入 permitAll。
- 整体 status 是 DOWN 但不知道谁挂了:details 没显示是因为
show-details 默认 never,开发环境配 always 定位;生产用 when-authorized + 认证查看。
- 加了 @Component 的自定义 HealthIndicator 没出现:Bean 名以 HealthIndicator 结尾才被自动发现(
transcodeHealthIndicator),或直接实现接口时确认在扫描包内。
- health 响应很慢拖垮探针:检查自定义 Indicator 的超时设置与数据库连接池余量;对慢依赖改用带缓存的异步探测。
面试常问
-
Actuator 的作用?说说你了解的端点。
生产监控与运维端点集合:health(聚合健康)、info(构建元数据)、metrics(Micrometer 指标)、env(配置)、loggers(运行时调日志级别)、threaddump/heapdump(故障排查)。强调「默认最小暴露 + 白名单」的安全实践。
-
健康检查机制是怎样的?如何自定义?
HealthIndicator 接口聚合:内置组件(DB/Redis/磁盘)由自动配置按类路径条件注册,任一 DOWN 则整体 DOWN;自定义实现 HealthIndicator 返回 Health.up()/down() 并附细节。K8s 场景区分 liveness/readiness 探针端点。
-
如何保障 Actuator 端点的安全?
最小暴露白名单、管理端口分离且防火墙限内网、接入 Security 认证、show-details 按角色授权;绝不允许 env/heapdump 无认证暴露公网。
-
你们怎么搭建监控体系?
Micrometer 埋点(框架自动 + 业务自定义 Counter/Timer)→ Prometheus 抓取 /actuator/prometheus → Grafana 看板与告警规则;日志侧 filebeat/Loki 聚合,链路侧 SkyWalking/Zipkin——指标、日志、链路三支柱。
自测三问
- 默认暴露哪两个端点?为什么其他端点默认关闭?
- 写一个自定义 HealthIndicator 需要实现什么接口?返回什么对象?Bean 命名有什么讲究?
- liveness 和 readiness 探针的语义区别是什么?把 Redis 检查放进 liveness 有什么风险?
小结一句话
Actuator 是应用的运维仪表盘:health 聚合依赖状态、metrics 经 Micrometer 通向监控体系——暴露多少、谁能看,是和「怎么用」同等重要的安全决策。
延伸