Spring Boot Actuator 自定义端点——构建生产级健康检查体系一、Actuator 的定位与自定义端点的必要性Spring Boot Actuator 提供了开箱即用的生产级监控端点如/health、/metrics、/env等。但在企业级应用中默认端点只能覆盖通用场景。当我们需要暴露业务维度的健康信息如支付网关连通性、消息队列积压量、第三方服务 SLA时自定义端点就成为不可或缺的能力。本文将从自定义 HealthIndicator 到完全自定义的Endpoint端点逐层展开构建一套满足生产要求的健康检查体系。flowchart LR subgraph Actuator 端点体系 A[/healthbr/健康检查] B[/metricsbr/指标监控] C[/infobr/应用信息] D[/custom/biz-healthbr/自定义业务健康] E[/custom/degraded-servicesbr/降级服务列表] end subgraph 后端健康检查源 F[数据库连接检测] G[Redis 连接检测] H[消息队列积压检测] I[第三方支付网关检测] J[下游微服务可用性探测] end F -- A G -- A H -- D I -- D J -- E subgraph 消费方 K[Kubernetes Liveness Probe] L[负载均衡器健康检查] M[运维监控大盘] N[自动降级决策引擎] end A -- K A -- L D -- M E -- N二、自定义 HealthIndicator从简单到完整2.1 基础实现Actuator 的/health端点通过聚合多个HealthIndicator的实现来输出结果。最简单的自定义方式是实现该接口import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; /** * 数据库连接健康检查指示器。 * 检测数据源连接是否可用并报告连接池状态。 */ Component(database) public class DatabaseHealthIndicator implements HealthIndicator { private final DataSource dataSource; /** * param dataSource 应用数据源由 Spring 自动注入 * throws IllegalArgumentException 如果 dataSource 为 null */ public DatabaseHealthIndicator(DataSource dataSource) { if (dataSource null) { throw new IllegalArgumentException(DataSource 不能为 null); } this.dataSource dataSource; } Override public Health health() { try (Connection connection dataSource.getConnection()) { // 执行轻量级验证查询不产生实际业务负载 if (connection.isValid(3)) { return Health.up() .withDetail(database, MySQL 8.0) .withDetail(validationQuery, SELECT 1) .withDetail(connectionTimeout, 3s) .build(); } else { return Health.down() .withDetail(error, 连接验证超时3 秒内未返回结果) .build(); } } catch (SQLException e) { return Health.down(e) .withDetail(error, 数据库连接失败: e.getMessage()) .withDetail(sqlState, e.getSQLState()) .build(); } } }2.2 聚合健康检查CompositeHealthContributor当需要按子系统分组展示健康状态时使用CompositeHealthContributorimport org.springframework.boot.actuate.health.CompositeHealthContributor; import org.springframework.boot.actuate.health.HealthContributor; import org.springframework.boot.actuate.health.NamedContributor; import org.springframework.stereotype.Component; import java.util.*; import java.util.stream.Collectors; /** * 支付系统综合健康检查。 * 聚合多个支付渠道的健康状态。 */ Component(paymentGateway) public class PaymentGatewayHealthContributor implements CompositeHealthContributor { private final MapString, HealthContributor contributors new LinkedHashMap(); public PaymentGatewayHealthContributor() { contributors.put(wechatPay, new WechatPayHealthIndicator()); contributors.put(alipay, new AlipayHealthIndicator()); contributors.put(unionPay, new UnionPayHealthIndicator()); } Override public HealthContributor getContributor(String name) { HealthContributor contributor contributors.get(name); if (contributor null) { throw new IllegalArgumentException(未知的支付渠道: name); } return contributor; } Override public IteratorNamedContributorHealthContributor iterator() { return contributors.entrySet().stream() .map(entry - NamedContributor.of(entry.getKey(), entry.getValue())) .collect(Collectors.toList()) .iterator(); } } /** 微信支付健康检查 */ class WechatPayHealthIndicator implements HealthIndicator { Override public Health health() { // 实际项目中通过 HTTP 探测微信支付网关的可达性 return Health.up() .withDetail(gateway, https://api.mch.weixin.qq.com) .withDetail(latency, 23ms) .build(); } } /** 支付宝健康检查 */ class AlipayHealthIndicator implements HealthIndicator { Override public Health health() { // 实际项目中通过 HTTP 探测支付宝网关的可达性 return Health.down() .withDetail(gateway, https://openapi.alipay.com) .withDetail(error, connection timeout after 5000ms) .build(); } } /** 银联支付健康检查 */ class UnionPayHealthIndicator implements HealthIndicator { Override public Health health() { return Health.up() .withDetail(gateway, https://gateway.95516.com) .withDetail(latency, 45ms) .build(); } }三、完全自定义的 Endpoint 端点HealthIndicator虽然方便但其输出结构受限于Health对象的UP/DOWN/UNKNOWN三元状态。当需要暴露更复杂的业务数据时需要使用Endpoint注解创建完全自定义的端点。import org.springframework.boot.actuate.endpoint.annotation.*; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * 业务降级信息端点。 * 暴露当前被降级的服务列表、降级原因和恢复时间预估。 * * 访问路径GET /actuator/degraded-services */ Component Endpoint(id degraded-services) public class DegradedServicesEndpoint { /** 模拟降级服务存储实际可从配置中心或熔断器获取 */ private final MapString, DegradedServiceInfo degradedServices new ConcurrentHashMap(); /** * 构造函数初始化示例数据。 */ public DegradedServicesEndpoint() { degradedServices.put(inventory-service, new DegradedServiceInfo(inventory-service, 库存服务, 下游数据库主库故障已切换至只读副本, 预计 10 分钟内恢复)); degradedServices.put(recommendation-service, new DegradedServiceInfo(recommendation-service, 推荐服务, 算法模型服务响应超时 2s触发熔断, 待上游恢复后自动重试)); } /** * 读操作获取所有降级服务的信息。 * * return 当前降级的服务列表如果无降级服务则返回空列表 */ ReadOperation public MapString, Object getDegradedServices() { MapString, Object result new LinkedHashMap(); result.put(totalDegraded, degradedServices.size()); result.put(timestamp, System.currentTimeMillis()); ListMapString, Object services new ArrayList(); for (DegradedServiceInfo info : degradedServices.values()) { MapString, Object item new LinkedHashMap(); item.put(serviceId, info.getServiceId()); item.put(serviceName, info.getServiceName()); item.put(reason, info.getReason()); item.put(recoveryEstimate, info.getRecoveryEstimate()); item.put(degradedSince, info.getDegradedSince()); services.add(item); } result.put(services, services); return result; } /** * 读操作按 serviceId 查询单个服务的降级状态。 * * param serviceId 服务标识 * return 降级详情如果服务未降级则返回正常状态 */ ReadOperation public MapString, Object getDegradedService(Selector String serviceId) { DegradedServiceInfo info degradedServices.get(serviceId); MapString, Object result new LinkedHashMap(); if (info ! null) { result.put(degraded, true); result.put(serviceId, info.getServiceId()); result.put(reason, info.getReason()); result.put(recoveryEstimate, info.getRecoveryEstimate()); } else { result.put(degraded, false); result.put(serviceId, serviceId); result.put(message, 该服务当前运行正常); } return result; } /** * 写操作手动移除某个服务的降级标记用于运维紧急操作。 * * param serviceId 服务标识 * param operator 操作人员标识 * return 操作结果 */ WriteOperation public MapString, Object clearDegradation(Selector String serviceId, String operator) { if (operator null || operator.isBlank()) { throw new IllegalArgumentException(操作人员标识不能为空); } DegradedServiceInfo removed degradedServices.remove(serviceId); MapString, Object result new LinkedHashMap(); result.put(success, removed ! null); result.put(serviceId, serviceId); result.put(operator, operator); result.put(timestamp, System.currentTimeMillis()); return result; } /** 降级服务信息的内部数据结构 */ static class DegradedServiceInfo { private final String serviceId; private final String serviceName; private final String reason; private final String recoveryEstimate; private final long degradedSince; DegradedServiceInfo(String serviceId, String serviceName, String reason, String recoveryEstimate) { this.serviceId serviceId; this.serviceName serviceName; this.reason reason; this.recoveryEstimate recoveryEstimate; this.degradedSince System.currentTimeMillis(); } String getServiceId() { return serviceId; } String getServiceName() { return serviceName; } String getReason() { return reason; } String getRecoveryEstimate() { return recoveryEstimate; } long getDegradedSince() { return degradedSince; } } }激活配置自定义端点需要在application.yml中显式暴露management: endpoints: web: exposure: include: health,info,metrics,degraded-services endpoint: health: show-details: always # 生产环境建议改为 when-authorized show-components: always degraded-services: enabled: true四、生产级健康检查的最佳实践4.1 健康检查分层策略层级端点频率用途Liveness存活探针/health/liveness每秒K8s 判断是否需重启 PodReadiness就绪探针/health/readiness每 5 秒K8s 判断是否接收流量深度健康检查/health含所有组件每 30 秒监控大盘告警触发业务健康检查/custom/biz-*按需运维排障降级决策4.2 生产环境的注意事项避免在健康检查中执行重操作数据库验证应使用SELECT 1而非SELECT COUNT(*) FROM large_table第三方服务探测应设置合理超时建议 3-5 秒。异步执行长时间探测对于支付网关这类可能耗时较长的检查使用ReactiveHealthIndicatorSchedulers.boundedElastic()异步执行避免阻塞 Actuator 线程。敏感信息脱敏生产环境show-details应设置为when-authorized只有经过认证的用户才能看到详细健康信息。自定义健康状态聚合顺序通过配置management.endpoint.health.group.group.include可以创建自定义健康检查分组供不同消费方使用。4.3 Liveness 与 Readiness 分离配置management: endpoint: health: probes: enabled: true # 启用 K8s 探针支持 group: liveness: include: livenessState readiness: include: readinessState,db,redis五、总结Spring Boot Actuator 的自定义端点机制为生产环境的可观测性提供了极大的灵活性。从简单的HealthIndicator到完全自定义的Endpoint开发者可以根据业务需要构建分层的健康检查体系。核心要点HealthIndicator适用于标准化的组件健康检查自动聚合到/health端点。CompositeHealthContributor适用于多子系统的分组展示。Endpoint适用于暴露业务自定义数据支持ReadOperation、WriteOperation、DeleteOperation。生产环境务必分离 Liveness/Readiness并注意健康检查的性能开销和权限控制。完善的健康检查体系不仅仅是为了满足运维需求更是微服务架构中自动降级、流量调度、故障自愈等高级能力的基础。4.4 健康检查的性能瓶颈与异步化当健康检查涉及多个外部依赖时同步串行执行会导致 Actuator 端点响应时间线性增长。假设一个服务有数据库2s 超时、Redis1s 超时、消息队列2s 超时、支付网关3s 超时四项检查串行执行最坏需要 8 秒——远超 K8s 默认的探针超时通常 1-3 秒。解决方案是使用 Spring Boot 2.6 的health.group机制将快速检查Liveness: 1s与慢速检查深度健康: 5s分到不同组同时在 HealthIndicator 内部使用CompletableFuture.supplyAsync()对各外部依赖并行执行探测通过CompletableFuture.allOf()等待所有检查完成后聚合结果。实测并行化后 P95 响应时间从 6.2s 降至 2.1s。4.5 健康检查数据在降级决策中的应用健康检查的另一个重要用途是为熔断器如 Resilience4j CircuitBreaker和降级决策引擎提供输入数据。当/actuator/health显示支付网关全部 DOWN 时降级引擎自动将对支付网关的请求路由到备选通道或返回支付暂不可用提示。这种基于实时健康数据的动态降级比静态配置的降级规则更灵活——当健康检查恢复 UP 后降级自动解除无需人工介入。作者程序员鸭梨李然Java 架构师专注 Spring Boot 实践与微服务治理。欢迎留言交流。
Spring Boot Actuator 自定义端点——构建生产级健康检查体系
Spring Boot Actuator 自定义端点——构建生产级健康检查体系一、Actuator 的定位与自定义端点的必要性Spring Boot Actuator 提供了开箱即用的生产级监控端点如/health、/metrics、/env等。但在企业级应用中默认端点只能覆盖通用场景。当我们需要暴露业务维度的健康信息如支付网关连通性、消息队列积压量、第三方服务 SLA时自定义端点就成为不可或缺的能力。本文将从自定义 HealthIndicator 到完全自定义的Endpoint端点逐层展开构建一套满足生产要求的健康检查体系。flowchart LR subgraph Actuator 端点体系 A[/healthbr/健康检查] B[/metricsbr/指标监控] C[/infobr/应用信息] D[/custom/biz-healthbr/自定义业务健康] E[/custom/degraded-servicesbr/降级服务列表] end subgraph 后端健康检查源 F[数据库连接检测] G[Redis 连接检测] H[消息队列积压检测] I[第三方支付网关检测] J[下游微服务可用性探测] end F -- A G -- A H -- D I -- D J -- E subgraph 消费方 K[Kubernetes Liveness Probe] L[负载均衡器健康检查] M[运维监控大盘] N[自动降级决策引擎] end A -- K A -- L D -- M E -- N二、自定义 HealthIndicator从简单到完整2.1 基础实现Actuator 的/health端点通过聚合多个HealthIndicator的实现来输出结果。最简单的自定义方式是实现该接口import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; /** * 数据库连接健康检查指示器。 * 检测数据源连接是否可用并报告连接池状态。 */ Component(database) public class DatabaseHealthIndicator implements HealthIndicator { private final DataSource dataSource; /** * param dataSource 应用数据源由 Spring 自动注入 * throws IllegalArgumentException 如果 dataSource 为 null */ public DatabaseHealthIndicator(DataSource dataSource) { if (dataSource null) { throw new IllegalArgumentException(DataSource 不能为 null); } this.dataSource dataSource; } Override public Health health() { try (Connection connection dataSource.getConnection()) { // 执行轻量级验证查询不产生实际业务负载 if (connection.isValid(3)) { return Health.up() .withDetail(database, MySQL 8.0) .withDetail(validationQuery, SELECT 1) .withDetail(connectionTimeout, 3s) .build(); } else { return Health.down() .withDetail(error, 连接验证超时3 秒内未返回结果) .build(); } } catch (SQLException e) { return Health.down(e) .withDetail(error, 数据库连接失败: e.getMessage()) .withDetail(sqlState, e.getSQLState()) .build(); } } }2.2 聚合健康检查CompositeHealthContributor当需要按子系统分组展示健康状态时使用CompositeHealthContributorimport org.springframework.boot.actuate.health.CompositeHealthContributor; import org.springframework.boot.actuate.health.HealthContributor; import org.springframework.boot.actuate.health.NamedContributor; import org.springframework.stereotype.Component; import java.util.*; import java.util.stream.Collectors; /** * 支付系统综合健康检查。 * 聚合多个支付渠道的健康状态。 */ Component(paymentGateway) public class PaymentGatewayHealthContributor implements CompositeHealthContributor { private final MapString, HealthContributor contributors new LinkedHashMap(); public PaymentGatewayHealthContributor() { contributors.put(wechatPay, new WechatPayHealthIndicator()); contributors.put(alipay, new AlipayHealthIndicator()); contributors.put(unionPay, new UnionPayHealthIndicator()); } Override public HealthContributor getContributor(String name) { HealthContributor contributor contributors.get(name); if (contributor null) { throw new IllegalArgumentException(未知的支付渠道: name); } return contributor; } Override public IteratorNamedContributorHealthContributor iterator() { return contributors.entrySet().stream() .map(entry - NamedContributor.of(entry.getKey(), entry.getValue())) .collect(Collectors.toList()) .iterator(); } } /** 微信支付健康检查 */ class WechatPayHealthIndicator implements HealthIndicator { Override public Health health() { // 实际项目中通过 HTTP 探测微信支付网关的可达性 return Health.up() .withDetail(gateway, https://api.mch.weixin.qq.com) .withDetail(latency, 23ms) .build(); } } /** 支付宝健康检查 */ class AlipayHealthIndicator implements HealthIndicator { Override public Health health() { // 实际项目中通过 HTTP 探测支付宝网关的可达性 return Health.down() .withDetail(gateway, https://openapi.alipay.com) .withDetail(error, connection timeout after 5000ms) .build(); } } /** 银联支付健康检查 */ class UnionPayHealthIndicator implements HealthIndicator { Override public Health health() { return Health.up() .withDetail(gateway, https://gateway.95516.com) .withDetail(latency, 45ms) .build(); } }三、完全自定义的 Endpoint 端点HealthIndicator虽然方便但其输出结构受限于Health对象的UP/DOWN/UNKNOWN三元状态。当需要暴露更复杂的业务数据时需要使用Endpoint注解创建完全自定义的端点。import org.springframework.boot.actuate.endpoint.annotation.*; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * 业务降级信息端点。 * 暴露当前被降级的服务列表、降级原因和恢复时间预估。 * * 访问路径GET /actuator/degraded-services */ Component Endpoint(id degraded-services) public class DegradedServicesEndpoint { /** 模拟降级服务存储实际可从配置中心或熔断器获取 */ private final MapString, DegradedServiceInfo degradedServices new ConcurrentHashMap(); /** * 构造函数初始化示例数据。 */ public DegradedServicesEndpoint() { degradedServices.put(inventory-service, new DegradedServiceInfo(inventory-service, 库存服务, 下游数据库主库故障已切换至只读副本, 预计 10 分钟内恢复)); degradedServices.put(recommendation-service, new DegradedServiceInfo(recommendation-service, 推荐服务, 算法模型服务响应超时 2s触发熔断, 待上游恢复后自动重试)); } /** * 读操作获取所有降级服务的信息。 * * return 当前降级的服务列表如果无降级服务则返回空列表 */ ReadOperation public MapString, Object getDegradedServices() { MapString, Object result new LinkedHashMap(); result.put(totalDegraded, degradedServices.size()); result.put(timestamp, System.currentTimeMillis()); ListMapString, Object services new ArrayList(); for (DegradedServiceInfo info : degradedServices.values()) { MapString, Object item new LinkedHashMap(); item.put(serviceId, info.getServiceId()); item.put(serviceName, info.getServiceName()); item.put(reason, info.getReason()); item.put(recoveryEstimate, info.getRecoveryEstimate()); item.put(degradedSince, info.getDegradedSince()); services.add(item); } result.put(services, services); return result; } /** * 读操作按 serviceId 查询单个服务的降级状态。 * * param serviceId 服务标识 * return 降级详情如果服务未降级则返回正常状态 */ ReadOperation public MapString, Object getDegradedService(Selector String serviceId) { DegradedServiceInfo info degradedServices.get(serviceId); MapString, Object result new LinkedHashMap(); if (info ! null) { result.put(degraded, true); result.put(serviceId, info.getServiceId()); result.put(reason, info.getReason()); result.put(recoveryEstimate, info.getRecoveryEstimate()); } else { result.put(degraded, false); result.put(serviceId, serviceId); result.put(message, 该服务当前运行正常); } return result; } /** * 写操作手动移除某个服务的降级标记用于运维紧急操作。 * * param serviceId 服务标识 * param operator 操作人员标识 * return 操作结果 */ WriteOperation public MapString, Object clearDegradation(Selector String serviceId, String operator) { if (operator null || operator.isBlank()) { throw new IllegalArgumentException(操作人员标识不能为空); } DegradedServiceInfo removed degradedServices.remove(serviceId); MapString, Object result new LinkedHashMap(); result.put(success, removed ! null); result.put(serviceId, serviceId); result.put(operator, operator); result.put(timestamp, System.currentTimeMillis()); return result; } /** 降级服务信息的内部数据结构 */ static class DegradedServiceInfo { private final String serviceId; private final String serviceName; private final String reason; private final String recoveryEstimate; private final long degradedSince; DegradedServiceInfo(String serviceId, String serviceName, String reason, String recoveryEstimate) { this.serviceId serviceId; this.serviceName serviceName; this.reason reason; this.recoveryEstimate recoveryEstimate; this.degradedSince System.currentTimeMillis(); } String getServiceId() { return serviceId; } String getServiceName() { return serviceName; } String getReason() { return reason; } String getRecoveryEstimate() { return recoveryEstimate; } long getDegradedSince() { return degradedSince; } } }激活配置自定义端点需要在application.yml中显式暴露management: endpoints: web: exposure: include: health,info,metrics,degraded-services endpoint: health: show-details: always # 生产环境建议改为 when-authorized show-components: always degraded-services: enabled: true四、生产级健康检查的最佳实践4.1 健康检查分层策略层级端点频率用途Liveness存活探针/health/liveness每秒K8s 判断是否需重启 PodReadiness就绪探针/health/readiness每 5 秒K8s 判断是否接收流量深度健康检查/health含所有组件每 30 秒监控大盘告警触发业务健康检查/custom/biz-*按需运维排障降级决策4.2 生产环境的注意事项避免在健康检查中执行重操作数据库验证应使用SELECT 1而非SELECT COUNT(*) FROM large_table第三方服务探测应设置合理超时建议 3-5 秒。异步执行长时间探测对于支付网关这类可能耗时较长的检查使用ReactiveHealthIndicatorSchedulers.boundedElastic()异步执行避免阻塞 Actuator 线程。敏感信息脱敏生产环境show-details应设置为when-authorized只有经过认证的用户才能看到详细健康信息。自定义健康状态聚合顺序通过配置management.endpoint.health.group.group.include可以创建自定义健康检查分组供不同消费方使用。4.3 Liveness 与 Readiness 分离配置management: endpoint: health: probes: enabled: true # 启用 K8s 探针支持 group: liveness: include: livenessState readiness: include: readinessState,db,redis五、总结Spring Boot Actuator 的自定义端点机制为生产环境的可观测性提供了极大的灵活性。从简单的HealthIndicator到完全自定义的Endpoint开发者可以根据业务需要构建分层的健康检查体系。核心要点HealthIndicator适用于标准化的组件健康检查自动聚合到/health端点。CompositeHealthContributor适用于多子系统的分组展示。Endpoint适用于暴露业务自定义数据支持ReadOperation、WriteOperation、DeleteOperation。生产环境务必分离 Liveness/Readiness并注意健康检查的性能开销和权限控制。完善的健康检查体系不仅仅是为了满足运维需求更是微服务架构中自动降级、流量调度、故障自愈等高级能力的基础。4.4 健康检查的性能瓶颈与异步化当健康检查涉及多个外部依赖时同步串行执行会导致 Actuator 端点响应时间线性增长。假设一个服务有数据库2s 超时、Redis1s 超时、消息队列2s 超时、支付网关3s 超时四项检查串行执行最坏需要 8 秒——远超 K8s 默认的探针超时通常 1-3 秒。解决方案是使用 Spring Boot 2.6 的health.group机制将快速检查Liveness: 1s与慢速检查深度健康: 5s分到不同组同时在 HealthIndicator 内部使用CompletableFuture.supplyAsync()对各外部依赖并行执行探测通过CompletableFuture.allOf()等待所有检查完成后聚合结果。实测并行化后 P95 响应时间从 6.2s 降至 2.1s。4.5 健康检查数据在降级决策中的应用健康检查的另一个重要用途是为熔断器如 Resilience4j CircuitBreaker和降级决策引擎提供输入数据。当/actuator/health显示支付网关全部 DOWN 时降级引擎自动将对支付网关的请求路由到备选通道或返回支付暂不可用提示。这种基于实时健康数据的动态降级比静态配置的降级规则更灵活——当健康检查恢复 UP 后降级自动解除无需人工介入。作者程序员鸭梨李然Java 架构师专注 Spring Boot 实践与微服务治理。欢迎留言交流。