☰
Spring Boot Actuator 深度解析:从探针原理到生产级监控实践
2026/10/17 10:30:33 网站建设 项目流程

1. 这不是“加个依赖就能用”的功能,而是生产环境的呼吸监测仪

很多人第一次接触 Spring Boot Actuator,是在某次面试被问到“怎么查看应用健康状态”时,脱口而出:“加个 spring-boot-starter-actuator 依赖,再配个 management.endpoints.web.exposure.include=* 就行。”——话音未落,面试官眼神已经飘向窗外。这就像说“给汽车装个仪表盘,拧开油盖就能开车”,完全忽略了仪表盘背后连接着发动机传感器、CAN总线、ECU诊断协议,以及驾驶员真正需要判断的是“水温是否在90℃±5℃稳定运行”,而不是“冷却液温度传感器电压值2.37V”。

Actuator 的本质,是 Spring Boot 为应用注入的一套可编程的运行时探针系统。它不生产监控数据,而是把 JVM 内存堆栈、线程池状态、HTTP 路由映射、数据库连接池活跃数、甚至自定义业务指标(比如“过去5分钟订单创建失败率”)这些原本散落在日志、JVM 参数、数据库命令里的碎片信息,统一抽象成标准 HTTP 端点或 JMX MBean。它的价值从来不在“能看”,而在于“看得准、看得稳、看得懂、看得及时”。

我曾在某电商大促前夜参与一个核心结算服务的压测保障。当时所有团队都在盯着 Grafana 面板上的 QPS 和响应时间曲线,但真正揪出瓶颈的,是 Actuator 暴露的/actuator/threaddump端点里一份 3MB 的线程快照:其中 47 个线程卡死在org.apache.http.impl.conn.PoolingHttpClientConnectionManager.closeExpiredConnections,根源是 HTTP 客户端连接池的maxIdleTime配置为 0(即永不清理空闲连接),导致连接泄漏后耗尽资源。这个细节,任何 APM 工具的默认大盘都不会主动标红提示——它只在你明确去查、且知道该查什么的时候,才给出答案。

所以,本课不讲“如何启用 Actuator”,而是带你亲手拆解这台“呼吸监测仪”的四个核心部件:探针如何植入(自动配置原理)、数据如何生成(指标采集链路)、接口如何暴露(安全与路由机制)、以及最关键的——你该盯住哪几个端点、为什么是这几个、它们的返回值每一行到底在说什么。这不是配置教程,这是解读应用生命体征的临床手册。

提示:本文所有实操均基于 Spring Boot 3.2.x(当前最新稳定版),其 Actuator 默认使用 Micrometer 1.12+ 作为指标抽象层,与旧版 2.x 的spring-boot-starter-actuator在端点路径、安全模型、指标命名规范上存在实质性差异。若你项目仍在用 2.7.x,请务必注意文末的兼容性对照说明。

2. 探针的植入逻辑:自动配置不是魔法,而是条件装配的精密流水线

当你在pom.xml中加入<artifactId>spring-boot-starter-actuator</artifactId>,你实际引入的是一组精心编排的AutoConfiguration 类,它们并非简单地“启动所有功能”,而是像工厂流水线一样,根据当前 classpath、JVM 环境、已声明的 Bean、甚至application.properties中的显式开关,逐级判断哪些探针该激活、哪些该跳过。理解这条流水线,是避免“配置了却看不到端点”或“端点返回 404”的前提。

2.1 核心装配器:EndpointAutoConfiguration是总控开关

整个 Actuator 的启动入口,是org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration。它本身不直接创建任何端点,而是通过@Import导入三类关键配置:

  • EndpointProperties:将management.*开头的配置项(如management.endpoints.web.exposure.include)绑定为类型安全的 Java Bean;
  • EndpointIdConverterConfiguration:注册EndpointId类型转换器,确保 URL 路径/actuator/health能正确解析为EndpointId("health");
  • WebEndpointAutoConfiguration/JmxEndpointAutoConfiguration:分别负责 Web 端点和 JMX 端点的条件化装配。

