SpringBoot健康检查实现原理

29次阅读

共计 10325 个字符,预计需要花费 26 分钟才能阅读完成。

相信看完之前文章的同学都知道了 SpringBoot 自动装配的套路了,直接看 spring.factories 文件,当我们使用的时候只需要引入如下依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

然后在 org.springframework.boot.spring-boot-actuator-autoconfigure 包下去就可以找到这个文件

自动装配

查看这个文件发现引入了很多的配置类,这里先关注一下 XXXHealthIndicatorAutoConfiguration 系列的类,这里咱们拿第一个 RabbitHealthIndicatorAutoConfiguration 为例来解析一下。看名字就知道这个是 RabbitMQ 的健康检查的自动配置类

@Configuration
@ConditionalOnClass(RabbitTemplate.class)
@ConditionalOnBean(RabbitTemplate.class)
@ConditionalOnEnabledHealthIndicator("rabbit")
@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class)
@AutoConfigureAfter(RabbitAutoConfiguration.class)
public class RabbitHealthIndicatorAutoConfiguration extends
        CompositeHealthIndicatorConfiguration<RabbitHealthIndicator, RabbitTemplate> {

    private final Map<String, RabbitTemplate> rabbitTemplates;

    public RabbitHealthIndicatorAutoConfiguration(Map<String, RabbitTemplate> rabbitTemplates) {this.rabbitTemplates = rabbitTemplates;}

    @Bean
    @ConditionalOnMissingBean(name = "rabbitHealthIndicator")
    public HealthIndicator rabbitHealthIndicator() {return createHealthIndicator(this.rabbitTemplates);
    }
}

按照以往的惯例,先解析注解

  1. @ConditionalOnXXX系列又出现了,前两个就是说如果当前存在 RabbitTemplate 这个 bean 也就是说我们的项目中使用到了 RabbitMQ 才能进行下去
  2. @ConditionalOnEnabledHealthIndicator这个注解很明显是 SpringBoot actuator 自定义的注解,看一下吧
@Conditional(OnEnabledHealthIndicatorCondition.class)
public @interface ConditionalOnEnabledHealthIndicator {String value();
}
class OnEnabledHealthIndicatorCondition extends OnEndpointElementCondition {OnEnabledHealthIndicatorCondition() {super("management.health.", ConditionalOnEnabledHealthIndicator.class);
    }

}
public abstract class OnEndpointElementCondition extends SpringBootCondition {

    private final String prefix;

    private final Class<? extends Annotation> annotationType;

    protected OnEndpointElementCondition(String prefix,
            Class<? extends Annotation> annotationType) {
        this.prefix = prefix;
        this.annotationType = annotationType;
    }

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        AnnotationAttributes annotationAttributes = AnnotationAttributes
                .fromMap(metadata.getAnnotationAttributes(this.annotationType.getName()));
        String endpointName = annotationAttributes.getString("value");
        ConditionOutcome outcome = getEndpointOutcome(context, endpointName);
        if (outcome != null) {return outcome;}
        return getDefaultEndpointsOutcome(context);
    }

    protected ConditionOutcome getEndpointOutcome(ConditionContext context,
            String endpointName) {Environment environment = context.getEnvironment();
        String enabledProperty = this.prefix + endpointName + ".enabled";
        if (environment.containsProperty(enabledProperty)) {boolean match = environment.getProperty(enabledProperty, Boolean.class, true);
            return new ConditionOutcome(match,
                    ConditionMessage.forCondition(this.annotationType).because(this.prefix + endpointName + ".enabled is" + match));
        }
        return null;
    }

    protected ConditionOutcome getDefaultEndpointsOutcome(ConditionContext context) {boolean match = Boolean.valueOf(context.getEnvironment()
                .getProperty(this.prefix + "defaults.enabled", "true"));
        return new ConditionOutcome(match,
                ConditionMessage.forCondition(this.annotationType).because(this.prefix + "defaults.enabled is considered" + match));
    }

}
public abstract class SpringBootCondition implements Condition {private final Log logger = LogFactory.getLog(getClass());

