Continue to analyze other access points.
Analysis of other access points requiring initialization
Sometimes we need to do some custom initialization operations, but how to do these operations before registering in the registry with the status of UP, that is, before starting to process requests?
In order to be more compatible with the cloud environment, Spring Boot introduces some concepts related to cloud deployment after version 2.3.0:
- Liveness state: for an application, the liveness state refers to whether the state of the application is normal. If the survival state is not normal, it means that the application itself has been damaged and cannot be recovered. In k8s, if the survival detection fails, kubelet will kill the Container and restart according to its restart policy:
- In spring boot, the corresponding interface is / Actor / health / liveness
- The corresponding enumeration class is org springframework. boot. availability. Livenessstate includes the following two states:
- CORRECT: the survival status is normal
- Broker: the survival status is abnormal
- Readiness: refers to whether the application is ready to accept and process client requests. For any reason, if the application is not ready to process a service request, it should be declared busy until it can respond to the request normally. If the readiness state is not ready, traffic should not be routed to the instance. In k8s, if the readiness detection fails, the Endpoints controller will delete the IP address of the Pod from the Endpoints. If you don't use k8s service discovery, you don't need to care about this:
- In spring boot, the corresponding interface is / Actor / health / readiness
- The corresponding enumeration class is org springframework. boot. availability. Readinessstate, including the following two states:
- ACCEPTING_TRAFFIC: ready to accept requests
- REFUSING_TRAFFIC: the request cannot be accepted at present
By default, Spring Boot will modify these states during initialization, corresponding to the source code (we only care about listeners, which marks the change of Spring Boot life cycle):
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); //Tell all listeners to start 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); } //Tell all listeners that startup is complete listeners.started(context); //Call various spring runners + commandrunners callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { //Notify all listeners that they are running listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; }
Among them, listeners Started and listeners What is done in running:
@Override public void started(ConfigurableApplicationContext context) { //Publishing ApplicationStartedEvent events context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context)); //Set LivenessState to CORRECT AvailabilityChangeEvent.publish(context, LivenessState.CORRECT); } @Override public void running(ConfigurableApplicationContext context) { //Publishing ApplicationReadyEvent events context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context)); //Set ReadinessState to ACCEPTING_TRAFFIC AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC); }
Since the processing of the ApplicationContext publishing event and subscribing to the event are synchronized, if we want to do something before the LivenessState is CORRECT, we can listen to the ApplicationStartedEvent event event. Similarly, you want to accept in the ReadinessState_ Before doing something, traffic listens to the ApplicationStartedEvent event. How to associate LivenessState and ReadinessState with the state of the registered instance to the registry?
The registry we use is Eureka, and the instance of the registry is stateful. Our Eureka Client configuration is:
eureka: client: # eureka client refresh local cache time # Default 30s # For common attributes, you can configure them with hump or bar name. The hump name used here is the bar name configured below registryFetchIntervalSeconds: 5 healthcheck: # Enable health check enabled: true # The interval between tasks that regularly check instance information and update the status of local instances instance-info-replication-interval-seconds: 10 # The initial timing checks the instance information and the task delay of updating the local instance status initial-instance-info-replication-interval-seconds: 5
The health check is performed by calling the local / health service of / Eureka. This health check will be called in the task of checking the instance information regularly and updating the local instance status. The initial delay of this task is set to 10s, and then the inspection interval is set to 5s. The health check includes the survival status check and the readiness status check. When the survival status is CORRECT, the status is UP and the readiness status is accepting_ The status is UP only when the traffic is. The corresponding HealthIndicator is:
public class ReadinessStateHealthIndicator extends AvailabilityStateHealthIndicator { public ReadinessStateHealthIndicator(ApplicationAvailability availability) { super(availability, ReadinessState.class, (statusMappings) -> { //The Status is UP only when the survival Status is CORRECT 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) -> { //The ready status is accepting_ The status is UP only when the traffic is statusMappings.add(ReadinessState.ACCEPTING_TRAFFIC, Status.UP); statusMappings.add(ReadinessState.REFUSING_TRAFFIC, Status.OUT_OF_SERVICE); }); } }
The process of regularly checking instance information and instance status and synchronizing to Eureka Server is as follows:
We can use this mechanism to make the status of initial registration with Eureka not UP, wait for the survival status to CORRECT and the ready status to accept_ The instance status will be set to UP and synchronized to Eureka Server through the above timing check task when traffic.
You can add configuration to specify the initial registration status:
eureka: instance: # Initial instance state initial-status: starting
In this way, we can listen to the ApplicationStartedEvent event to implement the microservice initialization operation, and start the service after the operation is completed. At the same time, we should also consider the problem of executing only once, because you have more than one ApplicationContext. For example, after Spring Cloud enables BootStrap Context, there will be another BootStrap Context. We should ensure that it is executed only once. We can write code like the following and inherit the following abstract class collection:
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; } //Due to spring cloud's org springframework. cloud. context. restart. Restartlistener causes the same context to be triggered multiple times //I personally feel org springframework. cloud. context. restart. Restartlistener is in spring-boot2 Spring cloud versions after 0.0 are unnecessary //However, the official did not respond positively in case the official would do something with this later. Here we make an adaptation. Refer to the issue I asked: https://github.com/spring-cloud/spring-cloud-commons/issues/693 synchronized (INITIALIZED) { if (INITIALIZED.get()) { return; } //Each spring cloud application can only be initialized once init(); INITIALIZED.set(true); } } protected abstract void init(); static boolean isBootstrapContext(ApplicationStartedEvent applicationEvent) { return applicationEvent.getApplicationContext().getEnvironment().getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME); } }
WeChat search "my programming meow" attention to the official account, daily brush, easy to upgrade technology, and capture all kinds of offer: