关于spring-cloud:实现微服务预热调用之后再开始服务下

52次阅读

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

持续剖析其余接入点。

其余须要初始化的接入点剖析

咱们有时候还须要做一些自定义的初始化操作,然而如何在注册到注册核心状态为 UP 也就是开始解决申请之前做这些操作呢?

为了更加与云环境兼容,Spring Boot 从 2.3.0 版本之后引入了一些云上部署相干的概念:

  • LivenessState(存活状态):就应用程序而言,存活状态是指应用程序的状态是否失常。如果存活状态不失常,则意味着 应用程序自身已损坏,无奈复原。在 k8s 中,如果存活检测失败,则 kubelet 将杀死 Container,并且依据其重新启动策略进行重启:

    • 在 spring boot 中对应的接口是 /actuator/health/liveness
    • 对应的枚举类是 org.springframework.boot.availability.LivenessState,包含上面两个状态:

      • CORRECT:存活状态失常
      • BROKEN:存活状态不失常
  • Readiness(就绪状态):指的是应用程序是否已筹备好承受并解决客户端申请。出于任何起因,如果应用程序尚未筹备好解决服务申请,则应将其申明为忙碌,直到可能失常响应申请为止。如果 Readiness 状态尚未就绪,则不应将流量路由到该实例。在 k8s 中,如果就绪检测失败,则 Endpoints 控制器将从 Endpoints 中删除这个 Pod 的 IP 地址,如果你没有应用 k8s 的服务发现的话,就不必太关怀这个:

    • 在 spring boot 中对应的接口是 /actuator/health/readiness
    • 对应的枚举类是 org.springframework.boot.availability.ReadinessState,包含上面两个状态:

      • ACCEPTING_TRAFFIC:筹备好承受申请
      • REFUSING_TRAFFIC:目前不能承受申请了

默认状况下,Spring Boot 在初始化过程中会批改这些状态,对应源码(咱们只关怀 listeners 相干,这些标记着 Spring Boot 生命周期变动):

SpringApplication.java


public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 通知所有 Listener 启动中
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // 通知所有 Listener 启动实现
        listeners.started(context);
        // 调用各种 SpringRunners + CommandRunners
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 告诉所有 Listener 运行中
        listeners.running(context);
    }
    catch (Throwable ex) {handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

其中,listeners.startedlisteners.running 外面做的事件是:

@Override
public void started(ConfigurableApplicationContext context) {
    // 公布 ApplicationStartedEvent 事件
    context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
    // 设置 LivenessState 为 CORRECT
    AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}

@Override
public void running(ConfigurableApplicationContext context) {
    // 公布 ApplicationReadyEvent 事件
    context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
    // 设置 ReadinessState 为 ACCEPTING_TRAFFIC
    AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}

因为 ApplicationContext 公布事件与订阅该事件的解决是同步进行的,所以如果咱们想在 LivenessState 为 CORRECT 之前做点操作,能够监听 ApplicationStartedEvent 事件。同理,想在 ReadinessState 为 ACCEPTING_TRAFFIC 之前做点操作就监听 ApplicationStartedEvent 事件。如何将 LivenessState 还有 ReadinessState 与注册实例到注册核心的状态分割起来呢

咱们用的注册核心是 Eureka,注册核心的实例是有状态的。咱们的 Eureka Client 的配置是:

eureka:
  client:
    # eureka client 刷新本地缓存工夫
    # 默认 30s
    # 对于一般属性,用驼峰或者横杠名称配置都能够,这里用的驼峰名称,上面配置用的横杠名称
    registryFetchIntervalSeconds: 5
    healthcheck:
      # 启用健康检查
      enabled: true
    # 定时查看实例信息以及更新本地实例状态的工作的距离
    instance-info-replication-interval-seconds: 10
    # 初始定时查看实例信息以及更新本地实例状态的工作提早
    initial-instance-info-replication-interval-seconds: 5

咱们启用了 Eureka 的健康检查,其实就是通过调用本地的 /actuator/health 接口的雷同服务进行健康检查。这个健康检查,会在定时查看实例信息以及更新本地实例状态的工作中调用。这个工作的初始提早咱们设置为了 10s,之后查看距离设置为了 5s。健康检查包含存活状态查看还有就绪状态查看,存活状态为 CORRECT 的时候 Status 才为 UP,就绪状态为 ACCEPTING_TRAFFIC 的时候 status 才为 UP。对应的 HealthIndicator 是:

public class ReadinessStateHealthIndicator extends AvailabilityStateHealthIndicator {public ReadinessStateHealthIndicator(ApplicationAvailability availability) {super(availability, ReadinessState.class, (statusMappings) -> {
            // 存活状态为 CORRECT 的时候 Status 才为 UP
            statusMappings.add(ReadinessState.ACCEPTING_TRAFFIC, Status.UP);
            statusMappings.add(ReadinessState.REFUSING_TRAFFIC, Status.OUT_OF_SERVICE);
        });
    }
}
public class ReadinessStateHealthIndicator extends AvailabilityStateHealthIndicator {public ReadinessStateHealthIndicator(ApplicationAvailability availability) {super(availability, ReadinessState.class, (statusMappings) -> {
            // 就绪状态为 ACCEPTING_TRAFFIC 的时候 status 才为 UP
            statusMappings.add(ReadinessState.ACCEPTING_TRAFFIC, Status.UP);
            statusMappings.add(ReadinessState.REFUSING_TRAFFIC, Status.OUT_OF_SERVICE);
        });
    }
}

定时查看实例信息以及实例状态并同步到 Eureka Server 的流程如下

咱们能够应用这个机制,让初始注册到 Eureka 的状态不为 UP,期待存活状态为 CORRECT 的时候并且就绪状态为 ACCEPTING_TRAFFIC 的时候,才会通过下面的定时查看工作将实例状态设置为 UP 同步到 Eureka Server。

能够加上配置,指定初始注册状态:

eureka:
  instance:
    # 初始实例状态
    initial-status: starting

这样咱们能够监听 ApplicationStartedEvent 事件实现微服务初始化操作,操作实现后才开始服务。同时还要思考只执行一次的问题,因为你的 ApplicationContext 不止一个,例如 Spring Cloud 启用 BootStrap Context 之后,就多了一个 BootStrap Context,咱们要保障只执行一次的话,能够像上面这么写代码,继承上面这个抽象类汇合:

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;

import java.util.concurrent.atomic.AtomicBoolean;

import static org.springframework.cloud.bootstrap.BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME;


public abstract class AbstractMicronServiceInitializer implements ApplicationListener<ApplicationStartedEvent> {private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {if (isBootstrapContext(event)) {return;}
        // 因为 spring-cloud 的 org.springframework.cloud.context.restart.RestartListener 导致同一个 context 触发屡次
        // 我个人感觉 org.springframework.cloud.context.restart.RestartListener 这个在 spring-boot2.0.0 之后的 spring-cloud 版本是没有必要存在的
        // 然而官网并没有侧面回应,以防之后官网还拿这个做点事件,这里咱们做个适配,参考我问的这个 issue:https://github.com/spring-cloud/spring-cloud-commons/issues/693
        synchronized (INITIALIZED) {if (INITIALIZED.get()) {return;}
            // 每个 spring-cloud 利用只能初始化一次
            init();
            INITIALIZED.set(true);
        }
    }

    protected abstract void init();

    static boolean isBootstrapContext(ApplicationStartedEvent applicationEvent) {return applicationEvent.getApplicationContext().getEnvironment().getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME);
    }
}

微信搜寻“我的编程喵”关注公众号,每日一刷,轻松晋升技术,斩获各种 offer

正文完
 0