基于Spring Health指标实现Prometheus应用可用性SLI(禁用UP指标)
基于Spring Health指标实现5分钟维度的服务可用性SLI方案
问题背景
需求明确:
- 基于Prometheus实现5分钟维度的应用/服务可用性SLI
- 禁止使用Prometheus原生
up指标,必须采用Spring Health暴露的healthgauge指标(1=可用,0=不可用) - 可用性结果仅返回0(所有实例故障)或1(至少一个实例正常)
- 业务限制禁止使用任何探针
当前使用的PromQL查询sum by (application) (avg_over_time(health{application="My Service"}[5m]))会返回实例数(如2个正常实例返回2),不符合需求。
解决方案
正确的PromQL查询
根据需求,我们需要判断5分钟窗口内是否至少有一个实例处于可用状态,以下两种PromQL均可实现:
- 基于实例的最大状态值
max by (application) (max_over_time(health{application="My Service"}[5m]))
- 逻辑:对每个实例取5分钟内
health指标的最大值(只要实例在这段时间内有过可用状态,最大值即为1),再对服务维度取最大值——只要有一个实例的最大值是1,最终结果就是1;所有实例最大值为0时结果为0。
- 基于条件匹配的存在性检查
max by (application) (any_over_time(health{application="My Service"} == 1 [5m]))
- 逻辑:
any_over_time(health == 1 [5m])会对每个实例返回1(如果5分钟内有任何一次health为1)或0(全程不可用),再对服务维度取最大值,达到“只要有一个实例正常就返回1”的效果。
这两种查询都能严格输出0或1的结果,符合需求。
代码优化建议
当前的指标暴露代码每次采集health指标时都会调用healthEndpoint.health(),而该方法会触发全量健康检查(可能涉及数据库、外部服务等远程调用),频繁执行会增加系统负载。建议优化为监听健康状态变化事件,减少不必要的健康检查执行:
@Configuration(proxyBeanMethods = false) public class MyHealthMetricsExportConfiguration { private final AtomicInteger healthStatus = new AtomicInteger(0); public MyHealthMetricsExportConfiguration(MeterRegistry registry, ApplicationContext context) { // 监听Spring健康状态变化事件,实时更新状态 context.addApplicationListener((ApplicationListener<HealthChangedEvent>) event -> { Status status = event.getHealth().getStatus(); healthStatus.set(Status.UP.equals(status) ? 1 : 0); }); // 初始化当前健康状态 HealthEndpoint healthEndpoint = context.getBean(HealthEndpoint.class); Status initialStatus = healthEndpoint.health().getStatus(); healthStatus.set(Status.UP.equals(initialStatus) ? 1 : 0); // 注册Gauge指标,直接读取缓存的状态值 Gauge.builder("health", healthStatus, AtomicInteger::get) .strongReference(true) .register(registry); } }
- 优势:仅在健康状态发生变化时执行健康检查,而非每次指标采集都执行,大幅降低系统开销。
- 注意:确保Spring Boot版本支持
HealthChangedEvent(Spring Boot 2.2+版本已提供该事件)。
内容的提问来源于stack exchange,提问作者Goku21
相关产品推荐
相关产品推荐

