详解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);
 }
}

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

  • @ConditionalOnXXX 系列又出现了,前两个就是说如果当前存在 RabbitTemplate 这个bean也就是说我们的项目中使用到了RabbitMQ才能进行下去
  • @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 类的自动配置才能继续下去

  • @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个bean ApplicationHealthIndicatorOrderedHealthAggregator 这两个bean的作用稍后再说,现在回到 RabbitHealthIndicatorAutoConfiguration 类

@AutoConfigureAfter
HealthIndicator
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);
  }
 }

}
  • 首先这里注入了一个对象 HealthAggregator ,这个对象就是刚才注册的 OrderedHealthAggregator
  • 第一个 createHealthIndicator 方法执行逻辑为:如果传入的beans的size 为1,则调用 createHealthIndicator 创建 HealthIndicator 否则创建 CompositeHealthIndicator ,遍历传入的beans,依次创建 HealthIndicator ,加入到 CompositeHealthIndicator
  • 第二个 createHealthIndicator 的执行逻辑为:获得 CompositeHealthIndicatorConfiguration 中的泛型参数根据泛型参数H对应的class和S对应的class,在H对应的class中找到声明了参数为S类型的构造器进行实例化
  • 最后这里创建出来的bean为 RabbitHealthIndicator
  • 回忆起之前学习健康检查的使用时,如果我们需要自定义健康检查项时一般的操作都是实现 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的健康检查实现原理全部解析完成

以上就是详解SpringBoot健康检查的实现原理的详细内容,更多关于SpringBoot健康检查实现原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • 如何自定义redis工具jar包供其他SpringBoot项目直接使用

    注:(最终redis数据库连接信息由使用者项目模块配置提供) 一.Redis常用存储操作实现(redis-util模块,该module最后会打包成jar供其他服务使用) 1.引用相关依赖 <!-- 如果有继承父级spring-boot-starter-parent,可不用添加版本号 --> <!-- Redis缓存 [start] --> <dependency> <groupId>org.springframework.boot</groupId&g

  • springboot整合mybatis-plus实现多表分页查询的示例代码

    1.新建一个springboot工程 2.需要导入mybatis和mybatis-plus的依赖文件 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.1</version> </dependency> <dependency> &l

  • 在WINXP下建立VPN服务器的方法

    最近想在WinXP上创建一个VPN服务器,在网上找了找,找到了相关资料,转来贴之. 利用WINXP不需要第三方软件,可以直接构建一个VPN服务器,如果没有采用SOFTETHER,虚拟HUB的必要的话,要实现VPN,可以直接用WINXP来实现.     接下来,我们以WINXP操作系统,宽带连接来构建一个虚拟专用网络. 第一步: VPN服务器的建立 右单击,网上邻居,选属性,打开网络连接属性. 在"网络任务"里选择,创建一个新的连接. 打开新建连接向导. 然后,点 下一步. 在 &quo

  • SpringBoot如何实现Tomcat自动配置

    目录 准备工作 我们知道SpringBoot的自动装配的秘密在 org.springframework.boot.autoconfigure 包下的 spring.factories 文件中,而嵌入Tomcat的原理就在这个文件中加载的一个配置类: org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration @Configuration @AutoConfigureOrde

  • 详解SpringBoot配置连接池

    内置的连接池 目前spring Boot中默认支持的连接池有dbcp,dbcp2, tomcat, hikari三种连接池. 数据库连接可以使用DataSource池进行自动配置. 由于Tomcat数据源连接池的性能和并发,在tomcat可用时,我们总是优先使用它. 如果HikariCP可用,我们将使用它. 如果Commons DBCP可用,我们将使用它,但在生产环境不推荐使用它. 最后,如果Commons DBCP2可用,我们将使用它. 以上的几种连接池,可以通过在配置application文

  • SpringBoot实现项目健康检查与监控

    Spring Boot 最主要的特性就是AutoConfig(自动配置),而对于我们这些使用者来说也就是各种starter, Spring Boot-Actuator 也提供了starter,为我们自动配置,在使用上我们只需要添加starter到我们的依赖中,然后启动项目即可. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-ac

  • 在SpringBoot下读取自定义properties配置文件的方法

    SpringBoot工程默认读取application.properties配置文件.如果需要自定义properties文件,如何读取呢? 一.在resource中新建.properties文件 在resource目录下新建一个config文件夹,然后新建一个.properties文件放在该文件夹下.如图remote.properties所示 二.编写配置文件 remote.uploadFilesUrl=/resource/files/ remote.uploadPicUrl=/resource

  • 详解SpringBoot健康检查的实现原理

    SpringBoot自动装配的套路,直接看 spring.factories 文件,当我们使用的时候只需要引入如下依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 然后在 org.springframework.boot.sprin

  • springboot项目打成war包部署到tomcat遇到的一些问题

    开发环境使用jdk1.8.0_60,把springboot 项目打成war包后, 部署到apache-tomcat-7.0.68时报错如下,换成apache-tomcat-8.0.9解决 org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/my-springboot-web-0.0.1

  • 关于idea中SpringBoot启动失败的坑

    很多时候你新建了Maven 或者SpringBoot 工程,激动的点了主启动类,你就发现了下面的错误 这里说的是啥意思呢,你没有数据库相关的链接,数据库相关的链接在哪里配置呢,就是在你的Resource文件目录下的properties 或者yml文件中 但是这里你可能会说,我**不用数据库,我干啥配这个b玩意,我想说这句话的时候,你已经点了很多遍的主启动都报这个错误, 今天你算来对了,我敢肯定80%的人都遇到过这个问题.但是不知道为啥,今天我给你分析一下昂: ①:在IDEA 中用SpringIn

  • springboot与mybatis整合实例详解(完美融合)

    简介 从 Spring Boot 项目名称中的 Boot 可以看出来,Spring Boot 的作用在于创建和启动新的基于 Spring 框架的项目.它的目的是帮助开发人员很容易的创建出独立运行和产品级别的基于 Spring 框架的应用.Spring Boot 会选择最适合的 Spring 子项目和第三方开源库进行整合.大部分 Spring Boot 应用只需要非常少的配置就可以快速运行起来. Spring Boot 包含的特性如下: 创建可以独立运行的 Spring 应用. 直接嵌入 Tomc

随机推荐