    @Override
    public final boolean matches(ConditionContext context,
            AnnotatedTypeMetadata metadata) {String classOrMethodName = getClassOrMethodName(metadata);
        try {ConditionOutcome outcome = getMatchOutcome(context, metadata);
            logOutcome(classOrMethodName, outcome);
            recordEvaluation(context, classOrMethodName, outcome);
            return outcome.isMatch();}
        catch (NoClassDefFoundError ex) {
            throw new IllegalStateException(
                    "Could not evaluate condition on" + classOrMethodName + "due to"
                            + ex.getMessage() + "not"
                            + "found. Make sure your own configuration does not rely on"
                            + "that class. This can also happen if you are"
                            + "@ComponentScanning a springframework package (e.g. if you"
                            + "put a @ComponentScan in the default package by mistake)",
                    ex);
        }
        catch (RuntimeException ex) {
            throw new IllegalStateException("Error processing condition on" + getName(metadata), ex);
        }
    }
    private void recordEvaluation(ConditionContext context, String classOrMethodName,
            ConditionOutcome outcome) {if (context.getBeanFactory() != null) {ConditionEvaluationReport.get(context.getBeanFactory())
                    .recordConditionEvaluation(classOrMethodName, this, outcome);
        }
    }
}

上方的入口方法是 SpringBootCondition 类的 matches 方法,getMatchOutcome 这个方法则是子类 OnEndpointElementCondition 的,这个方法首先会去环境变量中查找是否存在 management.health.rabbit.enabled 属性,如果没有的话则去查找 management.health.defaults.enabled 属性,如果这个属性还没有的话则设置默认值为 true

当这里返回 true 时整个 RabbitHealthIndicatorAutoConfiguration 类的自动配置才能继续下去

  1. @AutoConfigureBefore既然这样那就先看看类 HealthIndicatorAutoConfiguration 都是干了啥再回来吧
@Configuration
@EnableConfigurationProperties({HealthIndicatorProperties.class})
public class HealthIndicatorAutoConfiguration {

    private final HealthIndicatorProperties properties;

    public HealthIndicatorAutoConfiguration(HealthIndicatorProperties properties) {this.properties = properties;}

    @Bean
    @ConditionalOnMissingBean({HealthIndicator.class, ReactiveHealthIndicator.class})
    public ApplicationHealthIndicator applicationHealthIndicator() {return new ApplicationHealthIndicator();
    }

    @Bean
    @ConditionalOnMissingBean(HealthAggregator.class)
    public OrderedHealthAggregator healthAggregator() {OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator();
        if (this.properties.getOrder() != null) {healthAggregator.setStatusOrder(this.properties.getOrder());
        }
        return healthAggregator;
    }

}

首先这个类引入了配置文件 HealthIndicatorProperties 这个配置类是系统状态相关的配置

@ConfigurationProperties(prefix = "management.health.status")
public class HealthIndicatorProperties {

    private List<String> order = null;

    private final Map<String, Integer> httpMapping = new HashMap<>();}

接着就是注册了 2 个 beanApplicationHealthIndicatorOrderedHealthAggregator
这两个 bean 的作用稍后再说,现在回到 RabbitHealthIndicatorAutoConfiguration

  1. @AutoConfigureAfter这个对整体逻辑没影响,暂且不提
  2. 类中注册了一个 beanHealthIndicator这个 bean 的创建逻辑是在父类中的
public abstract class CompositeHealthIndicatorConfiguration<H extends HealthIndicator, S> {

    @Autowired
    private HealthAggregator healthAggregator;

    protected HealthIndicator createHealthIndicator(Map<String, S> beans) {if (beans.size() == 1) {return createHealthIndicator(beans.values().iterator().next());
        }
        CompositeHealthIndicator composite = new CompositeHealthIndicator(this.healthAggregator);
        for (Map.Entry<String, S> entry : beans.entrySet()) {composite.addHealthIndicator(entry.getKey(),
                    createHealthIndicator(entry.getValue()));
        }
        return composite;
    }

    @SuppressWarnings("unchecked")
    protected H createHealthIndicator(S source) {Class<?>[] generics = ResolvableType
                .forClass(CompositeHealthIndicatorConfiguration.class, getClass())
                .resolveGenerics();
        Class<H> indicatorClass = (Class<H>) generics[0];
        Class<S> sourceClass = (Class<S>) generics[1];
        try {return indicatorClass.getConstructor(sourceClass).newInstance(source);
        }
        catch (Exception ex) {
            throw new IllegalStateException("Unable to create indicator" + indicatorClass
                    + "for source" + sourceClass, ex);
        }
    }

}
  1. 首先这里注入了一个对象HealthAggregator,这个对象就是刚才注册的OrderedHealthAggregator
  2. 第一个 createHealthIndicator 方法执行逻辑为:如果传入的 beans 的 size 为 1, 则调用 createHealthIndicator 创建HealthIndicator

