有时候我觉得很多团队对“开发完成”和“生产就绪”的理解差了不止一个数量级。代码能跑通接口、页面能正常点击、数据库读写没毛病——这些只能证明你的业务逻辑没问题离让你的应用在服务器上稳定过日子还差着十万八千里。我见过不止一次这样的场景喜气洋洋上了生产第二天凌晨被监控群里的告警砸醒翻日志翻到崩溃连应用当前到底是个什么状态都不知道也见过K8s集群里Pod状态看着是Running但应用其实已经卡死流量打进来全超时负载均衡器却还在傻乎乎地转发。这就是典型的“业务代码写完生产环境没就绪”。而Spring Boot Actuator就是解决这一票问题的标配答案。它不是锦上添花的玩具是任何一个在生产环境中跑的服务都应该具备的基础能力。这篇文章我会从生产视角把它拆开揉碎健康检查怎么配置才不踩坑监控指标怎么看、怎么接内部状态怎么安全地暴露出来用于排障以及我这些年在实际项目里踩过的那些坑和总结出的经验。要知道Actuator在Spring Boot 2.x之后经历了比较大的改动底层监控体系统一到了Micrometer端点路径、配置方式都和1.x完全不同。所以今天的讨论以Spring Boot 2.3、最好是2.7或3.x为基准如果你的项目还在1.x建议优先做个升级计划别在旧版上继续打转了。1. 生产就绪与本地运行核心差别在哪里1.1 写了业务代码到底少了什么开发环境里你只需要关心一件事把接口调通。可应用一旦部署到生产周围会冒出一堆“看不见的邻居”——负载均衡器要判断你是否活着编排平台要决定要不要重启你监控系统要感知你的CPU、内存、线程池运维同学要能在出问题时快速定位是依赖挂了还是应用自身卡了。这一整套需求业务代码本身完全无法回答。举个最直接的例子一个Spring Boot应用数据库连接池已经被打满新请求全部排队。从业务接口看任何一个接口都可能超时但应用进程还活着端口还在监听。这时候如果负载均衡只看“端口通不通”它会把流量继续往这台机器发结果就是所有请求一起变慢事故范围进一步扩大。正确做法是把数据库连接池的状态放进健康检查里让负载均衡器能感知“这个节点虽然还活着但已经不健康了”从而停止转发流量。这就是Actuator存在的意义它把应用从“只有一个业务接口的黑盒”变成“一个可以观测、可以探活、可以拿到内部状态的翻译机”。1.2 Actuator能解决哪三个问题Actuator提供的功能很多面向生产场景可以归类成三条主线。第一条是健康检查。通过一个HTTP端点暴露应用的健康状态并且能够自动聚合数据库、Redis、消息队列等各组件的健康情况状态分为UP、DOWN、OUT_OF_SERVICE、UNKNOWN几种。负载均衡、容器编排、监控告警都可以依赖这个端点做决策。第二条是指标监控。借助Micrometer的指标抽象把JVM内存、GC次数、线程状态、HTTP请求量、数据源连接数等运行指标暴露给外部监控系统比如Prometheus、InfluxDB等。这些指标是事后追查“系统到底哪里先崩的”核心依据。第三条是内部状态暴露。你可以通过端点查看当前应用加载了哪些Bean、配置项的实际值、日志级别、线程栈快照、甚至生成堆转储文件。这些信息在生产排障时价值极高相当于给运维窗口打开了一扇门能做的远不止“看一眼日志”。一句话总结这篇文章要讲的就是三件事怎么让外部系统准确判断你的死活怎么用数据描述你的运行状态以及怎么在出问题时快速看清你的内脏。2. 健康检查让外部系统学会“探活”2.1 默认健康检查与状态码联动先做最简单的事引入依赖。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency启动应用后访问/actuator/health默认情况下这个端点是开放的不需要额外开启。返回内容大致长这样{ status: UP, components: { diskSpace: { status: UP }, ping: { status: UP } } }比较关键的一个细节是健康状态会映射到HTTP状态码。UP返回200DOWN返回503OUT_OF_SERVICE返回503。这意味着负载均衡器和K8s探针可以直接根据HTTP状态码判断节点健康状态不需要解析JSON。比如Nginx配置里做被动健康检查只要判断上游返回的码就能决定是否摘除节点。默认情况下健康检查只包含ping和一个diskSpace检查。前者就是说明应用进程还活着后者检查磁盘剩余空间是否低于阈值。但真实生产环境里你的应用通常依赖数据库、Redis、消息队列等外部组件这些组件的状态才是真正决定应用能不能服务的关键。Spring Boot对常见组件做了自动装配只要classpath里有对应的客户端依赖健康检查就会自动纳入这些组件的状态。举个例子如果你的服务用到了spring-boot-starter-data-redis健康检查会自动加入redis模块用到spring-boot-starter-data-jpa或MyBatis连接池也会有对应的数据源检查。这些自动装配的逻辑很省心但默认情况下健康端点的细节不会完整展示只给一个总体的UP或DOWN。这里就引出了生产环境第一个必改配置把健康检查详情打开。management: endpoint: health: show-details: alwaysshow-details有三个取值never、when-authorized、always。开发测试环境可以直接always生产环境我建议至少when-authorized这样配合Spring Security可以做到只有认证用户能看到详细组件状态外部探针只能拿到总体状态。如果你把健康检查端点暴露到公网又用了always那么数据库连接是否正常、Redis通不通这类信息就会被任何人看到为攻击者摸清你的技术栈提供了方便。2.2 自定义健康指示器把基础设施纳入探活范围自动装配能覆盖常见组件但每个项目总有自己独特的依赖。比如你的服务依赖某个第三方HTTP接口或者依赖一个内部的RPC服务这些不在Spring Boot的默认健康检查范围内。这时候就需要自定义HealthIndicator。实现方式非常简单实现HealthIndicator接口返回一个Health对象。Component public class ExternalApiHealthIndicator implements HealthIndicator { private final RestTemplate restTemplate; public ExternalApiHealthIndicator(RestTemplate restTemplate) { this.restTemplate restTemplate; } Override public Health health() { try { ResponseEntityString resp restTemplate.exchange( https://api.example.com/health, HttpMethod.GET, null, String.class ); if (resp.getStatusCode().is2xxSuccessful()) { return Health.up() .withDetail(statusCode, resp.getStatusCode().value()) .build(); } return Health.down() .withDetail(statusCode, resp.getStatusCode().value()) .build(); } catch (Exception e) { return Health.down(e).build(); } } }这里有一个我在项目中吃过亏的点自定义健康检查内部必须设置超时时间而且超时要短。默认的RestTemplate如果不配置超时会一直等到连接超时默认往往是几十秒甚至更长。健康检查端点是会被频繁轮询的如果一次检查就卡半分钟会导致负载均衡器认为节点响应缓慢甚至触发雪崩误判。一般建议健康检查里的外部依赖调用超时控制在1~2秒以内宁可把这路检查标记为DOWN也不能让健康检查自身拖垮调用方。另外一个容易踩的坑是健康检查的递归问题。如果你的服务A健康检查会调用服务B而服务B的健康检查又会反过来调用服务A两边一挂健康检查互相等待直接把这个环节变成故障放大器。生产环境里要避免在健康检查链路中引入跨服务的同步调用尤其不要形成环。健康检查只应该检查本应用直接依赖的资源不应层层代理。多组件状态的聚合顺序也值得了解一下。Spring Boot在返回健康信息时会按照HealthAggregator的规则汇总默认规则是只要有一个组件是DOWN整体就是DOWN如果全部UP整体就是UP如果处于不同状态按照UNKNOWN、UP、DOWN中的优先级输出。你当然也可以通过自定义HealthAggregator改变这个行为但一般默认规则已经够用。2.3 健康检查分组与按需裁剪很多团队会困惑一个问题健康检查端点究竟该给谁看给K8s探针看的和给运维人员看的对细节的需求并不一样。Spring Boot从2.2开始引入了健康组的机制允许你配置多组健康检查每组可以包含不同的健康检查器。management: endpoint: health: group: readiness: include: readinessState, db, redis liveness: include: ping比如说在K8s环境里通常会把存活探针liveness probe和就绪探针readiness probe分开配置。存活探针关心的是“进程是不是卡死了”就绪探针关心的是“这个实例能不能接收流量”。这是两件完全不同的事。一个比较典型的坑是很多团队在K8s里把存活探针直接指向/actuator/health。后果是什么假设数据库挂了健康检查返回DOWNK8s看到存活探针失败直接把Pod重启了。重启之后数据库还没恢复Pod再次失败再次重启。结果就是数据库故障期间你的应用在无限重启循环日志刷屏等数据库恢复后应用反而因为反复重启而启动缓慢白白延长了故障时间。正确的做法是存活探针只检查应用自身是否还活着比如访问/actuator/health/liveness它只关心进程状态就绪探针才检查外部依赖使用/actuator/health/readiness。这样数据库挂了Pod会处于未就绪状态不再接收流量但进程不会被反复杀掉。这里要点名一个配置项management: endpoint: health: probes: enabled: true启用之后/actuator/health/liveness和/actuator/health/readiness这两个端点才会生效。如果你不用K8s而是用Consul、Eureka或者其他注册中心思路是类似的注册中心探活用基础健康检查而流量摘除用更加严格的全量健康检查。3. 指标监控用数字看清应用的呼吸3.1 Micrometer指标体系抽象层Spring Boot 2.x之后Actuator的监控能力全面基于Micrometer实现。Micrometer做的事情简单说就是定义一套统一的指标API然后把它桥接到各种监控后端。你写代码时操作的是MeterRegistry它底层可以输出到Prometheus、InfluxDB、Graphite等切换后端只需要改依赖和配置业务代码不用动。为什么要强调这一点因为很多团队从Spring Boot 1.x升级到2.x时会发现原来Actuator自带的那些/metrics端点数据结构全变了。在1.x里你访问/metrics能拿到一堆直接拼好的JSON数据在2.x里/metrics只是一个指标列表具体某个指标的值要再访问/actuator/metrics/{name}才能拿到。这个设计变化后面是Micrometer的架构思想先收集再按需格式化成不同后端的格式。默认情况下Actuator帮你内置了大量自动配置的指标包括JVM内存、垃圾回收、线程池、HTTP请求、数据源连接池等。我们只需要把这些指标暴露出来即可。暴露的配置项是management: endpoints: web: exposure: include: health,info,metrics,prometheus注意这里有个新手几乎必踩的坑exposure.include的默认值只包含health和info。很多人引入依赖后访问/actuator/metrics发现404就是这个配置没改。改成*可以暴露所有端点但生产环境不建议因为很多端点信息密度高、敏感性强后面第5部分我会专门讲安全策略。3.2 JVM与系统指标解读哪些数据值得长期观察先不急着接入监控系统我们直接用Actuator自带的端点看一眼指标长什么样。访问/actuator/metrics会返回当前所有可用指标名称的列表有数百项之多挑几个生产环境真正关心的说一下。jvm.memory.used和jvm.memory.max反映的是JVM堆内和堆外的内存使用情况。前者是当前已用内存后者是JVM允许使用的最大内存。这两个指标配合起来看能判断应用是否出现了内存泄漏趋势。正常服务的内存曲线应该是随负载波动的如果某个版本发布后内存只升不降并且一路逼近Xmx上限GC压力会越来越大这时候就该考虑是不是有对象被无意识缓存了。jvm.gc.pause是观察停顿的关键指标。随着内存使用增长GC暂停时间也会变长。如果出现连续的Full GC每次暂停都超过秒级业务接口的响应时间就会肉眼可见地恶化。你会发现接口超时告警和GC告警高度相关这就是典型的“接口变慢罪魁祸首不在下游而在JVM自己身上”的场景。system.cpu.usage和process.cpu.usage分别表示整台机器和当前进程的CPU使用率。前者适合判断是不是有别的进程在抢占资源后者适合判断应用自身的CPU消耗是否异常。配合线程指标jvm.threads.live、jvm.threads.daemon你能快速判断是不是线程数异常膨胀了。正常业务线程应该在一个合理范围内波动如果线程数一直上涨而不回落那大概率是线程池用完了不归还典型的就是使用ThreadPoolExecutor时没有调用shutdown或者异步任务积压。数据源连接池的指标在很多项目里是救命的hikaricp.connections.active、hikaricp.connections.pending、hikaricp.connections.idle。数据库连接池被打满往往不是数据库本身的问题而是上层有慢SQL占着连接不释放。看到pending持续大于0时优先去查慢SQL和事务处理逻辑比起直接重启应用根治得多。HTTP请求指标http.server.requests是接口监控的基础。它记录了每个接口的请求次数、耗时分布、状态码等。通过这个指标你可以按接口维度统计P99延迟、错误率再结合告警规则直接定位到最不稳定的那个接口。3.3 对接Prometheus从一条HTTP请求到抓取配置本地看指标是一回事生产环境里指标的价值在于“持续收集 历史对比 告警触发”所以最终一定要接入监控系统。目前最主流的搭配就是Prometheus加Grafana。要使Actuator能够输出Prometheus格式的指标需要引入一个额外的依赖dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependency引入依赖后访问/actuator/prometheus就会返回Prometheus文本格式的指标数据。这个端点的内容长这样# HELP jvm_memory_used_bytes The amount of used memory # TYPE jvm_memory_used_bytes gauge jvm_memory_used_bytes{areaheap,idG1 Eden Space,} 2.3456789E8 jvm_memory_used_bytes{areaheap,idG1 Survivor Space,} 1.2345678E7注意Spring Boot 3.x之后Prometheus端点的路径默认就不再是/actuator/prometheus的旧写法而是保留了这个路径但依赖坐标和指标格式略有不同。你在网上搜到大量写于Spring Boot 2.x时期的文章配置核心原则是通用的但细节一定要以当前版本为准。Prometheus抓取配置只需要指向这个端点scrape_configs: - job_name: spring-boot-app metrics_path: /actuator/prometheus static_configs: - targets: [192.168.1.10:8080]如果生产环境的所有服务都在同一套K8s集群里更常见的做法是利用K8s的Pod注解实现自动发现Prometheus会为每个Pod创建抓取目标metadata: annotations: prometheus.io/scrape: true prometheus.io/path: /actuator/prometheus prometheus.io/port: 8080这一步做完指标数据的采集链路就通了。之后在Grafana里可以导入现成的Spring Boot监控面板比如JVM内存、GC、线程、HTTP请求等基本不用自己从零画图。这里需要提醒一件事不要把Prometheus和其他监控系统的采集能力混为一谈。指标监控解决的是“长期趋势和数据对比”日志监控解决的是“具体事件和异常堆栈”链路追踪解决的是“一次请求经过哪些服务”。三天能力各有分工没有谁可以完全替代谁。经常有团队把精力全部花在接入链路追踪上指标监控却一直没接结果排查性能问题的时候只能靠猜这就本末倒置了。4. 状态暴露生产排障的最后一扇窗4.1 常用端点逐个分析beans、configprops、env、threaddump等健康检查告诉我们应用是死是活指标告诉我们是哪里在消耗资源但很多时候我们还需要回答另一个问题应用当前到底处于什么状态Spring Boot Actuator提供了一系列端点把应用运行时的内部状态暴露出来。我把生产排障时最常用的几个端点整理在一起。/actuator/heapdump这个端点最直接也最重量级访问它会触发一次JVM堆转储生成一个hprof文件供你下载后用MAT或JProfiler分析。内存泄漏排查时这是最有力的一手证据。它的代价是堆转储期间应用会短暂停顿堆越大、GC越频繁停顿越明显所以生产环境触发堆转储要挑业务低峰期。我自己踩过一次坑在业务高峰期点了堆转储堆有8个G转储过程直接引发了一轮长达十几秒的Full GC线上接口大面积超时那叫一个后悔。4.2 实时调试生产环境的典型场景State暴露的具体实用场景包括场景一Spring Security的权限判断与预期不符。你搭建好了安全配置但某个接口的访问行为就是和预期不一样。一个比较典型的案例是开发环境一切正常生产环境某个接口莫名502。这时访问/actuator/beans查看是否有多个Filter Bean被加载Filter的执行顺序是什么再配合/actuator/configprops查看拦截器的配置值能快速找到问题。在微服务架构里这些按照服务维度暴露的信息尤其有用。有一次我在排查线上一个接口超时问题时用了/actuator/threaddump发现某个线程一直卡在Connection.prepareStatement上。结合/actuator/heapdump分析确认是数据库连接池被一个长事务占满了连接应用自身没有虚报状态是底层依赖方案的问题。这比手动打点、盲改代码快十倍。4.3 线程信息与配置问题的排查思路线程信息在生产排查中特别关键。必要信息整理如下端点典型用途注意事项/actuator/threaddump查看当前所有线程的栈信息定位死锁、阻塞、CPU热点并发量大时信息量大建议在线下环境或低峰期使用/actuator/loggers动态查看和修改日志级别修改日志级别是实时的适合临时排查但要注意及时恢复/actuator/beans查看所有已加载的Bean定义确认配置是否生效有助于快速验证配置类和自动装配是否生效/actuator/configprops查看所有ConfigurationProperties的实际绑定值校验配置项是否按预期生效/actuator/env查看环境变量、系统属性、配置文件中的属性值注意敏感信息也暴露需谨慎管理如果你要用/actuator/loggers调整线上日志级别来做排查我建议调用前先记下来排查结束后一定记得改回去。这个过程尽量脚本化或二次确认避免关键时刻忘了恢复运维一整天忍受海量日志。5. 上线前必改的安全与配置坑5.1 端口必须受保护从路径暴露讲到访问控制Actuator的所有端点默认都挂在应用端口下也就是说只要你能访问应用的HTTP端口就能访问/actuator/beans、/actuator/env、/actuator/heapdump这些端点。生产环境如果不做任何保护等于把应用的内部内脏摆在公网上任人查看。/actuator/env里通常包含数据库地址、账号密码、Redis密码等信息/actuator/heapdump里甚至可能包含业务数据。这个风险我用一个保守的说法任何生产环境的安全扫描都不会放过Actuator端点。第一层保护是把管理端点独立到单独的端口。给应用配置一个管理端口比如9001所有Actuator端点只在9001端口上提供服务对外完全不可访问。配置如下management: server: port: 9001这样做的好处很明显应用业务端口即使暴露到公网Actuator端点也不会一起暴露内网监控系统访问9001即可。但注意改了管理端口之后健康检查端口也变成了9001需要同步修改负载均衡和容器探针的配置。第二层保护是无论内外网都建议加上Spring Security认证。比较轻量的方式是给Actuator端点单独设置一个角色运维人员登录后才能访问。Configuration public class ActuatorSecurityConfig { Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.securityMatcher(/actuator/**) .authorizeHttpRequests(auth - auth.anyRequest().hasRole(ADMIN)) .httpBasic(Customizer.withDefaults()); return http.build(); } }如果你不想引入Spring Security的全套机制也可以直接用Actuator自身的management.endpoint.health.show-details配合when-authorized来做详情保护但这个保护粒度很粗生产环境还是推荐认真配置认证。5.2 精细控制端点暴露、启用与过滤器management.endpoints.web.exposure.include控制的是“哪些端点对外可通过HTTP访问”这里要区分两个概念端点是“启用”的不等于“暴露”的。端点默认都是启用的但需要通过exposure配置暴露。生产环境里我一般只暴露这几个端点management: endpoints: web: exposure: include: health,info,metrics,prometheusheapdump、env、beans、threaddump这些信息量大的端点默认不暴露只保留在本地排查时按需打开。如果实在需要在线查看我建议通过管理端口独立访问而不是暴露在业务端口上。另一个精细控制点是要防止断路器或熔断等线程池状态被打满影响监控指标准确性。比如Hystrix或Resilience4j的指标会创建大量线程池等中间件对象Grafana面板上会显示大量无关的线程和指标。生产环境建议只保留对排障有价值的指标关闭那些明显冗余的中间件指标。如果你用的是Spring Boot 2.2Actuator还支持对端点的访问进行“下次请求前过滤”。利用management.endpoints.web.cors.allowed-origins可以限制跨域访问来源。但这只是针对浏览器跨域场景服务端之间的调用不受CORS限制所以不要把它当成安全兜底。6. 生产环境实战经验与排查案例6.1 线上接口偶发超时一个典型的线程卡死排查有一次线上服务偶发出现接口超时错误率不高但用户感知明显。直觉上先看日志但日志里除了超时没有任何异常堆栈。接着做三件事查看/actuator/metrics里的HTTP请求指标确认超时集中在那个时间段访问/actuator/threaddump抓取线程快照再结合/actuator/health确认所有依赖都正常。线程快照显示大量业务线程停在某段数据库查询的SocketInputStream.read上而数据库指标显示连接池活跃数很高。顺着这个方向查最终定位到是慢SQL导致数据库连接池被打满日志里因为SQL执行没有超时配置所以只表现为接口超时。这个问题没有Actuator的线程快照和连接池指标排查时间会翻倍。事后我们做了三件事给所有数据库查询加上超时时间、增加慢SQL告警、调整连接池参数。这个教训我记到现在。6.2 健康检查与自动重启的“死亡循环”另一个让我印象深刻的案例是容器环境里的“死亡循环”。服务的健康检查直接指向/actuator/health有一天Redis连接故障健康检查返回503K8s判定Pod不健康自动重启。重启后Redis仍然故障再次重启循环往复。每个Pod都被这样反复重启启动日志刷满整个监控系统问题看起来比实际严重得多。最佳实践是把存活探针和就绪探针分开存活探针指向/actuator/health/liveness就绪探针指向/actuator/health/readiness。前者只检查应用进程是否活着后者才检查外部依赖。如果数据库挂了Pod保持Running状态但标记为未就绪流量会被摘除但进程不会反复被杀。等服务恢复就绪后流量会自然恢复。相比反复重启这种方式对应用和依赖的冲击都小得多。6.3 从Docker到K8s生产环境的完整配置参考最后给出一份我在容器化部署中常用的Actuator配置示例供参考management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: when-authorized probes: enabled: true group: liveness: include: ping readiness: include: db,redis metrics: tags: application: ${spring.application.name}配套的Dockerfile健康检查HEALTHCHECK --interval30s --timeout3s --start-period20s \ CMD curl -f http://localhost:9001/actuator/health || exit 1注意这里的健康检查超时时间设置为3秒间隔30秒。如果服务启动时间比较长start-period给足启动预热时间避免还没启动完就被标记为不健康。这些细节在容器平台里都是直接决定服务可用性的因素绝不建议用默认值草草了事。6.4 最后的几点心得做生产就绪改造这么多年有几个体会想分享给正在读这篇文章的朋友。首先Actuator不是装修是地基。引入依赖、暴露端点只是第一步真正有价值的是理解每个端点背后对应的生产问题。健康检查的意义不是让运维看一行UP/DOWN而是让决策系统在故障时能做出正确判断指标监控的意义不是给自己画几张漂亮的仪表盘而是当事故来临时能拿出数据来快速定位内部状态暴露的意义不是把Bean列表当成庆功会PPT而是把排障时间从几小时压缩到几分钟。其次安全边界一定先想清楚再谈便利。Actuator把状态暴露得越好风险也越清晰。生产环境永远不要裸奔管理端口隔离、认证保护、最小暴露原则这三条做好后续运维安全性会好很多。另外生产事故排查不是靠单点信息就能解决的。健康检查、指标、日志、链路trace、线程快照、堆转储是一整套组合拳。别人给你一个接口超时的告警你能在十分钟内说清楚是CPU瓶颈、数据库瓶颈还是下游延迟这才是真功夫。Actuator只是工具箱里最顺手的那一把扳手但把它练熟了你的排障手感会上一个台阶。最后想说业务代码写完只是开始让它稳稳地在生产环境里跑下去才是每个开发者真正要反复打磨的能力。希望这篇文章能让你对“生产就绪”这四个字多一些具体的感知也让你的下一份代码不止能跑更能“扛住事”。
阅读完成 · 觉得有帮助?