SOFABoot源码解析之Readiness检查

        SOFABoot在SpringBoot的Liveness检查能力的基础上,增加了Readiness检查能力。如果你需要使用SOFA中间件,那么建议使用SOFABoot的Readiness检查能力,来更优雅的上线应用实例。

一、SpringBoot健康检查

        SpringBootActuator帮助我们监控我们的SpringBoot项目。

        SpringBootActuator涉及的内容比较多。在此,为了便于理解SOFABoot的Readiness检查实现原理,我们回顾一下SpringBoot的健康检查相关内容。我们只关注核心的Endpoint、HealthIndicator和Health。

       (一)Endpoint

        Endpoint接口实现类主要用来向其它操作暴露有用的信息。通常情况下,使用SpringMVC对外提供信息服务。当然,也可以使用其它技术,例如JMX,对外提供信息服务。通过继承AbstractEndpoint抽象类,可以实现自己的Endpoint。

        SpringBoot Actuator提供了许多有用的Endpoint,对SpringBoot应用提供各种监控。

        Endpoint相关的类图如下所示:

        常用的EndPoint主要包括:

        1.  /health:应用的健康状态;

        2.  /configprops:获取应用的配置信息,因为Spring Boot 可能发布时是单独的Jar包,配置文件可能包含其中,当我们需要检查配置文件时,可以使用ConfigpropsEndPoint 进行查看一些配置是否正确;

        3.  /trace:最近几次的http请求信息;

        当我们访问 http://localhost:8088/health时,可以看到 HealthEndPoint 给我们提供默认的监控结果,包含 磁盘检测和数据库检测。

1.  {
2.      "status": "UP",
3.      "diskSpace": {
4.          "status": "UP",
5.          "total": 398458875904,
6.          "free": 315106918400,
7.          "threshold": 10485760
8.      },
9.      "db": {
10.         "status": "UP",
11.         "database":"MySQL",
12.         "hello": 1
13.     }
14. }

        (二)Health

        Health类代表了SpringBoot组件或子系统的健康信息。

        Health类使用了Builder模式,其类图如下:


        1.  Health类

        Health类上声明了@JsonInclude(Include.NON_EMPTY)注解,表明如果该类中所有属性如果为空或为NULL,则不序列化。

        Health类声明了如下字段:

1.  // 状态
2.  private final Status status;
3.   
4.  // 详情
5.  private final Map<String, Object>details;

        2.  Status类

        Status类上声明了如下注解:

1.  @JsonInclude(Include.NON_EMPTY)

        如果该类中的所有属性都为空或为 NULL,则不序列化。

        Status类内部定义了如下字段:

1.  // 状态码
2.  private final String code;
3.   
4.  // 描述
5.  private final String description;

        Status类预定义了4个静态字段:

1.  // 系统状态未知
2.  public static final Status UNKNOWN = newStatus("UNKNOWN");
3.   
4.  //服务可用
5.  public static final Status UP = newStatus("UP");
6.   
7.  // 服务挂掉
8.  public static final Status DOWN = newStatus("DOWN");
9.   
10. // 服务不可用
11. public static final Status OUT_OF_SERVICE= new Status("OUT_OF_SERVICE");

        3.  Builder类

        Builder类为内部类,定义在 Health类中,其构造函数:

1.  public Builder() {
2.          this.status = Status.UNKNOWN;
3.          this.details = newLinkedHashMap<String, Object>();
4.  }

        默认情况下,构建出来的Health状态为UNKNOWN。

        通过如下方式使用 Builder 构建Health:

1.  try {
2.      //do some test to determine state of component
3.      returnnew Health.Builder().up().withDetail("version","1.1.2").build();
4.  }catch (Exception ex) {
5.      returnnew Health.Builder().down(ex).build();
6.  }

        (三)HealthIndicator

        EndPoint依赖HealthIndicator接口的实现类提供各种比较有用的健康信息。

        HealthIndicator接口实现类在org.springframework.boot.actuate.health包下,其简化类图如下:

        1.  HealthIndicator

        HealthIndicator 是一个顶层接口,其只声明了1个方法:

1.  public interface HealthIndicator {
2.      // 返回health
3.      Health health();
4.  }

        2.  AbstractHealthIndicator

        AbstractHealthIndicator是一个抽象类,提供了一个模板方法health。

        在health方法中,调用抽象方法doHealthCheck方法。该方法由子类重写,完成实际的健康检查操作。

1.  public final Health health() {
2.          // 1. 实例化Health$Builder
3.          Health.Builder builder = newHealth.Builder();
4.          try {
5.              // 2. 进行状态的检查,如果在检查过程中出现异常,则状态为Status.DOWN
6.              doHealthCheck(builder);
7.          }
8.          catch (Exception ex) {
9.              this.logger.warn("Health checkfailed", ex);
10.             builder.down(ex);
11.         }
12.         // 3. 构建Health
13.         return builder.build();
14.     }

        2.  HealthIndicator实现类

        以ApplicationHealthIndicator和DataSourceHealthIndicator为例,详细描述HealthIndicator的实现细节。

        (1) ApplicationHealthIndicator

        该类继承AbstractHealthIndicator,实现doHealthCheck方法。在该方法中,直接将status设置为UP。具体实现代码如下:

