Continue analyzing other access points.

Analyze other access points that need to be initialized

We sometimes need to do some custom initialization, but how do we do this before we register with the registry and the state is UP, which means we start processing requests?

In order to be more compatible with cloud environments, Spring Boot has introduced some concepts related to cloud deployment since version 2.3.0:

  • LivenessState: In terms of an application, the live state refers to whether the state of the application is healthy. If the live state is abnormal, it means that the application itself is damaged and cannot be restored. In K8S, if the survival check fails, Kubelet will kill the Container and restart it according to its restart policy:

    • In the spring the boot in the corresponding interface is/physical/health/liveness

    • Corresponding enumeration class is org. Springframework. Boot. The availability. LivenessState, including the following two states:

      • CORRECT: The CORRECT status is normal
      • BROKEN: The survival status is abnormal
  • Readiness: Refers to the Readiness of an application to accept and process client requests. If, for any reason, the application is not ready to handle a service request, it should be declared busy until it can respond to the request properly. If the Readiness state is not ready, traffic should not be routed to this instance. In K8S, if the ready detection fails, the Endpoints controller will remove the IP address of the Pod from the Endpoints.

    • In the spring the boot in the corresponding interface is/physical/health/readiness

    • Corresponding enumeration class is org. Springframework. Boot. The availability. ReadinessState, including the following two states:

      • ACCEPTING_TRAFFIC: Ready to accept requests
      • REFUSING_TRAFFIC: The request cannot be accepted at this time

By default, Spring Boot changes these states during initialization, and the source code is important to note:

SpringApplication.java

public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); / / tell listeners. Of all the Listener to start the starting (bootstrapContext, enclosing 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 to start the completion.listeners started(context); // call various SpringRunners + CommandRunners callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } trylisteners {// Notice all listeners running on listeners. Running (context); } catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; }Copy the code

The listeners started and the listeners running are:

@ Override public void started (ConfigurableApplicationContext context) {/ / release ApplicationStartedEvent event context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context)); / / set the LivenessState to CORRECT AvailabilityChangeEvent. The publish (context, LivenessState. CORRECT); } @ Override public void running (ConfigurableApplicationContext context) {/ / release ApplicationReadyEvent event context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context)); / / set the ReadinessState to ACCEPTING_TRAFFIC AvailabilityChangeEvent. The publish (context, ReadinessState ACCEPTING_TRAFFIC); }Copy the code

Since the ApplicationContext publishes the event in sync with the processing of the subscribed event, we can listen for the ApplicationStartedEvent event if we want to do something before LivenessState is CORRECT. Similarly, listen for the ApplicationStartedEvent event if you want to do something before ReadinessState is ACCEPTING_TRAFFIC. How do you relate LivenessState and ReadinessState to the state of registering an instance to the registry?

The registry we use is Eureka, and instances of the registry are stateful. The configuration of our Eureka Client is:

eureka: client: # had 30 s # # the default client refresh local cache time for ordinary properties, with a hump or sheet name configuration can be, here with the names of the hump, the sheet name registryFetchIntervalSeconds configuration with below: 5 healthcheck: # enable healthcheck enabled: true # interval for periodically checking instance information and updating local instance status instance-info-replication-interval-seconds: 10 # Initial-instance-info-replication-interval-seconds: 5Copy the code

We enabled Eureka health checks by using the same services in the /actuator/ Health interface locally. This health check is invoked during tasks that periodically check instance information and update the state of the local instance. We set the initial delay for this task to 10s and the subsequent check interval to 5s. The health check includes the survival Status check and ready Status check. The Status is UP only when the survival Status is CORRECT, and the Status is UP only when the ready Status is ACCEPTING_TRAFFIC. The HealthIndicator is:

public class ReadinessStateHealthIndicator extends AvailabilityStateHealthIndicator { public ReadinessStateHealthIndicator(ApplicationAvailability availability) { super(availability, ReadinessState.class, StatusMappings. Add (readinessState. ACCEPTING_TRAFFIC, status.up); statusMappings.add(ReadinessState.REFUSING_TRAFFIC, Status.OUT_OF_SERVICE); }); }}Copy the code
public class ReadinessStateHealthIndicator extends AvailabilityStateHealthIndicator { public ReadinessStateHealthIndicator(ApplicationAvailability availability) { super(availability, ReadinessState.class, StatusMappings. Add (ReadinessState.ACCEPTING_TRAFFIC,); Status.UP); statusMappings.add(ReadinessState.REFUSING_TRAFFIC, Status.OUT_OF_SERVICE); }); }}Copy the code

The process for periodically checking instance information and instance status and synchronizing them to the Eureka Server is as follows:

We can use this mechanism to make the initial registration to Eureka not UP, wait for the live state to CORRECT and the ready state to ACCEPTING_TRAFFIC, The instance status is set to UP in the preceding periodic check task and synchronized to the Eureka Server.

You can add a configuration to specify the initial registration status:

Eureka: instance: # Initial-status: startingCopy the code

This allows us to listen for the ApplicationStartedEvent event to implement the microservice initialization operation before starting the service. Also consider executing only once, because you have more than one ApplicationContext. For example, when Spring Cloud enables the BootStrap Context, an additional BootStrap Context is added. To ensure that we only execute once, we can write code like this to 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; } / / as a result of the spring - cloud org. Springframework. Cloud. Context. Restart. RestartListener result in the same context trigger / / personally, I feel many times Org. Springframework. Cloud. Context. Restart. This spring after spring - boot2.0.0 RestartListener -- cloud version there is no need / / but officials did not respond, In case the official still does something with this later, here we make an adaptation, referring 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 initialize init() once; INITIALIZED.set(true); } } protected abstract void init(); static boolean isBootstrapContext(ApplicationStartedEvent applicationEvent) { return applicationEvent.getApplicationContext().getEnvironment().getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME);  }}Copy the code

Wechat search “my programming meow” public account, a daily brush, easy to improve skills, won a variety of offers