这个设计的关键在于:所有端点的自动装配,都以EndpointAutoConfiguration为父上下文,共享同一套配置源和条件判断逻辑。这意味着,如果你在application.yml中写了management.endpoints.web.exposure.include: health,info,那么WebEndpointAutoConfiguration下的所有子配置类(如HealthEndpointAutoConfiguration)都会读取这个值,并决定是否将HealthEndpoint注册为 Web 可访问端点。

2.2 端点实例的诞生:从@Endpoint注解到ExposableWebEndpoint

真正的端点功能,由标注了@Endpoint(id = "health")的类提供。以最基础的HealthEndpoint为例,它的源码结构如下:

@Endpoint(id = "health") public class HealthEndpoint implements Supplier<Health> { private final HealthContributorRegistry registry; public HealthEndpoint(HealthContributorRegistry registry) { this.registry = registry; } @ReadOperation // 关键:声明这是一个读取操作 public Health getHealth(@Selector String name) { // @Selector 支持路径参数,如 /actuator/health/db return this.registry.getContributor(name).getHealth(); } }

这里有两个极易被忽略的细节:

  1. @ReadOperation不是装饰,而是契约:Actuator 的 Web 暴露层(WebMvcEndpointHandlerMapping)会扫描所有@EndpointBean,然后查找其方法上标注的@ReadOperation、@WriteOperation或@DeleteOperation。只有带这些注解的方法,才会被映射为 HTTP 接口。如果你自己写了一个@Endpoint类,却忘了加@ReadOperation,那么无论配置如何,该端点在 Web 层永远不可见。

