ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot Actuator 实战:从健康检查到生产环境监控

Spring Boot Actuator 实战:从健康检查到生产环境监控 1. 为什么说 Actuator 是 Spring Boot 生产环境的“仪表盘”先讲一个我自己的真实经历。之前负责一个订单服务线上跑得好好的突然用户反馈下单变慢。我第一反应是登录服务器top看一眼 CPUdf看下磁盘再翻日志找异常。折腾了十几分钟才定位到是数据库连接池被打满而这一切如果提前接入了 Spring Boot Actuator其实在监控面板上一眼就能看到。Actuator 是 Spring Boot 提供的一个“探针式”监控模块它把应用内部的运行状态、环境信息、指标数据、日志级别、线程快照等内容统一通过 HTTP 端点或 JMX 暴露出来。你的应用在你的服务器里扮演什么角色——存活、健康、吞吐、内存压力、配置对不对、能不能远程调整日志级别这些都通过它对外“开口说话”。这篇文章我会按“引入配置—端点详解—安全加固—实战监控—踩坑排错”的路径把 Actuator 完整拆一遍。无论你是刚接触 Spring Boot 的新人还是在维护老项目的开发者都能从中拿到可以直接落地的配置和思路。先说一个很多人刚接触 Actuator 时的困惑为什么我加了依赖访问/actuator/health只有{status:UP}这么简单因为 Spring Boot 默认只暴露了health这一个端点而且默认只展示状态摘要。这种“安全优先”的设计理念贯穿整个 Actuator后面我会详细展开。2. 引入 Actuator最小化配置与暴露策略2.1 加依赖就这么简单在pom.xml中加入dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency如果你是 Gradle 项目implementation org.springframework.boot:spring-boot-starter-actuator这个 starter 会把你所需要的端点基础设施全部拉进来。注意它本身不包含第三方监控系统比如 Prometheus的适配器需要额外引入对应依赖。2.2 默认暴露策略安全优先Spring Boot 2.x 之后端点的逻辑被重新梳理了。所有端点默认是“已启用但未暴露”的状态你需要在配置中指定通过 HTTP 还是 JMX 暴露哪些端点。默认情况下只有health一个端点通过 HTTP 暴露。这是因为生产环境的安全红线——你不希望任何人一访问你的应用根路径就能看到你的环境变量、配置信息、线程状态这些东西。我的建议是先用最小暴露跑通再按需开放。比如只做存活检查什么都不配直接访问/actuator/health就够了。2.3 常用配置项详解management: endpoints: web: exposure: include: health,info,metrics,loggers,env exclude: shutdown base-path: /actuator endpoint: health: show-details: always shutdown: enabled: true逐行解释一下management.endpoints.web.exposure.include通过 HTTP 暴露哪些端点多个用逗号分隔也可以写*暴露全部不推荐除非你做好了安全控制。management.endpoints.web.exposure.exclude强制排除哪些端点。exclude的优先级高于include也就是说即使你写了include: *被 exclude 的端点也不会暴露。management.endpoints.web.base-path所有端点 URL 的前缀默认是/actuator。你可以改成/monitor之类的在一定程度上避免被扫描工具直接命中默认路径。management.endpoint.health.show-details控制健康检查端点是否展示详细信息。always在什么情况下都展示后面会专门说这个配置在生产环境有多重要。这里有一个容易踩坑的点include: health,info,metrics中间不要加空格如果你用 YAML 的列表写法也要保证格式正确management: endpoints: web: exposure: include: - health - info - metrics两种写法等价但混用容易出问题——我见过有人一行写多个还带了空格结果启动时端点一个都没暴露排查了半天。2.4 JMX 与 HTTP 双通道Actuator 的端点可以通过 HTTP 和 JMX 两种方式访问。默认情况下除了health和info其他端点都暴露在 JMX 中。生产环境中 JMX 用得相对少了因为需要通过jconsole或jvisualvm连接而且很多部署环境根本没开 JMX 端口。但如果你维护的是老项目又需要通过 JMX 获取运行状态可以显式配置management: endpoints: jmx: exposure: include: health,metrics这里我想强调一个观点Actuator 暴露什么取决于你对“监控面”的诉求。如果你只是要一个外部探活 URLhealth就够。如果要做容量规划和性能调优metrics和threaddump就是核心。后面逐个拆解。3. 核心端点逐个拆解有的只是数据有的是“救命稻草”3.1 health你的应用是“活着”还是“健康”这是最常用、也是唯一推荐无条件暴露的端点。它不只是简单返回一个 UP 或 DOWN而是会对各种健康指示器做聚合判断。默认情况下Spring Boot 会注册很多自动的健康指示器比如DiskSpaceHealthIndicator检查磁盘空间是否充足DataSourceHealthIndicator检查数据源能否获取连接RedisHealthIndicator检查 Redis 能否 ping 通MongoHealthIndicator/ElasticsearchHealthIndicator等对应中间件你可以在配置里看当前有多少健康指示器生效management: endpoint: health: show-details: always show-components: alwaysshow-details: always之后访问/actuator/health就能看到类似这样的返回{ status: UP, components: { db: { status: UP, details: { database: H2, validationQuery: isValid() } }, diskSpace: { status: UP, details: { total: 499963170816, free: 201011122176, threshold: 10485760, exists: true } }, redis: { status: UP, details: { version: 6.2.6 } } } }从上面返回能看出健康检查不是简单的“进程在不在”而是把应用依赖的关键资源全部检查了一遍。生产环境的建议show-details: always只在内网监控系统或安全可控环境下使用。如果应用暴露在公网建议设置为when-authorized并通过 Spring Security 控制谁能看到详细信息。再补充一个实用场景做 K8s 存活探针与就绪探针。livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080Spring Boot 2.3 自动注册了liveness和readiness两组探针端点如果你只是用/actuator/health做两种探针很可能会遇到“服务还没就绪就被打流量”或者“已处于不健康状态却一直不被重启”的尴尬。/actuator/health/readiness反映的是应用是否准备好接收流量比如 Spring 容器是否初始化完成、消息监听器是否启动/actuator/health/liveness反映的是应用进程是否活着如果挂了 K8s 会帮你重启。分开用别混用。3.2 info应用“名片”与构建信息info端点是一个自定义信息的聚合出口。默认情况下它只返回空的 JSON因为 Spring Boot 不知道你想展示什么。你可以通过application.yml或application.properties配置info: app: name: project.name version: project.version description: project.description这里的project.name是 Maven 资源过滤的占位符构建时会替换成pom.xml中对应的值。如果你用 Gradle需要手动配置processResources的展开。还可以通过实现InfoContributor来动态写入内容比如把 Git 提交号写进去Component public class GitInfoContributor implements InfoContributor { Override public void contribute(Info.Builder builder) { builder.withDetail(git, Map.of( commitId, abc123456, branch, main )); } }实际排查线上问题时info端点很有用你可以快速确认当前这个节点跑的是哪个版本、哪个分支的代码尤其是多环境部署时能省去很多“以为升级了其实没升”的尴尬。3.3 metrics性能数据的“矿产区”metrics端点是一个两级索引结构。访问/actuator/metrics你会看到一份指标名称列表类似{ names: [ jvm.memory.used, jvm.memory.max, jvm.threads.live, http.server.requests, process.cpu.usage, system.cpu.usage, hikaricp.connections.active, hikaricp.connections.pending, ... ] }想看具体某个指标的值要访问完整路径比如/actuator/metrics/jvm.memory.used /actuator/metrics/http.server.requests /actuator/metrics/hikaricp.connections.active每个指标还可以通过tag参数按维度过滤/actuator/metrics/http.server.requests?taguri:/orders /actuator/metrics/http.server.requests?tagstatus:500现场排查时最有用的几个指标jvm.memory.used和jvm.memory.max判断堆内存是否逼近上限jvm.threads.live看线程数是否异常飙升hikaricp.connections.active和hikaricp.connections.pending数据库连接池是否耗尽process.cpu.usage进程 CPU 占用http.server.requests接口请求量和耗时分布我记得有一次排查接口性能问题就是通过http.server.requests?taguri:/order/list发现某个接口的P99从 200ms 飙到 5s顺藤摸瓜找到了一个 N1 查询问题。3.4 loggers动态调整日志级别不用重启这个端点我在生产环境用过很多次它是 Actuator 里“性价比”极高的一个功能。平时定位问题最怕的是线上日志级别是 INFO而关键排查信息打在 DEBUG你只能加日志重新发版。有了loggers端点直接现场调。查看某个包的日志级别GET /actuator/loggers/com.example.order返回类似{ configuredLevel: null, effectiveLevel: INFO }动态修改为 DEBUGcurl -X POST -H Content-Type: application/json \ -d {configuredLevel:DEBUG} \ http://localhost:8080/actuator/loggers/com.example.order改完立即生效不需要重启。定位完问题再改回 INFO 就行。这里有一个小教训调 DEBUG 级别前要确认日志量不会把磁盘打爆。我之前在一个高并发服务上对全局 root logger 开了 DEBUG五分钟内日志文件涨了几个 GB差点把磁盘写满。正确做法是精确到出问题的那个类或包而不是一刀切。3.5 env 和 configprops配置问题排查的“照妖镜”线上最恶心的一个问题本地是好的测试环境也是好的一到生产就报错最后发现是某个配置项在服务器上没生效。env端点就是用来查这类问题的。GET /actuator/env返回所有Environment中的属性包括系统环境变量、application.yml、启动参数等。你可以按单个属性名查询GET /actuator/env/server.port也能看到这个属性来自哪里、覆盖关系是什么。搭配configprops端点可以查看ConfigurationProperties绑定后的真实值GET /actuator/configprops比如你配置了spring.datasource.hikari.maximum-pool-size从env里看到的是原始字符串从configprops里看到的是绑定到HikariDataSource配置类后的具体值。两者配合几乎能把“配置没生效”这个疑难杂症一锤定音。3.6 heapdump 和 threaddump故障现场的“尸检报告”heapdump端点用来下载 JVM 堆内存快照curl -o heap.hprof http://localhost:8080/actuator/heapdump这个文件可以用 MAT 或 JProfiler 分析看内存对象引用链、找出谁占着内存不释放。注意这个操作会触发 Full GC生产环境高负载时慎用。我一般在服务已经“病入膏肓”且准备重启时才抓 heapdump否则可能因为一次 Full GC 把服务卡死。threaddump则是给你一份当前所有线程的快照GET /actuator/threaddump包含每个线程的栈信息、锁状态、线程状态。在排查线程死锁、线程池阻塞、接口 hang 住等问题时这个端点比jstack命令来得方便因为它不要求你登录服务器而且能看到 Java 层的完整调用栈。3.7 shutdown优雅停机到底要不要开shutdown端点可以触发应用的优雅关闭management: endpoint: shutdown: enabled: true然后curl -X POST http://localhost:8080/actuator/shutdown它能调用 Spring 容器关闭流程执行PreDestroy回调释放连接池但是我强烈不建议在生产环境暴露它。原因很简单第一如果你用POST /actuator/shutdown关停应用K8s 或容器的优雅停机信号SIGTERM就无法传递到 JVM 进程内部可能导致 Spring 容器没来得及执行清理逻辑进程就被强制杀死。第二如果你需要远程关停服务用运维平台的发布系统发 SIGTERM 信号更规范和可控而不是通过一个 HTTP 请求直接干掉服务。4. 端点安全Actuator 暴露了你的内网“裸奔”了吗4.1 公网裸奔的风险很多人觉得“我的服务在内网没事”。但内网不等于安全横向移动攻击在真实攻防演练中非常常见。一个暴露了env、heapdump、configprops的 Actuator 端点等于把你的数据库密码、Redis 密码、中间件地址全递到攻击者手里。你在/actuator/env里能看到spring.datasource.password在/actuator/configprops里能看到那些带ConfigurationProperties的类绑定的完整配置。这些信息在乙方安全团队做渗透测试时是最容易得手的突破口。4.2 三种主流防护方式我把生产中常用的防护手段按推荐程度排一下方案一独立管理端口 内网白名单management: server: port: 9090 address: 127.0.0.1 endpoints: web: base-path: /actuatormanagement.server.port把 Actuator 从业务端口剥离出去address: 127.0.0.1则只允许本机访问。需要对接监控系统时通过 Nacos、Consul 等服务发现机制让监控系统访问本机地址或者配合云服务商的安全组只对监控服务器开放这个端口。方案二Spring Security 鉴权Configuration public class ActuatorSecurityConfig { Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.securityMatcher(/actuator/**) .authorizeHttpRequests(auth - auth .requestMatchers(/actuator/health).permitAll() .anyRequest().authenticated() ) .httpBasic(); return http.build(); } }只放行health其他端点都要认证。这种方案的缺点是引入了 Spring Security 依赖而且如果你的业务已经有一套安全配置需要小心过滤器链的顺序冲突。方案三网关层拦截如果服务在多级网关后面可以在网关层直接过滤/actuator/**路径只允许来自内网监控系统的 IP 访问。我的经验是方案一 方案三结合是成本最低、效果最好的组合。在服务上绑定本机回环地址从根源上断绝远程访问的可能监控系统通过机房侧的 Agent 采集本机数据网关层再做一层过滤。4.3 敏感信息脱敏那些你迟早要处理的密码就算做了认证env端点仍然会返回属性值。Spring Boot 中有SanitizingFunction机制可以自定义脱敏策略比如内置的password、secret、token等关键词会自动替换为******。GET /actuator/env/spring.datasource.password返回时密码字段会被打码。但你自定义的配置项如果不是用这些关键词命名比如custom: db: pwd: 123456那env端点会把这个值原样返回。解决办法是使用Value或ConfigurationProperties之前先确认属性名的关键词能被内置脱敏规则覆盖覆盖不了的实现一个自定义SanitizingFunctionComponent public class CustomSanitizer implements SanitizingFunction { Override public SanitizableData apply(SanitizableData data) { if (data.getKey().contains(pwd) || data.getKey().contains(password)) { return data.withSanitizedValue(******); } return data; } }这个细节很容易被忽略等到安全扫描报告出来第一个被点名的往往就是/actuator/env泄露敏感配置。5. 实战把 Actuator 接入 Prometheus Grafana 监控体系5.1 让 Actuator 输出 Prometheus 格式指标自定义指标和 JVM 指标只有转成 Prometheus 格式才能被 Prometheus 抓取。做法是引入 Micrometer 的 Prometheus 注册表dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependency然后在application.yml中暴露prometheus端点management: endpoints: web: exposure: include: health,prometheus,metrics访问/actuator/prometheus你会看到类似这样的文本输出# HELP jvm_memory_used_bytes The amount of used memory # TYPE jvm_memory_used_bytes gauge jvm_memory_used_bytes{areaheap,idPS Old Gen,} 1.23456789E8这就是 Prometheus 的标准文本协议格式。Prometheus 可以每隔 15 秒抓一次这个端点完成指标采集。5.2 自定义业务指标MeterRegistry 的正确用法metrics端点默认收集的都是 JVM、Tomcat、HikariCP 这一类“基础设施指标”。真正对业务有价值的是你自己埋点的指标。Micrometer 提供了MeterRegistry抽象在 Spring Boot 中直接注入即可Service public class OrderService { private final Counter orderCreatedCounter; private final Timer orderCreateTimer; public OrderService(MeterRegistry registry) { this.orderCreatedCounter Counter.builder(order.created.total) .description(Total number of orders created) .register(registry); this.orderCreateTimer Timer.builder(order.create.duration) .description(Time taken to create an order) .register(registry); } public void createOrder(Order order) { orderCreateTimer.record(() - { // 实际业务逻辑 orderCreatedCounter.increment(); }); } }这类业务指标接入prometheus端点后被采集才能在 Grafana 上画出“下单量趋势图”“接口耗时热力图”。只监控 CPU 和内存不监控业务指标在系统出问题的时候你很难定位到具体是哪个业务链路在恶化。5.3 Prometheus 配置与告警规则Prometheus 抓取配置scrape_configs: - job_name: order-service metrics_path: /actuator/prometheus static_configs: - targets: [192.168.1.10:8080]告警规则示例groups: - name: order-service-alerts rules: - alert: OrderServiceHighErrorRate expr: sum(rate(http_server_requests_seconds_count{status500}[5m])) / sum(rate(http_server_requests_seconds_count[5m])) 0.05 for: 5m labels: severity: critical annotations: summary: Order service 5xx error rate above 5%配合 Alertmanager 做企业微信、钉钉或邮件通知。这套东西搭起来之后你的服务就不再依赖“用户跑过来说用不了”才被发现问题了。5.4 Grafana 面板的关键图表我不建议把自己逼成全职监控开发但下面这几张基础图值得优先配置好JVM 堆内存使用率jvm_memory_used_bytes / jvm_memory_max_bytes活跃线程数与线程池队列长度HTTP 请求量、P99 耗时、5xx/4xx 比例数据库连接池活跃连接数与等待线程数GC 次数与 GC 耗时在晚上收到报警之后我会先打开 Grafana按照“先看基础设施CPU/内存→ 再看中间件连接池/GC→ 最后看应用指标”的顺序快速定位问题而不是一上来就翻日志。6. 生产环境里那些让你栽跟头的 Actuator 坑6.1 端口配置不当导致监控全部失效有些人想当然地设置了management: server: port: 8080表面上看没毛病实际上这个配置表示管理端点和业务端点共用 8080 端口但暴露路径仍然是/actuator/*。如果你后面又改成 9090 端口那么访问/actuator/health就会变成访问/9090/actuator/health很多老监控系统还是按原端口配的改完就抓不到数据。我见过一个生产事故运维把management.server.port改到 9090结果 Prometheus 的targets还指向 8080整整一天没有指标数据直到某个依赖的磁盘告警才发现监控断了。所以要么别单独设端口要么设了之后把所有监控采集端同步更新。6.2 show-details: always 的“好心办坏事”health端点把show-details设为always后每次被外部探活都会对所有健康指示器做一次完整检查。如果下游某个组件响应慢了比如 Redis 连接超时配置了 5 秒那么健康检查本身也会变慢。更麻烦的是健康检查 URL 如果被负载均衡器配置了较短的超时时间比如 3 秒一旦某个健康指示器卡住负载均衡器会认为你的服务不健康把它摘掉。健康检查链路影响调度决策所以生产环境我一般这样建议对外部负载均衡用show-details: never只返回 UP/DOWN响应最快对内部监控系统用另一个带鉴权的端口开show-details: when-authorized6.3 自定义 HealthIndicator 的“阴间操作”很多团队会自定义健康检查逻辑比如“检查某个第三方 API 是否能通”。我见过一个反面案例Component public class WechatApiHealthIndicator implements HealthIndicator { Override public Health health() { String result restTemplate.getForObject(https://api.weixin.qq.com/cgi-bin/token, String.class); return Health.up().build(); } }这个health()每次被调用都会发起真实的 HTTP 请求如果第三方接口慢或网络抖动你的健康检查就会被拖到超时。健康检查是用来反映“本应用能不能正常工作”的不是用来探活第三方服务的。正确做法是加缓存每隔 30 秒检测一次把结果缓存起来健康检查直接读缓存状态Component public class WechatApiHealthIndicator implements HealthIndicator { private volatile Health cachedHealth Health.unknown().build(); PostConstruct public void init() { ScheduledExecutorService scheduler Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::check, 0, 30, TimeUnit.SECONDS); } private void check() { try { // 第三方健康探测 cachedHealth Health.up().build(); } catch (Exception e) { cachedHealth Health.down(e).build(); } } Override public Health health() { return cachedHealth; } }这样既不影响健康检查的响应速度又能反映真实状况。6.4 Spring Boot 2.x 与 3.x 的端点迁移差异Spring Boot 3.x 里Actuator 有几个明显变化spring-boot-starter-actuator的坐标没变但底层基于 Jakarta EE 9一些端点路径微调比如/actuator/httptrace改成了/actuator/httpexchanges/actuator/conditions更名为/actuator/beans相关的条件报告路径有调整如果你从 2.x 直接升级到 3.x且监控脚本里硬编码了/actuator/httptrace升级后会遇到 404。建议升级前先扫一遍脚本里所有 Actuator 路径统一更新。6.5 依赖冲突Micrometer 版本不一致一个隐蔽的坑如果你的项目里既有micrometer-core又有micrometer-registry-prometheus但 Spring Boot 版本是 2.4Micrometer 会自动升级到 1.6而某些自研监控组件是基于 1.5 编译的就会出现NoSuchMethodError。踩过一次之后我现在都会在pom.xml里显式锁定 Micrometer 版本避免 Spring 依赖管理帮你“自动升级”。7. 我的个人实践体会Actuator 用到现在我最深刻的体会是它不是一个可以“加了依赖就完事”的功能而是一整套可观测性思维的入口。刚开始我也是一个端点都不看只知道/actuator/health返回 UP 就觉得服务正常。后来出了几次线上事故才慢慢理解健康检查、指标端点、日志动态调整、线程快照不是孤立的功能它们是“应用运行态的四个切面”。健康检查告诉你服务能不能接流量指标告诉你性能和容量到没到极限日志动态调整帮你现场排查问题线程快照帮你还原故障现场的调用关系。我建议你按照这个顺序逐步落地第一步先接入health和info配合 K8s 探针和发布流程解决“服务挂没挂”的问题第二步暴露metrics接入 Prometheus Grafana把基础设施指标可视化解决“容量够不够”的问题第三步暴露loggers和threaddump配合线上排障手册解决“出了故障怎么定位”的问题第四步自定义业务指标和 HealthIndicator解决“业务健康度怎么看”的问题最后再分享一个小技巧每次发版前把/actuator/health、/actuator/metrics、/actuator/info三个 URL 的返回结果截图或存一份万一发版后有问题可以对比前后差异快速定位是环境变了还是代码变了。这个习惯帮我省了无数次“我本地明明是好的”的争论。
返回列表