1.      protected void doHealthCheck(Health.Builderbuilder) throws Exception {
2.          builder.up();
3.      }

        (2) DataSourceHealthIndicator

        DataSourceHealthIndicator 中定义了如下字段:

1.  // 默认的测试语句为SELECT 1
2.  private static final String DEFAULT_QUERY= "SELECT 1";
3.   
4.  // 数据库资源
5.  private DataSource dataSource;
6.   
7.  // 测试语句
8.  private String query;
9.   
10. private JdbcTemplate jdbcTemplate;

        DataSourceHealthIndicator 实现了InitializingBean接口,因此在初始化后会调用afterPropertiesSet方法,代码如下:

1.  public void afterPropertiesSet() throwsException {
2.      Assert.state(this.dataSource != null,
3.              "DataSource for DataSourceHealthIndicatormust be specified");
4.  }

        在DataSourceHealthIndicator类中,提供了doHealthCheck 实现:

1.  protected voiddoHealthCheck(Health.Builder builder) throws Exception {
2.      // 1. 如果DataSource没有配置,则直接返回up,message unknown
3.      if (this.dataSource == null) {
4.          builder.up().withDetail("database","unknown");
5.      }
6.      else {
7.          // 2.
8.          doDataSourceHealthCheck(builder);
9.      }
10. }

        doHealthCheck方法的处理逻辑如下:

        1)  如果DataSource没有配置,则直接返回UP,message为UNKNOWN。

        2)  否则,调用doDataSourceHealthCheck,代码如下:

1.  private void doDataSourceHealthCheck(Health.Builderbuilder) throws Exception {
2.       String product = getProduct();
3.       builder.up().withDetail("database",product);
4.       StringvalidationQuery = getValidationQuery(product);
5.       if(StringUtils.hasText(validationQuery)) {
6.           try{
7.               //Avoid calling getObject as it breaks MySQL on Java 7
8.               List<Object>results = this.jdbcTemplate.query(validationQuery,
9.                       newSingleColumnRowMapper());
10.              Objectresult = DataAccessUtils.requiredSingleResult(results);
11.              builder.withDetail("hello",result);
12.          }
13.          catch(Exception ex) {
14.              builder.down(ex);
15.          }
16.      }
17. }

        获得数据库生产商名称,并添加至builder中,代码如下:

1.  private String getProduct() {
2.  return this.jdbcTemplate.execute(newConnectionCallback<String>() {
3.  @Override
4.  public String doInConnection(Connectionconnection)
5.          throws SQLException,DataAccessException {
6.      returnconnection.getMetaData().getDatabaseProductName();
7.  }
8.  });
9.  }

        获得测试用的SQL语句,默认为SELECT 1。获得测试用的SQL查询语句的代码如下:

1.  protected String getValidationQuery(Stringproduct) {
2.       Stringquery = this.query;
3.       if(!StringUtils.hasText(query)) {
4.           DatabaseDriver specific =DatabaseDriver.fromProductName(product);
5.           query= specific.getValidationQuery();
6.       }
7.       if(!StringUtils.hasText(query)) {
8.           query = DEFAULT_QUERY;
9.       }
10.      returnquery;
11. }

        使用测试用的SQL语句进行数据库查询,将查询结果放入builder中,key为hello,value为结果值。

        如果查询操作发生异常,则调用Builder.down 方法,设置状态为DOWN。

        至此,DataSourceHealthIndicator类doHealthCheck处理完成。

二、SOFABoot健康检查

        (一)简述

        了解了SpringBoot健康检查的原理以后,我们再来看看SOFABoot。

        目前,SOFABoot中间件已经通过SOFABoot的Readiness检查能力来控制了上游流量的进入,但是一个应用的流量可能并不是全部都是从中间件进入的,比较常见的还有从负载均衡器进入的,为了控制从负载均衡器进入的流量,建议使用者通过 PAAS 来访问Readiness检查的结果,根据结果来控制是否要在负载均衡器中上线对应的节点。

        看一下SOFABoot的Readiness检查相关的类图:

        与SpringBoot提供的HealthIndicator相似,SOFABoot提供了HealthChecker接口。各SOFABoot中间件,通过实现HealthChecker接口,提供中间件的Readiness检查能力。例如:runtime-sofa-boot-starter提供了SofaComponentHealthChecker类,实现了SOFABoot中所有注册组件的Readiness检查功能。isle-sofa-boot-starter提供了SofaModuleHealthChecker类,实现了SOFABoot模块化开发中,每个模块的的Readiness检查功能。

        1.  HealthChecker

        HealthChecker是一个顶层接口,声明了以下5个方法:

