一个服务跑在生产上,突然开始超时、内存飙升,你翻遍日志也没找到凶手,只能一边重启一边祈祷。这种场景我相信很多人都经历过。后来我给服务加上了 Actuator,五分钟内就锁定了问题——是某个连接池配置不合理,空闲连接回收不及时,导致资源被占满。那次之后,我几乎所有 Spring Boot 项目都默认带上 spring-boot-starter-actuator,它就像是给应用装上了心电图机,一切运行状态一目了然。
Actuator 是 Spring Boot 内置的监控与管理端点模块,不需要额外引入中间件,只要在依赖里加上一个 starter,应用就能立刻暴露出一批 HTTP 端点,让你看到当前应用的健康状态、运行时指标、环境配置、线程快照等信息。它能解决的核心问题是:业务代码没有感知能力,但运行环境需要可观测性。适合正在做 Spring Boot 应用开发的后端工程师、运维人员,以及负责线上环境保障和性能排查的同事。
- 把 Actuator 概念先讲透:端点到底在管理什么
1.1 一次请求背后的监控闭环
Actuator 的本质是“一组可启用的 HTTP 或 JMX 端点”。当一个请求打到/actuator/health时,Spring Boot 内部会遍历所有注册的 HealthIndicator,收集每个健康指标的当前值,经过聚合逻辑后返回一段 JSON。这个过程不需要你写任何业务代码,依赖项引入后就能跑。
这些端点大体分两类。一类是只读型的,比如 health、info、metrics、env、beans、mappings,它们负责把应用内部运行数据暴露出来,日常排查和告警基本都在用这类;另一类是操作型的,比如 shutdown、loggers、restart,它们能改变应用的运行时状态。操作型端点一旦误调用,影响会比较直接,所以生产环境默认不允许暴露。
用一个生活化类比来理解:Actuator 就像汽车的仪表盘。health 是发动机故障灯,metrics 是速度表和油量表,env 是发动机舱内部的线束走向,loggers 是你能实时调整的仪表亮度旋钮。仪表盘本身不驱动力,但它让司机知道车正在发生什么。
1.2 两个容易混的配置域
Actuator 的配置前缀看起来很像,实际上分两个层级:
management.endpoints.web.exposure.include:控制哪些端点通过 HTTP 暴露,这是全局开关。management.endpoint.health.show-details:控制单个端点的行为细节,这是局部配置。
很多人在配置时会把这两层混在一起,导致点开了某个端点但细节不返回,或者以为配了单个端点就能生效,结果访问时 404。我一开始也跳过这个坑,后来总结了一个记忆方式:endpoints是复数,它管“哪些端点能被外面访问”;endpoint是单数,它管“某个端点内部怎么表现”。
再看一个常见误配置:
management: endpoints: web: exposure: include: "*" endpoint: health: show-details: always这段配置的意思是:打开所有 Web 端点,并且 health 端点始终展示详细检查项。如果你把show-details写到了endpoints下面,它不会生效,因为 health 内部的细节开关属于单端点配置域。
另外include: "*"看起来很省事,但生产环境我强烈不建议这么写。它会把 env、heapdump、loggers 这些端点全部暴露出去,相当于把应用的家底全亮给任何能访问到这个地址的人。后面我会专门讲安全策略,这里先记住一条原则:白名单永远比黑名单难出错。
- 从零到一:最快接入 Actuator 并完成四个必开端点
2.1 加依赖和默认行为探路
接入方式非常直接。如果是 Maven 项目,在pom.xml里加上:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>如果是 Gradle 项目,在build.gradle里对应加上:
implementation 'org.springframework.boot:spring-boot-starter-actuator'启动服务后,访问http://localhost:8080/actuator,你会看到一个 JSON 数组,里面列出了当前服务实际暴露的所有端点。Spring Boot 的默认策略是只暴露 health 和 info 两个端点,其他端点虽然注册在容器里,但不会出现在 HTTP 响应中。这个默认行为很大程度上是为了安全考虑——开箱即用不会把敏感信息直接扔到公网。
我第一次接入时很疑惑:为什么只有 health?后来想明白了,这是设计者的取舍:让一个全新项目先处于“可用但最小暴露”的状态,你要什么端点就自己打开,而不是装完就裸奔。这个默认策略值得尊重,生产上也别一股脑全开。
2.2 四个必开端点的读法
实际项目中,我建议第一步先打开这四个端点:
management: endpoints: web: exposure: include: "health,info,metrics,env" endpoint: health: show-details: always逐个说它们的用途。
/actuator/health是最常用的。访问它,你得到的是一个状态汇总:
{ "status": "UP" }加上show-details: always后,会显示磁盘、数据库等具体检查项。这个端点最适合配置外部探活,因为它的语义简单明确:应用能接受流量,就返回 UP,否则返回 DOWN。
/actuator/info展示应用基本信息。默认情况下它几乎是空的,因为 Spring Boot 不会自动把项目名和版本塞进去。一般需要配合配置文件和 InfoContributor 实现类补充内容,后面第三章会说怎么喂数据。
/actuator/metrics是性能排查的抓手。访问它会返回当前系统支持的指标名列表,比如 jvm.memory.used、jvm.threads.live、http.server.requests 等。想看具体指标,继续往下访问:
curl http://localhost:8080/actuator/metrics/jvm.memory.used它会返回当前堆和非堆区的内存占用情况。这个端点在定位内存泄漏、线程抖动时价值很大。
/actuator/env会暴露应用的所有环境配置属性,包括数据库密码、密钥、外部服务地址。它的价值是排查“为什么这个配置没生效”“到底加载了哪个配置源”,但风险也最大。如果在公网暴露了它,几乎等于把数据库账密直接交出去。
2.3 用独立管理端口把监控和业务隔离
既然提到了敏感端点,这里就要引入一个关键设计:独立管理端口。
management: server: port: 8081 endpoints: web: base-path: /actuator exposure: include: "health,info,metrics,env"配置之后,应用的主业务端口 8080 继续提供接口,而管理端点全部迁移到 8081 端口。这样做有两个直接好处。
一是风险隔离。业务端口是面对外部用户的入口,攻击者、爬虫、误操作都可能触达;管理端口只在内网开放,就能把 Actuator 的暴露面控制在一个可控范围内。
二是日志和监控数据清晰。业务接口的访问日志与监控端点的探测请求混在一起,时间一长很难分辨;分端口之后,管理流量集中在一个端口,抓包、限流、白名单都更容易操作。
需要提醒的是,设置了独立管理端口后,原来的/actuator/health地址就不对了,而应该访问http://localhost:8081/actuator/health。这个变化在对接容器探活、负载均衡检测时很容易被忽略。我见过不止一次线上事故,探针还打着业务端口,但 Actuator 已经挪到管理端口,健康检查一直失败。
- 进阶实操:自定义检查与扩展端点
3.1 自定义 HealthIndicator,让健康检查真正代表业务
默认的健康检查只覆盖框架层面的组件,比如磁盘空间、数据源状态。但很多业务依赖的中间件不一定会被自动纳入健康体系。比如你用了某个消息队列客户端,或者对接了一个远程 RPC 服务,这些依赖不会自动出现在 health 的检查列表里。这时候就需要自定义 HealthIndicator。
假设你要检测一个短信服务是否可用,可以这样实现:
@Component public class SmsServiceHealthIndicator implements HealthIndicator { @Override public Health health() { boolean smsServiceAlive = checkSmsService(); if (smsServiceAlive) { return Health.up() .withDetail("smsService", "available") .build(); } return Health.down() .withDetail("smsService", "unreachable") .build(); } private boolean checkSmsService() { // 这里执行实际的连通性探测 return true; } }Spring Boot 在聚合 health 端点时,会自动发现容器里所有 HealthIndicator 类型的 Bean,把它们的状态聚合成一个整体结果。业务下游挂了,health 立刻体现,而不是等用户侧反馈。
这里有个值得注意的细节:Health.down()会让整体状态立即变为 DOWN,从而影响外部探活的判定。如果你的业务逻辑允许某个下游故障但主流程还能运行,更好的做法是返回Health.status(new Status("UNKNOWN"))或者使用自定义状态码,避免一个非核心依赖挂了就导致整个应用被踢出负载均衡。
3.2 用配置和代码给 info 端点喂数据
info 端点默认是空的,因为 Spring Boot 不知道你想展示什么内容。最简单的做法是在配置文件里声明:
info: app: name: @project.artifactId@ version: @project.version@ description: @project.description@这里的@...@占位符会从 Maven 项目属性里读取值,构建时自动替换。配合 CI 流水构建信息,info 端点就能展示出当前环境的制品版本和描述,排障时一眼确认线上跑的是哪个构建。
如果需要更动态的信息,可以实现InfoContributor:
@Component public class BuildInfoContributor implements InfoContributor { @Override public void contribute(Info.Builder builder) { builder.withDetail("lastDeployTime", LocalDateTime.now().toString()); } }这样每次服务启动后,info 端点都会带上最近一次部署的时间。如果你有发布平台或运维系统,把部署元数据写到这个接口里,让监控大盘能直接读取,是很实用的做法。
3.3 自定义一个业务端点
除了 health 和 info,Actuator 也允许你完全自定义端点。一个常见场景:线上想临时查看某个业务配置开关的值,又不想接一堆运维系统。
定义一个端点:
@Component @Endpoint(id = "businessConfig") public class BusinessConfigEndpoint { @ReadOperation public Map<String, Object> getConfig() { Map<String, Object> result = new HashMap<>(); result.put("orderTimeoutSeconds", 30); result.put("retryTimes", 3); return result; } }然后在配置里暴露这个端点:
management: endpoints: web: exposure: include: "health,info,metrics,businessConfig"访问/actuator/businessConfig,就能看到你定义的配置项。
自定义端点可以标@ReadOperation、@WriteOperation、@DeleteOperation,分别对应 GET、POST、DELETE 请求。写在管理体系的内部工具很顺手,但我建议在自定义端点里不要直接返回原始配置项,至少做一层脱敏或增加鉴权判断,否则容易埋雷。
- 生产环境安全策略与监控系统对接
4.1 三层暴露控制方案
Actuator 本身没有安全功能,它只负责把信息暴露出来。生产环境的安全防线要依次铺开。
第一层是自身配置。用include白名单明确列出允许暴露的端点,不用*。起码做到 env、heapdump、loggers 不直接暴露。
第二层是网络隔离。如果服务部署在云环境,管理端口只对内网安全组开放;如果是传统机房,管理端口走内网 IP 监听,不让公网 IP 绑定。这里有个个性化的做法:直接让 Actuator 监听在127.0.0.1上。可以查一下本地是否已经开了公网端口,如果只是本机做服务器,这样配置最稳。
第三层是应用级鉴权。如果管理端口必须给特定外部系统访问,可以引入 Spring Security,给 Actuator 端点单独设置规则:
@Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .securityMatcher("/actuator/**") .authorizeHttpRequests(registry -> registry .requestMatchers("/actuator/health", "/actuator/info").permitAll() .anyRequest().authenticated() ); return http.build(); }这样 health 和 info 允许匿名探活,其余端点都必须通过认证。注意这里的写法与 Spring Security 6 的版本对应,Spring Boot 2.x 和 3.x 在 API 上略有差异,但核心思路一致。
4.2 对接 Prometheus 指标采集
Actuator 自带 metrics 端点,但要真正接入监控系统做告警、趋势图,一般会搭配 Micrometer 和 Prometheus。
在依赖里加:
<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>然后暴露 prometheus 端点:
management: endpoints: web: exposure: include: "health,info,prometheus"之后访问/actuator/prometheus,会返回标准的 Prometheus 文本格式指标,包括 JVM 内存、GC 次数、线程数、HTTP 请求耗时分布等。Prometheus 采集器只需抓取这个地址,就能在监控面板上画出完整的趋势图了。
这里有一个常见坑:采集器抓不到指标时,先确认你是否把prometheus加进了暴露白名单。加了依赖并不等于端点自动暴露,Spring Boot 的默认行为仍然是只暴露 health 和 info。
另外,Micrometer 的指标名称在底层会转换成 Prometheus 风格。比如http.server.requests会变成http_server_requests_seconds这种带单位的时间序列。配置告警规则时要注意这个区别,别用错标签名。
4.3 容器环境下健康探针的正确姿势
如果把 Spring Boot 应用部署在 Kubernetes 之类的容器平台,Actuator 最典型的用法就是对接容器探针。
Spring Boot 2.3 之后支持了单独的存活探针和就绪探针状态,配置如下:
management: endpoint: health: probes: enabled: true开启后,/actuator/health/liveness代表应用进程是否存活,/actuator/health/readiness代表应用是否已经准备好接收流量。容器平台的探针可以直接指向这两个路径:
livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 periodSeconds: 10这里有个细节:只有 readiness 探针会考虑下游依赖的状态,liveness 探针只判断进程是否能继续运行。如果把下游依赖的检查也放进 liveness 探针,一旦依赖临时抖动,容器会反复重启,反而加剧故障。
Spring Boot 3.x 之后,探针端点默认开启,2.x 需要显式配置。这个版本差异在跨版本升级时最容易出问题。升级后如果发现探针路径 404,先检查是否因为默认值变化导致配置失效了。
- 实测中高频踩坑与排查速查
5.1 端点 404 的排查链路
访问/actuator/health时如果出现 404,大概率不是代码问题,而是暴露配置没生效。排查顺序我一般是这样:
先访问/actuator根路径,看它返回的 _links 里有哪些端点。如果 health 不在列表里,说明 include 配置没包含它,或者配置文件的层级写错了。
再确认配置前缀是否有management开头。很多人会把endpoints.web.exposure.include直接写在spring命名空间下面,这样 Spring Boot 根本读不到。
最后确认路径。Spring Boot 2.x 之后,所有端点都挪到了/actuator前缀下,旧版直接访问/health的路径已经废弃。
5.2 独立端口下访问不了
配置了management.server.port后,访问主端口会看到 404,这是正常现象。这时要访问的是管理端口本身。但如果管理端口也无法访问,检查三件事:
- 管理端口有没有被防火墙拦截。
- 绑定的 IP 是不是
0.0.0.0,如果在云环境绑定了 127.0.0.1,外部系统自然访问不到。 - 配置文件中是否有多处 management 配置互相覆盖,导致端口没有实际生效。
这个问题在容器环境尤为隐蔽,因为容器网络和宿主机网段经常不一致。探针请求到了容器内层,但端口绑定在某个特定网段,就会被网络层直接丢弃。
5.3 Security 拦截导致 401
引入 Spring Security 后访问 Actuator 端点出现 401 或 403,是最常见的权限冲突问题。Actuator 默认自己有一套基于 Endpoint 的权限机制,但与 Spring Security 结合时,交给 Security 的过滤器链处理即可。
关键是理清放行与保护的边界。health 和 info 建议放行给探活使用,其他端点走认证。
如果你的应用里已经存在一个 SecurityFilterChain 匹配了所有路径/, 注意别让 Actuator 的请求也落到这个规则里。必要的时候用securityMatcher("/actuator/**")单独切分一套规则,能避免很多奇奇怪怪的 401。
5.4 版本升级导致的配置不生效
从 Spring Boot 1.x 到 2.x 再到 3.x,Actuator 的配置模型有过比较大的调整。
最明显的是端点路径。旧版直接就是/health、/metrics,2.x 之后统一挂到了/actuator下,导致很多老监控脚本直接失效。
另一个是配置项迁移。1.x 里的management.security.enabled在 2.x 已经不存在,安全控制完全交给 Spring Security;2.x 里management.endpoints.web.exposure.include则延续到了 3.x。
升级时不要只盯着业务代码,把 Actuator 配置全部过一遍。我处理过几次升级事故,最后发现都是监控配置还在用旧版的方式,导致新的实例一直在生产中裸奔。
5.5 敏感信息泄露事故防范
Actuator 最容易出问题的三个端点:env、heapdump、loggers。
env 能直接看到配置属性,很多团队会把数据库密码直接写在 application.yml 里,一旦 env 暴露,所有凭据相当于公开了。生产环境下,密码依赖配置中心或环境变量注入,同时把 env 端点关掉或至少限制在管理端口。
heapdump 会生成 JVM 堆内存快照,这个文件里基本包含应用运行期间所有对象内容,里面可能有用户敏感数据、加密密钥等。不要让这个端点出现在公网可访问的路径上。
loggers 可以动态修改日志级别,这本身是排查问题的一把好手,但也意味着攻击者可以将服务日志调到 DEBUG 级别,制造大量输出拖垮磁盘。
我的生产配置一般是这样:
management: server: port: 8081 endpoints: web: exposure: include: "health,info,metrics,prometheus"就这四个,加上探针端点的话再把 health 的 probes 打开。env、beans、mappings 这些排查用的端点只在本地调试时临时打开,调试完立刻关掉。
我平时排查一个新服务的效率顺序是这样的:先看 health 的详情确认整体状态,再拉 metrics 看 JVM 内存和线程,最后到日志里找异常堆栈。大部分性能问题在这三步内都能定位。如果排查对象涉及第三方依赖,再看 mappings 确认路由是否注册正确。这套方法不依赖任何外部工具,只需要一个能访问管理端口的通道,非常丝滑。
这里再分享一个小技巧。如果你临时需要看某个端点但不想改配置重启应用,可以试试用 Spring Boot 2.x 之后提供的环境变量覆盖配置。在启动脚本里临时加一行:
MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics,prometheus不用改配置文件、不用重新构建,直接覆盖生效。排查完把环境变量去掉,重启就恢复原样。这种方式比直接改配置文件更干净,也更容易回滚。我在处理线上问题时经常这么用,省掉了一整套流水线流程。