否则创建 CompositeHealthIndicator, 遍历传入的 beans, 依次创建HealthIndicator, 加入到CompositeHealthIndicator

  1. 第二个 createHealthIndicator 的执行逻辑为:获得 CompositeHealthIndicatorConfiguration 中的泛型参数根据泛型参数 H 对应的 class 和 S 对应的 class, 在 H 对应的 class 中找到声明了参数为 S 类型的构造器进行实例化
  2. 最后这里创建出来的 bean 为RabbitHealthIndicator
  3. 回忆起之前学习健康检查的使用时,如果我们需要自定义健康检查项时一般的操作都是实现 HealthIndicator 接口,由此可以猜测 RabbitHealthIndicator 应该也是这样做的。观察这个类的继承关系可以发现这个类继承了一个实现实现此接口的类AbstractHealthIndicator,而 RabbitMQ 的监控检查流程则如下代码所示
    // 这个方法是 AbstractHealthIndicator 的
public final Health health() {Health.Builder builder = new Health.Builder();
        try {doHealthCheck(builder);
        }
        catch (Exception ex) {if (this.logger.isWarnEnabled()) {String message = this.healthCheckFailedMessage.apply(ex);
                this.logger.warn(StringUtils.hasText(message) ? message : DEFAULT_MESSAGE,
                        ex);
            }
            builder.down(ex);
        }
        return builder.build();}
// 下方两个方法是由类 RabbitHealthIndicator 实现的
protected void doHealthCheck(Health.Builder builder) throws Exception {builder.up().withDetail("version", getVersion());
    }

    private String getVersion() {return this.rabbitTemplate.execute((channel) -> channel.getConnection()
                .getServerProperties().get("version").toString());
    }
健康检查

上方一系列的操作之后,其实就是搞出了一个 RabbitMQ 的 HealthIndicator 实现类,而负责检查 RabbitMQ 健康不健康也是这个类来负责的。由此我们可以想象到如果当前环境存在 MySQL、Redis、ES 等情况应该也是这么个操作

那么接下来无非就是当有调用方访问如下地址时,分别调用整个系统的所有的 HealthIndicator 的实现类的 health 方法即可了

http://ip:port/actuator/health
HealthEndpointAutoConfiguration

上边说的这个操作过程就在类 HealthEndpointAutoConfiguration 中,这个配置类同样也是在 spring.factories 文件中引入的

@Configuration
@EnableConfigurationProperties({HealthEndpointProperties.class, HealthIndicatorProperties.class})
@AutoConfigureAfter({HealthIndicatorAutoConfiguration.class})
@Import({HealthEndpointConfiguration.class, HealthEndpointWebExtensionConfiguration.class})
public class HealthEndpointAutoConfiguration {public HealthEndpointAutoConfiguration() {}}

这里重点的地方在于引入的 HealthEndpointConfiguration 这个类

@Configuration
class HealthEndpointConfiguration {

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnEnabledEndpoint
    public HealthEndpoint healthEndpoint(ApplicationContext applicationContext) {return new HealthEndpoint(HealthIndicatorBeansComposite.get(applicationContext));
    }

}

这个类只是构建了一个类HealthEndpoint,这个类我们可以理解为一个 SpringMVC 的 Controller,也就是处理如下请求的

http://ip:port/actuator/health

那么首先看一下它的构造方法传入的是个啥对象吧

public static HealthIndicator get(ApplicationContext applicationContext) {HealthAggregator healthAggregator = getHealthAggregator(applicationContext);
        Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
        indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
        if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {new ReactiveHealthIndicators().get(applicationContext)
                    .forEach(indicators::putIfAbsent);
        }
        CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory();
        return factory.createHealthIndicator(healthAggregator, indicators);
    }

跟我们想象中的一样,就是通过 Spring 容器获取所有的 HealthIndicator 接口的实现类,我这里只有几个默认的和 RabbitMQ 的

然后都放入了其中一个聚合的实现类 CompositeHealthIndicator

既然 HealthEndpoint 构建好了,那么只剩下最后一步处理请求了

@Endpoint(id = "health")
public class HealthEndpoint {

    private final HealthIndicator healthIndicator;

    @ReadOperation
    public Health health() {return this.healthIndicator.health();
    }

}

刚刚我们知道,这个类是通过 CompositeHealthIndicator 构建的,所以 health 方法的实现就在这个类中

public Health health() {Map<String, Health> healths = new LinkedHashMap<>();
        for (Map.Entry<String, HealthIndicator> entry : this.indicators.entrySet()) {
          // 循环调用
            healths.put(entry.getKey(), entry.getValue().health());
        }
        // 对结果集排序
        return this.healthAggregator.aggregate(healths);
    }

至此 SpringBoot 的健康检查实现原理全部解析完成

正文完
 0