1.  public interface HealthChecker {
2.   
3.      Health isHealthy();
4.   
5.      String getComponentName();
6.   
7.      int getRetryCount();
8.   
9.      long getRetryTimeInterval();
10.  
11.     boolean isStrictCheck();
12. }

        2.  HealthCheckManager

        HealthCheckManager是一个工具类,为SOFABoot应用提供了获取各类健康检查接口实现类的方法,供其它健康检查处理器使用。

        另外,SOFABoot关于Readiness检查功能,提供了如下扩展点:

        (1)扩展 Readiness检查能力

        在Readiness Check的各个阶段,SOFABoot都提供了扩展的能力,应用可以根据自己的需要进行扩展,目前可供扩展的点如下:

        1.  org.springframework.context.ApplicationListener

        如果想要在Readiness检查之前做一些事情,那么监听这个Listener 的 SofaBootBeforeHealthCheckEvent 事件。

        2.  org.springframework.boot.actuate.health.HealthIndicator

        如果想要在SOFABoot的Readiness检查里面增加一个Liveness检查项,那么可以实现HealthIndicator接口。

        3.  com.alipay.sofa.healthcheck.core.HealthChecker

        如果想要在SOFABoot的Readiness检查里面增加一个Readiness检查项,那么可以实现HealthChecker接口。

        4.  com.alipay.sofa.healthcheck.startup.SofaBootAfterReadinessCheckCallback

        如果想要在Readiness Check之后做一些SOFABoot应用级别的事情,例如端口是否可用等,那么可以扩展SOFABoot的这个接口。

        5.  com.alipay.sofa.healthcheck.startup.SofaBootMiddlewareAfterReadinessCheckCallback

        如果想要在Readiness Check之后做一些SOFABoot中间件级别的事情,例如某个Server是否启动成功,那么可以扩展SOFABoot的这个接口。

        (2)Readiness检查配置项

        应用在引入SOFABoot的健康检查扩展之后,可以在SpringBoot的配置文件 application.properties 中添加相关配置项来定制ReadinessCheck的相关行为。

        1.  com.alipay.sofa.healthcheck.skip.all:是否跳过整个Readiness检查阶段默认值:false;

        2.  com.alipay.sofa.healthcheck.skip.component   :是否跳过SOFABoot中间件的 Readiness检查阶段,默认值:false;

        3.  com.alipay.sofa.healthcheck.skip.indicator:是否跳过HealthIndicator的Readiness检查阶段,默认值:false;

        (二)源码解析

        以SOFABoot自带的sofa-boot-sample为例,详细描述Healthcheck原理。

        在此提前说明,源码分析主要分析主流程,以及本人认为比较重要的一些内容,对于其它部分,大家可以基于本文档,自行研读。

        与SpringBoot最主要的特性自动配置AutoConfig一样,SOFABoot也是通过提供Healthcheck的starter,为我们自动配置。

        我们只需要在项目sofa-boot-sample的pom.xml文件中,添加healthcheck-sofa-boot-starter到项目依赖中即可。

1.          <dependency>
2.              <groupId>com.alipay.sofa</groupId>
3.              <artifactId>healthcheck-sofa-boot-starter</artifactId>
4.          </dependency>

        通过SpringApplication类run方法,启动项目:

1.  @SpringBootApplication
2.  public class DemoApplication {
3.      public static void main(String[] args) {
4.          SpringApplication.run(DemoApplication.class,args);
5.      }
6.  }

        本文主要详述Healthcheck相关的内容,SOFABoot的启动原理可以参考《启动原理》,在此不在详述。

        由于SOFABoot 基于SpringBoot的基础上进行构建,所以完全兼容SpringBoot。作为一个遵循SpringBoot规范的Jar包,也是通过自动配置(spring.factories)和HealthcheckInitializer(实现了ApplicationContextInitializer接口)完成Healthcheck的初始化工作和Healthcheck相关Bean的注册和服务的启动。

        healthcheck-sofa-boot-starter模块的spring.factories文件内容如下:

1.  org.springframework.context.ApplicationContextInitializer=\
2.  com.alipay.sofa.healthcheck.initializer.HealthcheckInitializer
3.   
4.  org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
5.  com.alipay.sofa.healthcheck.initializer.HealthcheckInitializer

        在SpringApplication实例初始化的时候,使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer接口实现类,此时会加载HealthcheckInitializer。

        SpringApplication实例初始化完成后,开始执行run方法的逻辑。

        1、Healthcheck初始化

        在调用prepareContext方法准备Spring应用上下文过程中,依次调用刚才加载的ApplicationContextInitializer接口的实现类的initialize方法,完成Spring应用上下文的初始化工作。此时,会调用HealthcheckInitializer的initialize方法,完成Healthcheck相关的初始化工作。

1.  ……
2.  public class HealthcheckInitializerimplements
3.                                     ApplicationContextInitializer<ConfigurableApplicationContext>{
4.   
5.      @Override
6.      public voidinitialize(ConfigurableApplicationContext applicationContext) {
7.   
8.          initializeLog();
9.   
10.         initializeConfiguration(applicationContext);
11.  
12.     }
13.  
14.     private void initializeLog() {
15.         ……
16.     }
17.  
18.     private voidinitializeConfiguration(ConfigurableApplicationContext applicationContext) {
19.         ……
20.     }
21. }

        HealthcheckInitializer的initialize方法主要完成两个任务:

        1.   Healthcheck日志空间初始化。日志空间初始化参考《日志空间隔离》,在此不详述。

        2.   使用Spring应用上下文中的Environment初始化HealthCheckConfiguration的Enviroment属性;

        2、Healthcheck配置类注册

        在调用refreshContext方法刷新Spring应用上下文过程中,在AbstractApplicationContext类refresh方法的invokeBeanFactoryPostProcessors阶段,通过ConfigurationClassPostProcessor类开始处理@Configuration配置类。

        按照spring.factories文件中EnableAutoConfiguration的配置:

1.  org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2.  com.alipay.sofa.healthcheck.initializer.HealthcheckInitializer

        healthcheck-sofa-boot-starter模块自动配置的起始配置类还是HealthcheckInitializer:

1.  @Component
2.  @Configuration
3.  @ComponentScan(basePackageClasses = {HealthCheckTrigger.class,
4.                                       SofaBootComponentHealthCheckInfo.class})
5.  public class HealthcheckInitializerimplements
6.                                     ApplicationContextInitializer<ConfigurableApplicationContext>{
7.   
8.      ……
9.  }

        此处主要关注@ComponentScan注解,该注解的basePackageClasses属性规定了自动配置的类扫描包路径:

        1.   HealthCheckTrigger类所在的包com.alipay.sofa.healthcheck.startup;

        2.   SofaBootComponentHealthCheckInfo类所在的包com.alipay.sofa.healthcheck.service;

        ConfigurationClassPostProcessor类会处理上述包中@Configuration配置类。

        在com.alipay.sofa.healthcheck.startup包中,存在的@Configuration配置类为EndPointConfig:

1.  @Configuration
2.  public class EndPointConfig {
3.      static final StringREADINESS_CHECK_ENDPOINT_NAME = "health_readiness";
4.   
5.      @Bean
6.      public SofaBootReadinessCheckEndpointreadinessCheck() {
7.          return newSofaBootReadinessCheckEndpoint(READINESS_CHECK_ENDPOINT_NAME, false);
8.      }
9.   
10.     @Bean
11.     @ConditionalOnBean(SofaBootReadinessCheckEndpoint.class)
12.     @ConditionalOnEnabledEndpoint(READINESS_CHECK_ENDPOINT_NAME)
13.     public SofaBootReadinessCheckMvcEndpointsofaBootReadinessCheckMvcEndpoint(SofaBootReadinessCheckEndpoint delegate) {
14.         return newSofaBootReadinessCheckMvcEndpoint(delegate);
15.     }
16. }

        在Spring应用上下文中注册带@Configuration注解的配置类EndPointConfig 、SofaBootReadinessCheckEndpoint、SofaBootReadinessCheckMvcEndpoint的Bean定义。同时,在Spring应用上下文中注册上述starup和service包路径中带有@Component注解的组件SofaBootComponentHealthCheckInfo、SpringContextHealthCheckInfo、HealthCheckTrigger的Bean定义。

        3、Healthcheck配置类实例化

        在AbstractApplicationContext类refresh方法中,调用finishBeanFactoryInitialization(beanFactory)方法实例化所有非延时加载的单例实例。此时会实例化上述注册的Healthcheck相关的Bean。

        4、Healthcheck健康检查

        在AbstractApplicationContext类refresh方法中,调用finishRefresh()方法,完成Spring应用上下文刷新任务。此时,会发布ContextRefreshedEvent事件,触发所有监听ContextRefreshedEvent事件的ApplicationListener接口的实现类。由于HealthCheckTrigger类实现了ApplicationListener<ContextRefreshedEvent>接口,所以在finishRefresh阶段,会调用HealthCheckTrigger的onApplicationEvent方法,完成Healthcheck初始化工作:

1.  public void onApplicationEvent(ContextRefreshedEventcontextRefreshedEvent) {
2.          ApplicationContext applicationContext =contextRefreshedEvent.getApplicationContext();
3.          HealthCheckManager.init(applicationContext);
4.   
5.          logPrintCheckers();
6.   
7.          ReadinessCheckProcessorhealthCheckStartupProcessor = new ReadinessCheckProcessor();
8.          healthCheckStartupProcessor.checkHealth();
9.      }

        1.  获取Spring应用上下文;

        2.  初始化HealthCheckManager:设置HealthCheckManager的静态成员变量applicationContext;

        3.  在日志中打印Spring应用上下文中HealthChecker接口和HealthIndicator接口的实现类信息,例如:

1.  Found 0 SOFABoot component healthcheckers:
2.  Found 1 HealthIndicator checkers:
3.  class org.springframework.boot.actuate.health.DiskSpaceHealthIndicator

        4.  创建ReadinessCheckProcessor实例;

        5.  调用ReadinessCheckProcessor类checkHealth方法,开始Readiness检查。整个Readiness检查共分为两个阶段:

1.  public void checkHealth() {
2.   
3.          //在健康检查之前发布SofaBootBeforeReadinessCheckEvent事件
4.          publishBeforeHealthCheckEvent();
5.   
6.          boolean result = false;
7.          try {
8.   
9.              //设置健康检查开始状态
10.             StartUpHealthCheckStatus.openStartStatus();
11.  
12.             //开始健康检查流程
13.             if (startHealthCheckProcess()) {
14.                 //run the runtime check
15.                 result = operationHealthCheckProcess();
16.             }
17.         } finally {
18.             if (result) {
19.                 logger.info("Readinesscheck result: success");
20.             } else {
21.                 logger.error("Readinesscheck result: fail");
22.             }
23.  
24.             StartUpHealthCheckStatus.closeStartStatus();
25.         }
26.     }

        (1)   第一阶段:startHealthCheckProcess

        此方法开始第一阶段的Readiness检查:

        需要注意的是,只有当所有的检查项都通过,整个第一阶段的Readiness检查结果才为true。否则为false。