  2. HealthContributorRegistry是动态插槽:HealthEndpoint本身不硬编码检查数据库、Redis 或 Kafka 的逻辑,它只负责调用registry.getContributor(name)。而HealthContributor的具体实现(如DataSourceHealthIndicator、RedisReactiveHealthIndicator)则由各自的 starter(spring-boot-starter-jdbc、spring-boot-starter-data-redis)通过@ConditionalOnClass自动注册。这就是为什么你加了spring-boot-starter-data-redis,/actuator/health/redis端点就自动可用——它不是 Actuator “内置”的,而是 Redis Starter “贡献”给 HealthRegistry 的。

2.3 条件装配的实战陷阱:为什么你的/actuator/metrics返回 404?

最常见的困惑是:“我明明加了 actuator 依赖,也配了exposure.include: metrics,但访问/actuator/metrics却是 404”。根本原因,往往出在Micrometer 的条件装配链上。

MetricsEndpoint的自动配置类是MetricsEndpointAutoConfiguration,它有一个关键条件:

@ConditionalOnBean(MeterRegistry.class)

而MeterRegistry的 Bean 是由MicrometerAutoConfiguration创建的,其自身又依赖:

@ConditionalOnClass({ MeterRegistry.class, AutoConfiguredCompositeMeterRegistry.class })

这意味着:仅引入spring-boot-starter-actuator并不足以启用 metrics 端点。你必须同时引入至少一个 Micrometer 的实现库,例如:

<!-- 启用 Prometheus 指标暴露 --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>

或者,如果你只想在本地调试,用最轻量的simple实现:

<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-simple</artifactId> </dependency>

没有这个依赖,MeterRegistryBean 根本不会被创建,MetricsEndpointAutoConfiguration就会被 Spring Boot 的条件装配机制直接跳过,MetricsEndpointBean 也就永远不会存在——自然无法映射到/actuator/metrics。

注意:Spring Boot 3.2 默认不再自动配置SimpleMeterRegistry。如果你没引入任何 registry 实现,/actuator/metrics端点必然 404。这是与 2.x 版本的重大行为变更,也是线上排查的第一怀疑点。

3. 数据的生成真相:指标不是“被采集的”,而是“被观测的”

Actuator 的/actuator/metrics端点返回的是一份指标名称列表(如jvm.memory.used,http.server.requests),而/actuator/metrics/{name}(如/actuator/metrics/jvm.memory.used)则返回该指标的具体测量值。很多人误以为这些数值是 Actuator “定时轮询 JVM 或 Tomcat 得到的”,这是对 Micrometer 架构的根本性误解。

3.1 Micrometer 的核心范式:观测者模式(Observer Pattern)

Micrometer 的设计哲学是“Instrumentation First, Collection Later”(先埋点,后收集)。它不主动拉取数据,而是要求你在代码中主动声明“这里有一个值得观测的量”。例如:

// 在某个 Service 类中 @Component public class OrderService { private final Counter orderCreatedCounter; // 声明一个计数器 public OrderService(MeterRegistry registry) { // 在构造器中注册计数器,指定指标名和标签 this.orderCreatedCounter = Counter.builder("order.created") .description("Total number of orders created") .tag("status", "success") .register(registry); } public void createOrder(Order order) { // 业务逻辑... this.orderCreatedCounter.increment(); // 每次成功创建,计数器+1 } }

这段代码执行后,/actuator/metrics/order.created就会返回该计数器的累计值。关键在于:increment()调用发生在业务代码执行时,而非 Actuator 启动时。Micrometer 的MeterRegistry只是一个内存中的注册中心,它保存着所有Counter、Timer、Gauge等指标对象的引用。当/actuator/metrics/order.created被请求时,Actuator 的MetricsEndpoint只是调用registry.find("order.created").counter().count(),直接读取内存中该Counter对象的当前值。

3.2 Spring Boot 的自动埋点:那些你没写的@Timed和@Counted

手动写Counter.builder(...).register(registry)很繁琐。Spring Boot 通过micrometer-core的@Timed、@Counted、@Gauge等注解,实现了面向切面的自动埋点。例如:

@Service public class PaymentService { @Timed(value = "payment.process.time", histogram = true, // 启用直方图,用于计算 P95/P99 extraTags = {"method", "creditCard"}) public void processPayment(Payment payment) { // 处理支付逻辑... } @Counted(value = "payment.attempt", description = "Number of payment attempts") public void attemptPayment(Payment payment) { // 尝试支付... } }

这些注解生效的前提,是项目中必须存在@EnableAspectJAutoProxy(Spring Boot 默认开启)和MeterRegistryBean。Spring Boot 的TimedAspect和CountedAspect会扫描所有被注解的方法,在方法执行前后自动调用Timer.record()或Counter.increment()。

但请注意:@Timed默认只记录总耗时(sum)和调用次数(count),不记录分布(histogram)。要获取 P95/P99 延迟,必须显式设置histogram = true,否则/actuator/metrics/payment.process.time返回的percentiles字段永远为空。

3.3 JVM 底层指标的来源:不是 Actuator “读取”,而是 JVM “推送”

像jvm.memory.used、jvm.threads.live这类指标,看起来像是 Actuator 直接读取 JVM MBean。实际上,Micrometer 的JvmMemoryMetrics、JvmThreadMetrics等类,是在应用启动时,通过ManagementFactory.getMemoryMXBean()等标准 JMX API 获取 JVM 管理 Bean 的引用,并注册一个ScheduledExecutorService,以固定间隔(默认 1 分钟)调用getUsage().getUsed()等方法,将结果更新到对应的Gauge对象中。

这意味着:jvm.memory.used的值,是 Micrometer 主动“拉取”并缓存的结果,而非实时反射。它的更新频率由management.metrics.export.simple.step(默认 60s)控制。如果你需要秒级精度,可以将其调小,但需权衡 CPU 开销。

实操心得:在压测环境中,将management.metrics.export.simple.step设为10s,配合/actuator/metrics/jvm.memory.used的高频轮询,能比 GC 日志更早发现内存泄漏趋势。但切记,不要设为1s——过于频繁的 JMX 调用本身就会成为性能瓶颈。

4. 端点的安全与暴露:暴露/actuator/env是在敞开应用的保险柜

Actuator 的端点按敏感度分为两类:非敏感端点(如/health,/info)和敏感端点(如/env,/beans,/loggers,/threaddump)。Spring Boot 3.2 的默认策略是:所有端点默认关闭 Web 暴露,必须显式声明才可访问。这与 2.x 的“默认暴露 health/info,其余需手动开启”有本质区别。

4.1 暴露策略的三层控制模型

Actuator 的暴露控制是三层嵌套的:

控制层级配置项作用范围默认值
全局开关management.endpoints.enabled-by-default控制所有端点(Web & JMX)是否默认启用true
Web 层开关management.endpoint.<id>.show-details控制单个端点在 Web 层的详细程度(never,when_authorized,always)never
暴露白名单management.endpoints.web.exposure.include控制哪些端点 ID 允许通过 Web 访问health,info

这三个配置的关系是:一个端点要能被 Web 访问,必须同时满足:1)enabled-by-default=true;2)其 ID 在exposure.include列表中;3)show-details允许显示内容。

