The foreword 0.

  • Springboot version: 2.1.9.RELEASE
  • Springcloud version: Greenwich.SR4

1. Method entry

@Configuration
/ / Step1: introducing EurekaServerInitializerConfiguration configuration class
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
    / /...
}


@Configuration
public class EurekaServerInitializerConfiguration 
        implements ServletContextAware.SmartLifecycle.Ordered {
    / /...
    / / EurekaServerInitializerConfiguration configuration class implements the SmartLifecycle interface
    Call the lifecycle start() method when IOC container initialization is about to end
    @Override
    public void start(a) {
        new Thread(() -> {
	    try {
	        // TODO: is this class even needed now?
                // Step2: initialize Eureka Server context
		eurekaServerBootstrap.contextInitialized(
                           EurekaServerInitializerConfiguration.this.servletContext);
		/ /...
	    }
	    catch (Exception ex) {
	        // Help!
	        log.error("Could not initialize Eureka servlet context", ex);
	    }
        }).start();
    }
    / /...
}


public class EurekaServerBootstrap {
    / /...
    public void contextInitialized(ServletContext context) {
        try {
	    initEurekaEnvironment();
            // Step3: initialize the Eureka Server context
	    initEurekaServerContext();

            context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
	}
        catch (Throwable e) {
	    / /...}}protected void initEurekaServerContext(a) throws Exception {
        / /...
        // 2 Synchronize the cluster node registry
        int registryCount = this.registry.syncUp();
        / /...
    }

    / /...
}
Copy the code

2. Synchronize the registry of cluster nodes

// PeerAwareInstanceRegistry.class
public int syncUp(a) {
    // Copy entire entry from neighboring DS node
    // Count the number of instances synchronized to the local registry
    int count = 0;

    // The default retry interval is 30 seconds. If the retry succeeds once, the retry will not be performed
    for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
        if (i > 0) {
            try {
                Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
            } catch (InterruptedException e) {
                logger.warn("Interrupted during registry transfer..");
                break; }}// 2.1 When the server is started, the service instance information is pulled from the registry of cluster nodes
        Applications apps = eurekaClient.getApplications();
      	// Iterate through the service information
        for (Application app : apps.getRegisteredApplications()) {
            // Iterate through the instance information in the service information
            for (InstanceInfo instance : app.getInstances()) {
                try {
                    if (isRegisterable(instance)) {
                      	// 3 Register the instance to the local registry
                        register(instance, instance.getLeaseInfo().getDurationInSecs(), true); count++; }}catch (Throwable t) {
                    logger.error("During DS init copy", t); }}}}return count;
}
Copy the code

2.1 eurekaClient. GetApplications ()

// DiscoveryClient.class
public class DiscoveryClient implements EurekaClient {
    / /...
    @Override
    public Applications getApplications(a) {
        return localRegionApps.get();
    }
    / /...
}
Copy the code
2.1.1 localRegionApps source

This is described in the Eurekaclient-Pull Registry

3. Register the instance to the local registry

// AbstractInstanceRegistry.class
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    try {
        // Enable read lock
        read.lock();
        // Get the service lease information based on the service name from the local registry
        Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
        REGISTER.increment(isReplication);
        if (gMap == null) {
            // If the service lease information is not available, create a new one and add it to the local registry
            final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
            gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
            if (gMap == null) { gMap = gNewMap; }}// Get instance lease information from service lease information
        Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
        // Retain the last dirty timestamp without overwriting it, if there is already a lease
        if(existingLease ! =null&& (existingLease.getHolder() ! =null)) {
            Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
            Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
            logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);

            // this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
            // InstanceInfo instead of the server local copy.
            if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
                logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
                        " than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
                logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
                // If the instance lease information is obtained locally and its latest modification time (dirty) is longer than the latest modification time (dirty) of the instance to be registered
                // The existing instance lease information is used
                // Prevent expired service instances from being registered in the local registryregistrant = existingLease.getHolder(); }}else {
            // The lease does not exist and hence it is a new registration
            // The first time the local registry was registered
            synchronized (lock) {
                if (this.expectedNumberOfClientsSendingRenews > 0) {
                    // Since the client wants to register it, increase the number of clients sending renews
                    // Expect to receive the number of heartbeat renew instances +1
                    this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
                    // 3.1 Updated the expected number of heartbeat renewal requests received per minute
                    updateRenewsPerMinThreshold();
                }
            }
            logger.debug("No previous lease information found; it is new registration");
        }
        Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
        if(existingLease ! =null) {
            lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
        }
        // Add instance lease information to service lease information
        gMap.put(registrant.getId(), lease);
        // Add the service instance information to the latest registration queue
        synchronized (recentRegisteredQueue) {
            recentRegisteredQueue.add(new Pair<Long, String>(
                    System.currentTimeMillis(),
                    registrant.getAppName() + "(" + registrant.getId() + ")"));
        }
        // This is where the initial state transfer of overridden status happens
        if(! InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {/ / to be registered instance when things aren't as UNKNOWN coverage, said is not registered for the first time, instructions are modified, need to overriddenInstanceStatusMap cache
            logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
                            + "overrides", registrant.getOverriddenStatus(), registrant.getId());
            if(! overriddenInstanceStatusMap.containsKey(registrant.getId())) { logger.info("Not found overridden id {} and hence adding it", registrant.getId());
                overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
            }
        }
        InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
        if(overriddenStatusFromMap ! =null) {
            logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
            // Set the override state
            registrant.setOverriddenStatus(overriddenStatusFromMap);
        }

        // Set the status based on the overridden status rules
        // 3.2 Based on the overwrite state, the instance state is matched and assigned according to certain rules
        InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
        // Set the instance state
        registrant.setStatusWithoutDirty(overriddenInstanceStatus);

        // If the lease is registered with UP status, set lease service up timestamp 
        if (InstanceStatus.UP.equals(registrant.getStatus())) {
            // If the instance status is UP, the instance startup time is recorded in the instance lease information
            lease.serviceUp();
        }
      	// Set the behavior type
        registrant.setActionType(ActionType.ADDED);
        // Add instance change information to the recently changed queue
        recentlyChangedQueue.add(new RecentlyChangedItem(lease));
        // Instance info Sets the latest modification time
        registrant.setLastUpdatedTimestamp();
        // Invalidate the corresponding cache, involving the response body cache, used when handling the client pull registry, later analysis
        invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
        logger.info("Registered instance {}/{} with status {} (replication={})",
                registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
    } finally {
        // Close the read lockread.unlock(); }}Copy the code

3.1 updateRenewsPerMinThreshold ()

// AbstractInstanceRegistry.class

// Expected number of heartbeat renewal requests received per minute
protected volatile int numberOfRenewsPerMinThreshold;
// Expected number of heartbeat renew instances received
protected volatile int expectedNumberOfClientsSendingRenews;

protected void updateRenewsPerMinThreshold(a) {
    // Default Number of expected heartbeat lease requests received per minute = Number of expected heartbeat lease requests received * (60/30) * 0.85
    // The self-protection mechanism of the server is related
    this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
            * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
            * serverConfig.getRenewalPercentThreshold());
}
Copy the code

3.2 getOverriddenInstanceStatus ()

// AbstractInstanceRegistry.class
protected InstanceInfo.InstanceStatus getOverriddenInstanceStatus(InstanceInfo r,
                                                                Lease<InstanceInfo> existingLease,
                                                                boolean isReplication) {
    // 3.2.1 Obtaining rules
    InstanceStatusOverrideRule rule = getInstanceInfoOverrideRule();
    logger.debug("Processing override status using rule: {}", rule);
    // 3.3.2 Processing based on rule matching
    return rule.apply(r, existingLease, isReplication).status();
}
Copy the code

3.2.1 getInstanceInfoOverrideRule ()

// PeerAwareInstanceRegistryImpl.class

private final InstanceStatusOverrideRule instanceStatusOverrideRule;

public PeerAwareInstanceRegistryImpl( EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs, EurekaClient eurekaClient ) {
    / /...
    When the constructor initialization is performed, three rules are added
    this.instanceStatusOverrideRule = new FirstMatchWinsCompositeRule(new DownOrStartingRule(),
            new OverrideExistsRule(overriddenInstanceStatusMap), new LeaseExistsRule());
}


protected InstanceStatusOverrideRule getInstanceInfoOverrideRule(a) {
    return this.instanceStatusOverrideRule;
}

Copy the code

3.3.2 rainfall distribution on 10-12 rule. The apply ()

// FirstMatchWinsCompositeRule.class

private final InstanceStatusOverrideRule defaultRule;

public FirstMatchWinsCompositeRule(InstanceStatusOverrideRule... rules) {
    / /...
    // Default rules are set when constructor initialization is performed
    this.defaultRule = new AlwaysMatchInstanceStatusRule();
    / /...
}

public StatusOverrideResult apply(InstanceInfo instanceInfo,
                                  Lease<InstanceInfo> existingLease,
                                  boolean isReplication) {
    // Iterate over the above three rules
    for (int i = 0; i < this.rules.length; ++i) {
        StatusOverrideResult result = this.rules[i].apply(instanceInfo, existingLease, isReplication);
        if (result.matches()) {
            returnresult; }}// If no result is found, the default rule is executed
    return defaultRule.apply(instanceInfo, existingLease, isReplication);
}
Copy the code

3.3.2.1 Rule Match Parsing

  • DownOrStartingRule
  • OverrideExistsRule
  • LeaseExistsRule
  • AlwaysMatchInstanceStatusRule

The four rules are processed sequentially until a match is made, and the default rule is the bottom

Rule matching is invoked only when the server processes registration and heartbeat renewal requests

public class DownOrStartingRule implements InstanceStatusOverrideRule {
    private static final Logger logger = LoggerFactory.getLogger(DownOrStartingRule.class);

    @Override
    public StatusOverrideResult apply(InstanceInfo instanceInfo,
                                      Lease<InstanceInfo> existingLease,
                                      boolean isReplication) {
        // ReplicationInstance is DOWN or STARTING - believe that, but when the instance says UP, question that
        // The client instance sends STARTING or DOWN (because of heartbeat failures), then we accept what
        // the client says. The same is the case with replica as well.
        // The OUT_OF_SERVICE from the client or replica needs to be confirmed as well since the service may be
        // currently in SERVICE
        // If the state of the client instance is DOWN or STARTING, it is considered trusted and the corresponding state value can be returned directly
        // If the status of the instance is UP or OUT_OF_SERVICE, it is considered untrusted, because the status of the instance may change on the server using the Actuator. Therefore, check whether the status of the instance is changed on the server and the next rule is required
        if((! InstanceInfo.InstanceStatus.UP.equals(instanceInfo.getStatus())) && (! InstanceInfo.InstanceStatus.OUT_OF_SERVICE.equals(instanceInfo.getStatus()))) { logger.debug("Trusting the instance status {} from replica or instance for instance {}",
                    instanceInfo.getStatus(), instanceInfo.getId());
            return StatusOverrideResult.matchingStatus(instanceInfo.getStatus());
        }
        return StatusOverrideResult.NO_MATCH;
    }

