实现微服务预热调用之后再开始服务(下)

继续分析其他接入点。

其他需要初始化的接入点分析

我们有时候还需要做一些自定义的初始化操作,但是如何在注册到注册中心状态为 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 的流程如下

image

我们可以使用这个机制,让初始注册到 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

推荐这些技术文章:

实现微服务预热调用之后再开始服务(下)

继续分析其他接入点。
其他需要初始化的接入点分析
我们有时候还需要做一些自定义的初始化操作,但是如何在注册到注册中心状态为 UP 也就是开始处理请求之前做这些操作呢?
为了更加与云环境兼容,Spring Boot 从 2.3.0 版本之后引入了一些云上部署相关的概念:

LivenessState(存活状态):就应用程序而言,存活状态是指应用程序的状态是否正常。如果存活状态不正常,则意味着应用程...

文章标题:实现微服务预热调用之后再开始服务(下)
文章链接:https://www.dianjilingqu.com/352.html
本文章来源于网络,版权归原作者所有,如果本站文章侵犯了您的权益,请联系我们删除,联系邮箱:saisai#email.cn,感谢支持理解。
THE END
< <上一篇
下一篇>>