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个beanApplicationHealthIndicator
和OrderedHealthAggregator
这两个bean的作用稍后再说,现在回到RabbitHealthIndicatorAutoConfiguration
类
@AutoConfigureAfter
这个对整体逻辑没影响,暂且不提- 类中注册了一个bean
HealthIndicator
这个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);
}
}
}
- 首先这里注入了一个对象
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 Actuator健康检查
通过情况下,如我们想在系统中添加一个健康检查的接口,我们怎么做呢? 我们会新建一个类,或在已存在类的基础上添加检测接口. package com.crhms.medicareopinion; impo ...
- SpringBoot之旅 -- SpringBoot 项目健康检查与监控
前言 You build it,You run it, 当我们编写的项目上线后,为了能第一时间知晓该项目是否出现问题,常常对项目进行健康检查及一些指标进行监控. Spring Boot-Actuato ...
- keepalive的工作原理和如何做到健康检查
keepalived是以VRRP协议为实现基础的,VRRP全称Virtual Router Redundancy Protocol,即虚拟路由冗余协议. 虚拟路由冗余协议,可以认为是实现路由器高可用的 ...
- SpringBoot 项目健康检查与监控(转)
前言 You build it,You run it, 当我们编写的项目上线后,为了能第一时间知晓该项目是否出现问题,常常对项目进行健康检查及一些指标进行监控.Spring Boot-Actuator ...
- Springboot监控之一:SpringBoot四大神器之Actuator之2--覆盖修改spring cloud的默认的consul健康检查规则
微服务网关是socket长连接与支付公司对接,该网关需要提供http接口给内部系统调用,当socket没有建立连接时(网关服务的高可用是haProxy搭建的,有些服务的socket可能未连上支付公司) ...
- Springboot监控之一:SpringBoot四大神器之Actuator之2--springboot健康检查
Health 信息是从 ApplicationContext 中所有的 HealthIndicator 的 Bean 中收集的, Spring Boot 内置了一些 HealthIndicator. ...
- K8s-Pod健康检查原理与实践
Pod健康检查介绍 默认情况下,kubelet根据容器运行状态作为健康依据,不能监视容器中应用程序状态,例如程序假死.这将会导致无法提供服务,丢失流量.因此重新健康检查机制确保容器健康幸存.Pod通过 ...
- Oracle SQL 调优健康检查脚本
Oracle SQL 调优健康检查脚本 我们关注数据库系统的性能,进行数据库调优的主要工作就是进行SQL的优化.良好的数据架构设计.配合应用系统中间件和写一手漂亮的SQL,是未来系统上线后不出现致命性 ...
- [转]Eureka自我保护机制、健康检查的作用、actuator模块监控
Eureka自我保护机制 接着以上篇文章建立的三个工程为基础(eureka-server,uerreg,myweb),默认Eureka是开启自我保护的.我们来做个测试,我们先启动三个工程,我们访问注册 ...
随机推荐
- 快速、优雅的前端IDE之H-builder-X
为什么介绍的是HBuidler-X而不是Hbuilder HX是全新的一个软件,它抛弃了eclipse架构,使用C++为基础架构.HX目前还不能完全替代HBuilder.但在markdown记事的 ...
- Bert实战---情感分类
1.情感分析语料预处理 使用酒店评论语料,正面评论和负面评论各5000条,用BERT参数这么大的模型, 训练会产生严重过拟合,,泛化能力差的情况, 这也是我们下面需要解决的问题; 2.sigmoid二 ...
- Rust中的Slices
这个slice切片,python中有,go中有, 但确实,Rust中最严格. 精彩见如下URL: Rust 程序设计语言(第二版) 简体中文版 · GitBook (Legacy) https://k ...
- 构建n位元的格雷码
二进制格雷码的生成 1.什么是格雷码 Gray Code是一个数列集合,每个数使用二进制来表示,假设使用n位元来表示每个数字,那么任两个数之间只有一个位元值不同.log2(16)=4 例如: 生成4位 ...
- Maven 中 dependencyManagement 元素,知识点
Maven 提供的 dependencyManagement 元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性.在 dependencyManagement 元素下的依赖声明不会 ...
- loadrunner 集合点设置2
1.集合点的含义 当通过controller虚拟多个用户执行该脚本时.用户的启动或运行步骤不一定都是同步的,集合点是在脚本的某处设置一个标记.当有虚拟用户运行到这个标记处时,停下等待,直到 ...
- 关于几类STL容器swap的复杂度问题
\(swap\)的方式有 \(S1.swap(S2)\) 或 \(swap(S1,S2)\) \(vector,map,set,deque \ \ \ \ swap\)复杂度:\(O(1)\) \(p ...
- 分析并解决Linux发行版的自带OpenJdk和自己安装的OracleJdk新旧版本冲突问题
解决办法: 从Oraclejdk 目录里可执行文件链接都复制到自己的LINK目录,然后IDE使用LINK变量下的命令 本文没有具体解决方法,只有探索思路........................ ...
- oracle--10GRAC集群(NFS共享存储)
一,NFS服务器配置 01, 安装包查看 [root@standby2 ~]# rpm -qa|grep nfs nfs-utils-lib--.el6.x86_64 nfs4-acl-tools-- ...
- 你还在为了JVM而烦恼么?(内存结构和垃圾回收算法)
做JAVA也有接近2年的时间了,公司的leader说,做JAVA,三年是个坎,如果过了三年你还没有去研究JVM的话,那么你这个程序员只能是板砖的工具了.恰逢辞职,来个JVM的解析可好? JVM是J ...