1. 程式人生 > 其它 >SpringBoot健康檢查實現原理

SpringBoot健康檢查實現原理

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
  3. 第二個createHealthIndicator的執行邏輯為:獲得CompositeHealthIndicatorConfiguration中的泛型引數根據泛型引數H對應的class和S對應的class,在H對應的class中找到聲明瞭引數為S型別的構造器進行例項化
  4. 最後這裡創建出來的bean為RabbitHealthIndicator
  5. 回憶起之前學習健康檢查的使用時,如果我們需要自定義健康檢查項時一般的操作都是實現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的健康檢查實現原理全部解析完成