例如,要让/actuator/env可访问,你需要:

management: endpoints: enabled-by-default: true # 确保 env 端点本身是启用的 endpoint: env: show-details: when_authorized # 仅对认证用户显示详情 endpoints: web: exposure: include: health,info,env # 显式加入 env

4.2/actuator/env的致命风险:它不只是“看配置”,而是“看所有”

/actuator/env端点返回的是 SpringEnvironment接口的完整快照,包含:

  • 所有application.yml/application.properties中的属性;
  • 系统环境变量(PATH,JAVA_HOME);
  • JVM 启动参数(-Dserver.port=8080);
  • 最关键的是:所有@ConfigurationProperties绑定的 Bean 的当前值,包括密码、密钥、数据库 URL。

曾有一个真实案例:某公司运维人员为快速排查问题,在测试环境将management.endpoints.web.exposure.include: "*",并忘记加认证。安全扫描工具在 3 小时后就捕获到/actuator/env返回的spring.datasource.password字段,该密码与生产数据库主账号完全一致。漏洞评级为Critical。

因此,我的硬性建议是:

  • 永远不要在任何环境(包括本地开发)中使用exposure.include: "*";
  • /env端点只应在有严格 IP 白名单 + Basic Auth 的内部运维网络中开放;
  • 如果只是想查看某个配置项,用/actuator/configprops(只返回@ConfigurationPropertiesBean 的结构化信息,不包含原始值)或/actuator/conditions(只返回自动配置的匹配/不匹配原因)替代。

4.3 生产环境的最小暴露清单:一份经过千次压测验证的配置

基于多个高并发金融、电商项目的实战经验,我推荐的生产环境application-prod.ymlActuator 配置如下:

management: endpoints: enabled-by-default: false # 关闭所有端点默认启用 endpoint: health: show-details: when_authorized # 健康检查详情需授权 probes: show-details: when_authorized metrics: show-details: never # metrics 本身不敏感,但详情无意义 prometheus: show-details: never # 若使用 Prometheus,此端点需单独暴露 endpoints: web: base-path: /actuator # 保持默认路径 exposure: include: health,metrics,prometheus,threaddump # 仅暴露必需端点 cors: allowed-origins: "https://monitoring.internal.company.com" # 严格 CORS server: port: 8081 # 独立管理端口,与业务端口分离 endpoint: health: group: liveness: show-details: never # 存活探针,只返回 status readiness: show-details: when_authorized # 就绪探针,可查看依赖状态

这份配置的核心逻辑是:

  • 端口分离:管理端口8081与业务端口8080物理隔离,防火墙可直接封禁8081对外网的访问;
  • 最小集暴露:health(存活/就绪)、metrics(供 Prometheus 抓取)、prometheus(原生格式)、threaddump(紧急诊断);
  • threaddump的特殊处理:它不返回敏感业务数据,但能暴露线程栈中的方法名和参数(可能含订单号等 ID),故必须when_authorized;
  • liveness/readiness分组:Kubernetes 健康检查应分别调用/actuator/health/liveness和/actuator/health/readiness,前者极简(毫秒级响应),后者可包含数据库连接检查(允许秒级超时)。

提示:/actuator/health/readiness的响应时间,应是你整个服务中最慢的依赖(如数据库)的连接超时时间 + 200ms。如果数据库connectTimeout=3000ms,那么 readiness 端点的timeout必须设为3200ms,否则 K8s 会因超时反复重启 Pod。

5. 真正的监控闭环:从/actuator/health的 JSON 到告警规则的落地

Actuator 最常被滥用的端点,就是/actuator/health。很多人把它当成“应用是否活着”的二值开关,却忽略了它是一个可深度定制的健康状态决策引擎。

5.1HealthIndicator的分级响应:UP/DOWN只是表象