1.  private boolean startHealthCheckProcess(){
2.          boolean result = true;
3.          try {
4.              //是否跳过所有健康检查
5.              if (skipAllCheck()) {
6.                  logger.info("Skip thefirst phase of the readiness check");
7.                  return true;
8.              }
9.              logger.info("Begin first phaseof the readiness check");
10.             if (!springContextCheckProcessor.springContextCheck()) {
11.                 result = false;
12.             }
13.             if (!componentCheckProcessor.startupCheckComponent()) {
14.                 result = false;
15.             }
16.             if (!healthIndicatorCheckProcessor.checkIndicator()) {
17.                 result = false;
18.             }
19.             if (result) {
20.                 logger.info("first phaseof the readiness check result: success");
21.             } else {
22.                 logger.error("first phaseof the readiness check result: fail");
23.             }
24.             return result;
25.         } catch (Throwable e) {
26.             ……
27.             return false;
28.         }
29.     }

        1)  检查com.alipay.sofa.healthcheck.skip.all属性

        此处为false,表示进行Readiness检查。需要注意的是,如果跳过所有Readiness检查,则默认Readiness检查结果为true,表示所有组件状态为正常。

        2)  检查Spring应用上下文健康状态

        调用SpringContextCheckProcessor类springContextCheck方法,检查Spring应用上下文健康状态:

1.      public boolean springContextCheck() {
2.          logger.info("Begin SpringContextreadiness check.");
3.          boolean isHealth =HealthCheckManager.springContextCheck();
4.          if (isHealth) {
5.              logger.info("SpringContextreadiness check result: success.");
6.          } else {
7.              logger.error("SpringContextreadiness check result: fail.");
8.          }
9.          StartUpHealthCheckStatus.setSpringContextStatus(isHealth);
10.  
11.         return isHealth;
12.     }

        调用HealthCheckManager类springContextCheck方法,检查Spring应用上下文健康状态:如果Spring应用上下文为AbstractApplicationContext类的实例,并且其isActive方法返回值为true,或者Spring应用上下文不为null,并且不是AbstractApplicationContext类的实例,在这两种情况下,表示Spring应用上下文健康状态为正常状态,否则为不正常状态。

        设置StartUpHealthCheckStatus类属性springContextStatus为刚才健康检查的结果。

        3)  检查SOFABoot组件的健康状态

        调用ComponentCheckProcessor类startupCheckComponent方法,检查SOFABoot组件的健康状态(所有提供HealthChecker接口实现类的组件)。

        需要注意的是,当无SOFABoot中间件需要Readiness检查或所有SOFABoot中间件的Readiness检查结果为true,整个SOFABoot中间件的Readiness检查结果才为true。否则为false。

1.  public boolean startupCheckComponent() {
2.          if (skipComponentHealthCheck()) {
3.              logger.info("Skip SOFABootcomponent readiness check.");
4.              return true;
5.          }
6.          logger.info("Begin SOFABootcomponent readiness check.");
7.          boolean result = true;
8.   
9.          for (HealthChecker healthChecker :HealthCheckManager.getHealthCheckers()) {
10.  
11.             boolean resultItem =doCheckComponentHealth(healthChecker, true, null);
12.             if (!resultItem) {
13.                 result = false;
14.             }
15.         }
16.         if (result) {
17.             logger.info("SOFABootcomponent readiness check result: success.");
18.         } else {
19.             logger.error("SOFABootcomponent readiness check result: failed.");
20.         }
21.         StartUpHealthCheckStatus.setComponentStatus(result);
22.         return result;
23.     }

         检查com.alipay.sofa.healthcheck.skip.component属性值。此处为false,表示进行SOFABoot中间件的Readiness检查。

        调用HealthCheckManager类getHealthCheckers方法,获取Spring应用上下文中所有HealthChecker接口的实现类。

        然后,针对每个HealthChecker接口的实现类,调用doCheckComponentHealth方法,进行组件的Readiness检查:

1.  private boolean doCheckComponentHealth(HealthCheckerhealthChecker, boolean isRetry,
2.                                             Map<String,Health> healthMap) {
3.   
4.          boolean result = true;
5.   
6.          Health health =healthChecker.isHealthy();
7.          String componentName = healthChecker.getComponentName();
8.          int retryCount =healthChecker.getRetryCount();
9.          long retryTimeInterval =healthChecker.getRetryTimeInterval();
10.         boolean isStrictCheck =healthChecker.isStrictCheck();
11.  
12.         if (!isRetry) {
13.             retryCount = 0;
14.         }
15.         if (!HealthCheckUtil.isHealth(health)){
16.             for (int i = 0; i < retryCount;i++) {
17.  
18.                 try {
19.                     TimeUnit.MILLISECONDS.sleep(retryTimeInterval);
20.                 } catch (InterruptedExceptione) {
21.                     ……
22.                 }
23.                 health = healthChecker.isHealthy();
24.                 if(HealthCheckUtil.isHealth(health)) {
25.                     ……
26.                     break;
27.                 } else {
28.                     ……
29.                 }
30.             }
31.             if (retryCount == 0) {
32.                 ……
33.             }
34.         } else {
35.             ……
36.         }
37.  
38.         StartUpHealthCheckStatus.putComponentDetail(componentName,health);
39.         if (healthMap != null) {
40.             healthMap.put(componentName,health);
41.         }
42.         if (!isStrictCheck) {
43.             result = true;
44.         } else {
45.             result =HealthCheckUtil.isHealth(health);
46.         }
47.         return result;
48.     }

        组件Readiness检查处理逻辑简述如下:

        首先,获取组件的健康状态实例health,如果health的status为UP,则返回true。否则,在重试次数范围内,每次间隔指定的时间,重新调用HealthChecker接口实现类的isHealthy方法,检查组件的健康状态。在循环过程中,只要某次health的status为UP,则退出循环。否则,直至达到最大重试次数。

        对于检查结果,如果strictCheck属性值为false,表示非严格检查模式,则默认检查结果为true;如果strictCheck属性值为true,表示严格检查模式,则以最后一次健康检查的结果health为最终的检查结果。

        需要注意的是,不同HealthChecker接口实现类的isHealthy方法是不同的。如SofaComponentHealthChecker,它会检查组件管理器中所有组件的状态(包括服务组件ServiceComponent、引用组件ReferenceComponent等),只有当所有组件的状态都为true时,SofaComponentHealthChecker才返回status为UP的Health。

        另外,不同组件的isHealthy方法也是不同的。如ServiceComponent的isHealthy方法,它在组件为activated状态,并且组件发布过程中无异常发生,则表示该组件状态为true。

        最后,设置StartUpHealthCheckStatus类属性componentStatus为刚才健康检查的结果。

        4)  检查SpringBoot组件的健康状态

        调用HealthIndicatorCheckProcessor的checkIndicator方法,检查HealthIndicator组件的健康状态(实现HealthIndicator接口的实现类)。

        需要注意的是,当无HealthIndicator需要Readiness检查或所有HealthIndicator的Readiness检查结果为true,整个HealthIndicator的Readiness检查结果才为true。否则为false。

1.  public boolean checkIndicator() {
2.          if (skipHealthIndicator()) {
3.              logger.info("SkipHealthIndicator readiness check.");
4.              return true;
5.          }
6.          logger.info("Begin HealthIndicatorreadiness check.");
7.          List<HealthIndicator> healthIndicators =HealthCheckManager.getHealthIndicator();
8.          if (healthIndicators == null) {
9.              return true;
10.         }
11.  
12.         boolean result = true;
13.         for (HealthIndicator healthIndicator :healthIndicators) {
14.             try {
15.                 Health health =healthIndicator.health();
16.                 Status status =health.getStatus();
17.                 if (!status.equals(Status.UP)){
18.                     result = false;
19.                     ……
20.                 } else {
21.                     ……
22.                 }
23.                 StartUpHealthCheckStatus.addHealthIndicatorDetail(newHealthIndicatorDetail(
24.                     getKey(healthIndicator), health));
25.             } catch (Exception e) {
26.                 result = false;
27.                 ……           }
28.  
29.         }
30.         if (result) {
31.             logger.info("Readiness checkHealthIndicator result: success.");
32.         } else {
33.             logger.error("Readiness checkHealthIndicator result: fail.");
34.         }
35.         StartUpHealthCheckStatus.setHealthIndicatorStatus(result);
36.         return result;
37.     }

        检查com.alipay.sofa.healthcheck.skip.indicator属性值。此处为false,表示进行HealthIndicator的Readiness检查。

        调用HealthCheckManager类getHealthIndicator方法,获取Spring应用上下文中所有HealthIndicator接口的实现类。

        然后,针对每个HealthIndicator接口的实现类,调用health方法,进行Readiness检查。每个HealthIndicator实现类health方法的实现不同,如DiskSpaceHealthIndicator,其判断指定目录下可用磁盘空间是否超过了设置的上限值。如果未超过,则status为UP,否则status为DOWN。

        最后,设置StartUpHealthCheckStatus类属性healthIndicatorStatus为刚才健康检查的结果。

        至此,Healthcheck健康检查工作完成。

        (2)   第二阶段:operationHealthCheckProcess

        如果健康检查结果为true,则调用operationHealthCheckProcess方法,开始第二阶段的Readiness检查。

1.  private booleanoperationHealthCheckProcess() {
2.          boolean checkResult = true;
3.   
4.          try {
5.              logger.info("Begin secondphase of the readiness check");
6.              if(!afterHealthCheckCallbackProcessor.checkAfterHealthCheckCallback()) {
7.                  checkResult = false;
8.              }
9.   
10.             if (checkResult) {
11.                 logger.info("second phaseof the readiness check result: success");
12.             } else {
13.                 logger.error("second phaseof the readiness check result: fail");
14.             }
15.         } catch (Throwable e) {
16.             ……
17.             return false;
18.         }
19.  
20.         return checkResult;
21.     }

        调用AfterHealthCheckCallbackProcessor类checkAfterHealthCheckCallback方法:

1.  public booleancheckAfterHealthCheckCallback() {
2.   
3.          boolean result = false;
4.   
5.          if (doMiddlewareAfterHealthCheckCallback()) {
6.              result = doApplicationAfterHealthCheckCallback();
7.          }
8.   
9.          StartUpHealthCheckStatus.setAfterHealthCheckCallbackStatus(result);
10.  
11.         if (result) {
12.             logger.info("SOFABootreadiness check callback : success.");
13.         } else {
14.             logger.error("SOFABootreadiness check callback : failed.");
15.         }
16.         return result;
17.  
18.     }

        checkAfterHealthCheckCallback方法主要包括2部分检查工作:

        1)  中间件级别健康检查后回调处理

        调用doMiddlewareAfterHealthCheckCallback方法:

        注意:当所有SofaBootMiddlewareAfterReadinessCheckCallback接口的实现类都返回status为UP的Health,此方法才返回true。否则返回false。

1.  private booleandoMiddlewareAfterHealthCheckCallback() {
2.          boolean result = true;  
3.   
4.          List<SofaBootMiddlewareAfterReadinessCheckCallback>middlewareAfterReadinessCheckCallbacks = HealthCheckManager
5.              .getMiddlewareAfterHealthCheckCallbacks();
6.          for(SofaBootMiddlewareAfterReadinessCheckCallback middlewareAfterReadinessCheckCallback: middlewareAfterReadinessCheckCallbacks) {
7.              try {
8.                  Health health =middlewareAfterReadinessCheckCallback.onHealthy(HealthCheckManager
9.                      .getApplicationContext());
10.                 Status status =health.getStatus();
11.                 if (!status.equals(Status.UP)){
12.                     result = false;
13.                    
14.                 } else {
15.                     ……
16.                 }
17.  
18.                 StartUpHealthCheckStatus.putAfterHealthCheckCallbackDetail(
19.                     getKey(middlewareAfterReadinessCheckCallback.getClass().getName()),health);
20.  
21.             } catch (Throwable t) {
22.                 result = false;
23.             }
24.         }
25.  
26.         ……
27.         return result;
28.     }

        调用HealthCheckManager类getMiddlewareAfterHealthCheckCallbacks方法,获取Spring应用上下文中所有SofaBootMiddlewareAfterReadinessCheckCallback接口的实现类。

        然后,针对每个SofaBootMiddlewareAfterReadinessCheckCallback接口的实现类,调用onHealthy方法,进行Readiness检查。正常返回status为UP的Health实例,否则返回status为DOWN的Health实例,并把每个检查结果存入StartUpHealthCheckStatus类属性afterHealthCheckCallbackDetail中。

        2)  应用级别健康检查后回调处理

        当doMiddlewareAfterHealthCheckCallback方法返回true,则调用doApplicationAfterHealthCheckCallback方法:

        注意:当所有SofaBootAfterReadinessCheckCallback接口的实现类都返回status为UP的Health,此方法才返回true。否则返回false。

1.  private booleandoApplicationAfterHealthCheckCallback() {
2.          boolean result = true;
3.   
4.          logger.info("BeginSofaBootAfterReadinessCheckCallback readiness check");
5.   
6.          List<SofaBootAfterReadinessCheckCallback>afterReadinessCheckCallbacks = HealthCheckManager
7.              .getApplicationAfterHealthCheckCallbacks();
8.          for(SofaBootAfterReadinessCheckCallback afterReadinessCheckCallback :afterReadinessCheckCallbacks) {
9.              try {
10.                 Health health =afterReadinessCheckCallback.onHealthy(HealthCheckManager
11.                     .getApplicationContext());
12.                 Status status =health.getStatus();
13.                 if (!status.equals(Status.UP)){
14.                     result = false;
15.                     ……
16.                 } else {
17.                     ……
18.                 }
19.  
20.                 StartUpHealthCheckStatus.putAfterHealthCheckCallbackDetail(
21.                     getKey(afterReadinessCheckCallback.getClass().getName()),health);
22.  
23.             } catch (Throwable t) {
24.                 result = false;
25.             }
26.         }
27.                ……
28.         }
29.         return result;
30.     }

        调用HealthCheckManager类getApplicationAfterHealthCheckCallbacks方法,获取Spring应用上下文中所有SofaBootAfterReadinessCheckCallback接口的实现类。

        然后,针对每个SofaBootAfterReadinessCheckCallback接口的实现类,调用getStatus方法,进行Readiness检查。正常返回UP,否则返回DOWN,并把每个检查结果存入StartUpHealthCheckStatus类属性afterHealthCheckCallbackDetail中。

        至此,Readiness检查完成。

        从上述的分析可以看出,只有当应用中所有的Readiness检查结果都为true时,才认为整个应用的健康状态为success。否则为fail。

        5、  访问Healthcheck服务

        我们可以通过浏览器查看健康检查的结果。

        例如:在浏览器中输入URL:http://localhost:8080/health/readiness:

{
"status":"UP",
"sofaBootComponentHealthCheckInfo":{"status":"UP"},
"springContextHealthCheckInfo":{"status":"UP"},
"DiskSpaceHealthIndicator":{"status":"UP","total":107374178304,"free":38490177536,"threshold":10485760}
}

        服务端接受客户端请求,具体响应流程如下:

        由于Healthcheck通过SpringMVC机制对外提供服务,所以,当服务端接受到客户端发起的请求时,根据请求URL:/health/readiness,查找到名为sofaBootReadinessCheckMvcEndpoint的服务处理类SofaBootReadinessCheckEndpoint的实例,然后调用其invoke方法,处理请求:

1.      @RequestMapping(produces =MediaType.APPLICATION_JSON_VALUE)
2.      @ResponseBody
3.      public Object invoke(Principal principal) {
4.          if (!getDelegate().isEnabled()) {
5.              // Shouldn't happen because therequest mapping should not be registered
6.              return getDisabledResponse();
7.          }
8.          Health health = getDelegate().invoke();
9.          HttpStatus status = getStatus(health);
10.         if (status != null) {
11.             return newResponseEntity<Health>(health, status);
12.         }
13.         return health;
14.     }

        首先,判断sofaBootReadinessCheckMvcEndpoint的委托处理类是否开启,此处为com.alipay.sofa.healthcheck.service.SofaBootReadinessCheckEndpoint的实例,且enable为true,表示开启。

        然后,调用SofaBootReadinessCheckEndpoint的invoke方法:

1.  public Health invoke() {
2.          //spring
3.          boolean springContextStatus = StartUpHealthCheckStatus.getSpringContextStatus();
4.   
5.          //component
6.          boolean componentStatus =StartUpHealthCheckStatus.getComponentStatus();
7.          Map<String, Health>componentDetail = StartUpHealthCheckStatus.getComponentDetail();
8.   
9.          //HealthIndicator
10.         boolean healthIndicatorStatus = StartUpHealthCheckStatus.getHealthIndicatorStatus();
11.         List<HealthIndicatorDetail>healthIndicatorDetails = StartUpHealthCheckStatus
12.             .getHealthIndicatorDetails();
13.  
14.         //AfterHealthCheckCallback
15.         boolean afterHealthCheckCallbackStatus= StartUpHealthCheckStatus
16.             .getAfterHealthCheckCallbackStatus();
17.         Map<String, Health>afterHealthCheckCallbackDetails = StartUpHealthCheckStatus
18.             .getAfterHealthCheckCallbackDetails();
19.  
20.         Map<String, Health> healths = newHashMap<>();
21.  
22.         //spring
23.         if (springContextStatus) {
24.             healths.put("springContextHealthCheckInfo",Health.up().build());
25.         } else {
26.             healths.put("springContextHealthCheckInfo",Health.down().build());
27.  
28.         }
29.  
30.         //component and callback
31.         Builder builder;
32.         if (componentStatus &&healthIndicatorStatus && afterHealthCheckCallbackStatus) {
33.             builder = Health.up();
34.         } else {
35.             builder = Health.down();
36.         }
37.  
38.         if(!CollectionUtils.isEmpty(componentDetail)) {
39.             builder =builder.withDetail("Middleware-start-period", componentDetail);
40.         }
41.         if (!CollectionUtils.isEmpty(afterHealthCheckCallbackDetails)){
42.             builder =builder.withDetail("Middleware-operation-period",
43.                 afterHealthCheckCallbackDetails);
44.         }
45.  
46.         healths.put("sofaBootComponentHealthCheckInfo",builder.build());
47.  
48.         //HealthIndicator
49.         for (HealthIndicatorDetailhealthIndicatorDetail : healthIndicatorDetails) {
50.             String name =healthIndicatorDetail.getName();
51.             Health health =healthIndicatorDetail.getHealth();
52.  
53.             healths.put(name, health);
54.  
55.         }
56.  
57.         returnthis.healthAggregator.aggregate(healths);
58.     }

        通过StartUpHealthCheckStatus,查看应用启动时Readiness检查的结果,主要包括Spring应用上下文状态、中间件状态、HealthIndicator状态、中间件级别健康检查后回调处理完状态、应用级别健康检查后回调处理完状态。

        然后根据所有这些Readiness检查的结果,构造一个代表整个应用的Health。

        根据Health,创建SpringMVC响应实体对象ResponseEntity。

        最后,SpringMVC以JSON数据格式,返回Readiness检查的结果。例如:本项目sofa-boot-sample的Readiness检查结果如下:

{
"status":"UP",
"sofaBootComponentHealthCheckInfo":{"status":"UP"},
"springContextHealthCheckInfo":{"status":"UP"},
"DiskSpaceHealthIndicator":{"status":"UP","total":107374178304,"free":38490177536,"threshold":10485760}
}

        至此,SOFABoot Readiness检查解析完成。

        另外,补充一点,如果输入URL:http://localhost:8080/health,则可以直接查看SpringBoot的健康检查结果:

{"status":"UP",
"sofaBootComponentHealthCheckInfo":{"status":"UP","Middleware":{}},
"springContextHealthCheckInfo":{"status":"UP"},
"diskSpace":{"status":"UP","total":107374178304,"free":38483980288,"threshold":10485760}}
        查看SpringBoot健康检查结果服务的处理类为org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter,在此不详述。

猜你喜欢

转载自blog.csdn.net/beyondself_77/article/details/80846270