    @Override
    public String toString(a) {
        returnDownOrStartingRule.class.getName(); }}public class OverrideExistsRule implements InstanceStatusOverrideRule {

    private static final Logger logger = LoggerFactory.getLogger(OverrideExistsRule.class);

    private Map<String, InstanceInfo.InstanceStatus> statusOverrides;

    public OverrideExistsRule(Map<String, InstanceInfo.InstanceStatus> statusOverrides) {
        this.statusOverrides = statusOverrides;
    }

    @Override
    public StatusOverrideResult apply(InstanceInfo instanceInfo, Lease<InstanceInfo> existingLease, boolean isReplication) {
        InstanceInfo.InstanceStatus overridden = statusOverrides.get(instanceInfo.getId());
        // If there are instance specific overrides, then they win - otherwise the ASG status
        // When this rule is executed, the instance state is UP or OUT_OF_SERVICE
        / / the rules execution logic: check the server cache if there is a corresponding instance overriddenStatus overriddenInstanceStatusMap
        // If so, it is considered trusted and returns overriddenStatus
        // If not, the next rule is required
        / / for instance through physical change state, will be cached overriddenInstanceStatusMap
        if(overridden ! =null) {
            logger.debug("The instance specific override for instance {} and the value is {}",
                    instanceInfo.getId(), overridden.name());
            return StatusOverrideResult.matchingStatus(overridden);
        }
        return StatusOverrideResult.NO_MATCH;
    }

    @Override
    public String toString(a) {
        returnOverrideExistsRule.class.getName(); }}public class LeaseExistsRule implements InstanceStatusOverrideRule {

    private static final Logger logger = LoggerFactory.getLogger(LeaseExistsRule.class);

    @Override
    public StatusOverrideResult apply(InstanceInfo instanceInfo,
                                      Lease<InstanceInfo> existingLease,
                                      boolean isReplication) {
        // This is for backward compatibility until all applications have ASG
        // names, otherwise while starting up
        // the client status may override status replicated from other servers
        // When this rule is executed, the instance state is UP or OUT_OF_SERVICE
        // This rule executes logic: if the cluster node is not synchronous replication, check the instance status of the corresponding instance of the local registry
        // If the instance status exists and is UP or OUT_OF_SERVICE, the corresponding status is returned directly
        // If the instance state exists and is not UP or OUT_OF_SERVICE, the next rule is required
        // If the instance state does not exist, the next rule processing is required
        if(! isReplication) { InstanceInfo.InstanceStatus existingStatus =null;
            if(existingLease ! =null) {
                existingStatus = existingLease.getHolder().getStatus();
            }
            // Allow server to have its way when the status is UP or OUT_OF_SERVICE
            if((existingStatus ! =null)
                    && (InstanceInfo.InstanceStatus.OUT_OF_SERVICE.equals(existingStatus)
                    || InstanceInfo.InstanceStatus.UP.equals(existingStatus))) {
                logger.debug("There is already an existing lease with status {} for instance {}",
                        existingLease.getHolder().getStatus().name(),
                        existingLease.getHolder().getId());
                returnStatusOverrideResult.matchingStatus(existingLease.getHolder().getStatus()); }}return StatusOverrideResult.NO_MATCH;
    }

    @Override
    public String toString(a) {
        returnLeaseExistsRule.class.getName(); }}public class AlwaysMatchInstanceStatusRule implements InstanceStatusOverrideRule {
    private static final Logger logger = LoggerFactory.getLogger(AlwaysMatchInstanceStatusRule.class);

    @Override
    public StatusOverrideResult apply(InstanceInfo instanceInfo,
                                      Lease<InstanceInfo> existingLease,
                                      boolean isReplication) {
        // Directly returns the status of the corresponding client instance
        logger.debug("Returning the default instance status {} for instance {}", instanceInfo.getStatus(),
                instanceInfo.getId());
        return StatusOverrideResult.matchingStatus(instanceInfo.getStatus());
    }

    @Override
    public String toString(a) {
        returnAlwaysMatchInstanceStatusRule.class.getName(); }}Copy the code
  • Summary:
    • DownOrStartingRule: If the state sent by the client instance is DOWN or STARTING, the client is considered trusted. Otherwise, the client cannot be trusted to match the next rule. Because the status of the client instance in the server registry can be changed using the Actuator. The instance state of the maintenance instance in the server registry is not necessarily the true state of the client instance.
    • OverrideExistsRule: if the server client instance corresponding overriddenStatus overriddenInstanceStatusMap, is considered to be credible, otherwise don’t credible rule processing next. Because through physical changes to the client instance on the server in the registry, will be cached overriddenInstanceStatusMap.
    • LeaseExistsRule: If the cluster nodes are not synchronized to each other and the client instance status is UP or OUT_OF_SERVICE exists on the server, this rule is trusted. Otherwise, this rule is not trusted and is processed in the next step.
    • AlwaysMatchInstanceStatusRule: the default rules, direct return to the corresponding client instance state.

4. To summarize

Note: This summary is for synchronizing the registry at server startup, but some of the above analysis covers other cases

  • First, all the instance information pulled locally from the cluster node is retrieved, and then the registry is traversed to the local registry
  • During registration, new service lease information and instance lease information are saved to the local registry
  • The expected number of received instance heartbeat renewal requests per minute is updated for the self-protection mechanism of the server
  • Then the corresponding instance information is added into the latest registration queue and the latest change queue, and the instance status is matched and recorded according to the rules
  • Finally, the corresponding cache (the response cache, used by the server to handle pull registry requests) is invalidated