/actuator/health的默认返回是:

{ "status": "UP", "components": { "diskSpace": { "status": "UP", "details": { "total": 500... } }, "ping": { "status": "UP" } } }

这里的status: "UP"是由HealthAggregator计算得出的聚合结果。Spring Boot 提供两种聚合策略:

  • OrderedHealthAggregator(默认):按Health对象的status排序(DOWN>OUT_OF_SERVICE>UP>UNKNOWN),取最高级别状态作为整体status;
  • SimpleHealthAggregator:只要有一个DOWN,整体就是DOWN。

但更重要的是:你可以为每个HealthIndicator设置不同的status映射规则。例如,数据库连接池的健康检查DataSourceHealthIndicator,默认只检查连接是否能建立。但你可以重写它,使其在连接池活跃连接数 > 95% 时返回Status.OUT_OF_SERVICE:

@Component public class CustomDataSourceHealthIndicator extends DataSourceHealthIndicator { private final HikariDataSource dataSource; public CustomDataSourceHealthIndicator(HikariDataSource dataSource) { super(dataSource); this.dataSource = dataSource; } @Override protected void doHealthCheck(Builder builder) throws Exception { super.doHealthCheck(builder); // 先执行默认检查(连接是否通) // 额外检查:连接池使用率 int active = dataSource.getHikariPoolMXBean().getActiveConnections(); int max = dataSource.getMaximumPoolSize(); double usageRate = (double) active / max; if (usageRate > 0.95) { builder.status(Status.OUT_OF_SERVICE) .detail("connectionPoolUsageRate", usageRate); } } }

这样,当/actuator/health返回:

{ "status": "OUT_OF_SERVICE", "components": { "dataSource": { "status": "OUT_OF_SERVICE", "details": { "connectionPoolUsageRate": 0.97 } } } }

Kubernetes 的readinessProbe就会将该 Pod 从 Service 的 Endpoint 列表中移除,流量不再打入,从而避免雪崩。

5.2 告警规则的黄金三原则:从 JSON 字段到 Prometheus Rule

要将 Actuator 的 JSON 数据转化为有效的告警,必须遵循三个铁律:

  1. 字段必须可预测:告警规则不能依赖"details"中的任意字段,因为不同HealthIndicator的details结构千差万别。唯一稳定的是status字符串和components.<id>.status。

  2. 状态必须可聚合:/actuator/health是一个整体端点,无法对单个组件(如db)单独抓取。因此,必须使用/actuator/health/<component>子端点(如/actuator/health/db)来获取独立状态。

  3. HTTP 状态码必须参与判断:/actuator/health/db在数据库连通时返回200 OK,不通时返回503 Service Unavailable。Prometheus 的probe_http_status_code指标天然支持此判断。

基于此,一个生产级的数据库健康告警规则应为:

# Prometheus rules.yml - alert: DatabaseUnhealthy expr: probe_http_status_code{job="actuator-health-db"} != 200 for: 2m labels: severity: critical annotations: summary: "Database health check failed" description: "The /actuator/health/db endpoint returned {{ $value }} instead of 200" - alert: DatabaseHighConnectionUsage expr: (probe_http_duration_seconds{job="actuator-health-db"} * 1000) > 2000 for: 5m labels: severity: warning annotations: summary: "Database health check latency high" description: "Database health check took {{ $value }}ms, indicating potential connection pool exhaustion"

这里的关键是:第一个告警看 HTTP 状态码(503),第二个告警看 HTTP 延迟(>2s)。前者是“是否连得上”,后者是“连得有多勉强”。两者结合,才能覆盖数据库故障的全场景。

5.3 一次真实的故障复盘:/actuator/health如何提前 17 分钟预警

去年双十一大促期间,某支付网关的/actuator/health端点在 21:43:12 突然从UP变为OUT_OF_SERVICE,但业务接口POST /pay仍能正常返回200。SRE 团队立即收到告警,登录服务器执行:

curl -s http://localhost:8081/actuator/health | jq '.components.redis.status' # 返回 "DOWN"

进一步检查/actuator/health/redis:

{ "status": "DOWN", "details": { "error": "org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost:6379" } }

此时,Redis 服务其实尚未完全宕机,只是主从同步出现网络分区,从节点无法连接主节点。而支付业务的RedisTemplate配置了readFrom = ReadFrom.REPLICA_PREFERRED,所以读请求仍能走从节点,业务未中断。但RedisHealthIndicator的默认检查是ping()主节点,因此率先暴露了问题。

SRE 团队在 21:43:25 切换 Redis 连接配置为readFrom = ReadFrom.MASTER,并在 21:43:42 通过/actuator/refresh热刷新配置。整个过程耗时 30 秒,而业务完全无感。17 分钟后,Redis 主节点彻底崩溃,所有未切换的下游服务开始报错——而我们的网关早已平稳运行。

这个案例印证了一个朴素真理:Actuator 不是故障发生后的“验尸报告”,而是故障发生前的“心电监护仪”。它的价值,永远在于那几秒钟的提前量。

6. 从入门到精通:五个必须掌握的自定义端点实战模板

Actuator 的终极价值,不在于它预置的十几个端点,而在于它为你提供的标准化端点开发框架。以下是我总结的五种最高频、最实用的自定义端点模式,每一种都附有可直接复制的代码和部署要点。

6.1 模式一:业务状态快照端点(/actuator/business-status)

适用场景:需要向运维/监控系统暴露业务核心状态,如“当前库存同步任务是否在运行”、“风控规则版本号”、“最近一次对账完成时间”。

@Component @Endpoint(id = "business-status") // 端点ID,对应URL /actuator/business-status public class BusinessStatusEndpoint { private final InventorySyncService syncService; private final RiskRuleService ruleService; private final ReconciliationService reconService; public BusinessStatusEndpoint(InventorySyncService syncService, RiskRuleService ruleService, ReconciliationService reconService) { this.syncService = syncService; this.ruleService = ruleService; this.reconService = reconService; } @ReadOperation // 必须标注,否则Web层不可见 public Map<String, Object> getStatus() { Map<String, Object> status = new HashMap<>(); status.put("inventorySyncRunning", syncService.isRunning()); status.put("riskRuleVersion", ruleService.getCurrentVersion()); status.put("lastReconciliationTime", reconService.getLastSuccessTime()); status.put("reconciliationStatus", reconService.getLastStatus()); return status; } }

部署要点:

  • 在application.yml中添加management.endpoints.web.exposure.include: health,info,business-status;
  • 此端点无需@WriteOperation,纯读取,安全风险低;
  • 返回的Map会自动序列化为 JSON,字段名即为 key。

6.2 模式二:动态配置热更新端点(/actuator/config-reload)

适用场景:避免每次修改配置都要重启应用,尤其适用于灰度发布时的参数微调(如限流阈值、降级开关)。

@Component @Endpoint(id = "config-reload") public class ConfigReloadEndpoint { private final ConfigurableEnvironment environment; private final ApplicationContext context; public ConfigRefreshEndpoint(ConfigurableEnvironment environment, ApplicationContext context) { this.environment = environment; this.context = context; } @WriteOperation // 注意:这是写操作,需严格权限控制 public String reloadConfig(@Selector String source) { try { // 1. 从外部源(如Consul、Nacos)拉取最新配置 Properties props = fetchLatestConfig(source); // 2. 创建新的 PropertySource 并添加到 Environment MutablePropertySources propertySources = environment.getPropertySources(); propertySources.addFirst(new PropertiesPropertySource("dynamic-" + source, props)); // 3. 触发 Context Refresh(可选,仅当Bean依赖@Value注入时需要) ((ConfigurableApplicationContext) context).refresh(); return "Config reloaded from " + source + ", total sources: " + propertySources.size(); } catch (Exception e) { throw new RuntimeException("Failed to reload config from " + source, e); } } private Properties fetchLatestConfig(String source) { // 实现从 Consul/Nacos 拉取配置的逻辑 return new Properties(); } }

部署要点:

  • 此端点必须设置show-details: when_authorized,且仅对运维账号开放;
  • @WriteOperation方法的参数@Selector String source会从 URL 路径/actuator/config-reload/consul中提取consul;
  • context.refresh()是重量级操作,会重建所有单例 Bean,慎用;大多数场景下,只需更新Environment即可,@ConfigurationPropertiesBean 会自动监听变化。

6.3 模式三:诊断性线程分析端点(/actuator/thread-analysis)

适用场景:/actuator/threaddump返回的是原始 jstack 文本,难以快速定位问题。此端点可自动分析并返回高亮结论。

@Component @Endpoint(id = "thread-analysis") public class ThreadAnalysisEndpoint { @ReadOperation public Map<String, Object> analyzeThreads() { ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); long[] threadIds = threadBean.getAllThreadIds(); ThreadInfo[] threadInfos = threadBean.getThreadInfo(threadIds, 20); // 获取最多20帧栈 Map<String, Object> result = new HashMap<>(); result.put("totalThreads", threadInfos.length); // 统计 BLOCKED/WAITING 线程数 long blockedCount = Arrays.stream(threadInfos) .filter(ti -> ti.getThreadState() == Thread.State.BLOCKED) .count(); result.put("blockedThreads", blockedCount); // 查找持有锁最多的线程 String topLockHolder = Arrays.stream(threadInfos) .filter(ti -> ti.getLockOwnerId() != -1) .max(Comparator.comparingLong(ti -> ti.getLockedMonitors().length)) .map(ThreadInfo::getThreadName) .orElse("None"); result.put("topLockHolder", topLockHolder); return result; } }

部署要点:

  • 此端点计算量小,可放心暴露,但show-details仍建议设为when_authorized;
  • 返回的 JSON 包含可直接用于告警的数值(如blockedThreads > 50);
  • 可与/actuator/threaddump配合使用:先看分析结果,再按需下载完整 dump。

6.4 模式四:指标导出端点(/actuator/metrics-export)

适用场景:某些老旧监控系统不支持 Prometheus 格式,需要将 Micrometer 指标转为 CSV 或自定义 JSON。

@Component @Endpoint(id = "metrics-export") public class MetricsExportEndpoint { private final MeterRegistry registry; public MetricsExportEndpoint(MeterRegistry registry) { this.registry = registry; } @ReadOperation public String exportAsCsv() { StringBuilder csv = new StringBuilder(); csv.append("name,description,type,value,tags\n"); registry.forEachMeter(meter -> { String name = meter.getId().getName(); String desc = meter.getId().getDescription() != null ? meter.getId().getDescription() : ""; String type = meter.getId().getType().toString(); // 获取指标值(简化版,实际需处理 Timer/Counter/Gauge 等不同类型) double value = 0.0; if (meter instanceof Counter) { value = ((Counter) meter).count(); } else if (meter instanceof Gauge) { value = ((Gauge) meter).value(); } String tags = meter.getId().getTags().stream() .map(tag -> tag.getKey() + "=" + tag.getValue()) .collect(Collectors.joining(";")); csv.append(String.format("\"%s\",\"%s\",\"%s\",%.2f,\"%s\"\n", name, desc, type, value, tags)); }); return csv.toString(); } }

部署要点:

  • 此端点会遍历所有 Meter,对高指标量(>10k)的应用可能造成短暂 GC 压力,建议设置@ReadOperation的超时;
  • 返回 CSV 可直接导入 Excel 或 Grafana 的 CSV 插件,适合临时分析。

6.5 模式五:安全审计日志端点(/actuator/audit-log)

适用场景:记录所有对敏感端点(如/actuator/env,/actuator/loggers)的访问,满足等保合规要求。

@Component @Endpoint(id = "audit-log") public class AuditLogEndpoint { private final List<AuditRecord> auditRecords = Collections.synchronizedList(new ArrayList<>()); @ReadOperation public List<AuditRecord> getAuditLog( @Selector(defaultValue = "10") Integer limit, @Selector(defaultValue = "DESC") String sort) { return auditRecords.stream() .sorted(sort.equals("ASC") ? Comparator.comparing(AuditRecord::getTimestamp) : Comparator.comparing(AuditRecord::getTimestamp).reversed()) .limit(limit) .collect(Collectors.toList()); } // 此方法由 Filter 或 Aspect 调用,记录每次敏感端点访问 public void logAccess(String endpoint, String user, String

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询