Eureka Server source code startup process and REST interface analysis

Posted by victordb on Wed, 05 Jan 2022 01:50:16 +0100

catalogue

EurekaBootStrap

initEurekaEnvironment

initEurekaServerContext

ApplicationInfoManager

Create EurekaClient

Create a registry of application instance information

Initialize EurekaServerContext

EurekaBootStrap

Eureka server # startup entry: this class implements ServletContextListener. When the Servlet container (such as Tomcat and Jetty) starts, it calls #contextInitialized() method.

    @Override
    public void contextInitialized(ServletContextEvent event) {
        try {
            // Initialize Eureka server configuration environment
            initEurekaEnvironment();
            // Initialize Eureka server context
            initEurekaServerContext();

            ServletContext sc = event.getServletContext();
            sc.setAttribute(EurekaServerContext.class.getName(), serverContext);
        } catch (Throwable e) {
            logger.error("Cannot bootstrap eureka server :", e);
            throw new RuntimeException("Cannot bootstrap eureka server :", e);
        }
    }

Let's take a look at the specific operations of these two steps in turn

initEurekaEnvironment

protected void initEurekaEnvironment() throws Exception {
        logger.info("Setting the eureka configuration..");

        // Get data center
        String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
        if (dataCenter == null) {
            logger.info("Eureka data center value eureka.datacenter is not set, defaulting to default");
            ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
        } else {
            ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
        }
        // Obtain environmental information
        String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT);
        if (environment == null) {
            ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
            logger.info("Eureka environment value eureka.environment is not set, defaulting to test");
        }
    }

It mainly initializes the environment information. There is not much content. Let's take a look at the steps of initializing the context

initEurekaServerContext

protected void initEurekaServerContext() throws Exception {
        EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();

        // For backward compatibility
        JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
        XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);

        // Create a service decoder according to the server configuration
        logger.info("Initializing the eureka client...");
        logger.info(eurekaServerConfig.getJsonCodecName());
        ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig);

        ApplicationInfoManager applicationInfoManager = null;
        // Eureka client is embedded in Eureka server to communicate with other nodes in Eureka server cluster
        if (eurekaClient == null) {
            EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
                    ? new CloudInstanceConfig()
                    : new MyDataCenterInstanceConfig();
            
            applicationInfoManager = new ApplicationInfoManager(
                    instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
            
            EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
            /**
             * eureka server Eureka client is also a discovery client
             */
            eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
        } else {
            applicationInfoManager = eurekaClient.getApplicationInfoManager();
        }
        // Create a registry of application instance information
        PeerAwareInstanceRegistry registry;
        if (isAws(applicationInfoManager.getInfo())) {
            registry = new AwsInstanceRegistry(
                    eurekaServerConfig,
                    eurekaClient.getEurekaClientConfig(),
                    serverCodecs,
                    eurekaClient
            );
            awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager);
            awsBinder.start();
        } else {
            registry = new PeerAwareInstanceRegistryImpl(
                    eurekaServerConfig,
                    eurekaClient.getEurekaClientConfig(),
                    serverCodecs,
                    eurekaClient
            );
        }
        // Create Eureka server cluster node collection
        PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes(
                registry,
                eurekaServerConfig,
                eurekaClient.getEurekaClientConfig(),
                serverCodecs,
                applicationInfoManager
        );
        // Create Eureka server context (provide initialization, shutdown, acquisition and other methods)
        serverContext = new DefaultEurekaServerContext(
                eurekaServerConfig,
                serverCodecs,
                registry,
                peerEurekaNodes,
                applicationInfoManager
        );

        // Initialize EurekaServerContextHolder and use it to easily obtain the context of the server
        EurekaServerContextHolder.initialize(serverContext);
        // During initialization, the client instance information registered by other server s is obtained through the http request initiated by the creation thread when creating the remoteRegionRegistry (which should also be related to other mechanisms)
        serverContext.initialize();
        logger.info("Initialized server context");

        // Copy registry from neighboring eureka node pull registration information from other Eureka servers
        int registryCount = registry.syncUp();
        registry.openForTraffic(applicationInfoManager, registryCount);

        // Register all monitoring statistics.  Registration monitoring
        EurekaMonitors.registerAllStats();
    }

The whole process and details of initializing EurekaServer are many, which are analyzed little by little:

ApplicationInfoManager

Note 1 ⃣ ️:

First, create application management classes according to EurekaInstanceConfig and InstanceInfo. These two classes constitute ApplicationInfoManager as attributes. Therefore, mainly look at the creation code of InstanceInfo through eurekainconfigbasedinstanceinfoprovider (instanceconfig) Get() to get instance information:

Firstly, the builder of lease class is created according to the heartbeat interval (30 seconds by default) and renewal expiration time (90 seconds by default) of the configuration file. Lease class is the basis for service renewal and server expiration application. Subsequently, the instance attribute is assigned through the setting data of the configuration file. If the instance ID is configured, the configuration is read. Otherwise, the host name is used as the instance ID.

@Override
    public synchronized InstanceInfo get() {
        if (instanceInfo == null) {
            // Build the lease information to be passed to the server based on config build the lease information to be passed to the server according to eureka's Client configuration
            LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder.newBuilder()
                    .setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds())
                    .setDurationInSecs(config.getLeaseExpirationDurationInSeconds());

            if (vipAddressResolver == null) {
                vipAddressResolver = new Archaius1VipAddressResolver();
            }

            // Builder the instance information to be registered with eureka server
            InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder(vipAddressResolver);

            // set the appropriate id for the InstanceInfo, falling back to datacenter Id if applicable, else hostname set the instanceId, if configured, read the configuration, otherwise take the current hostname as the instance ID
            String instanceId = config.getInstanceId();
            if (instanceId == null || instanceId.isEmpty()) {
                DataCenterInfo dataCenterInfo = config.getDataCenterInfo();
                if (dataCenterInfo instanceof UniqueIdentifier) {
                    instanceId = ((UniqueIdentifier) dataCenterInfo).getId();
                } else {
                    instanceId = config.getHostName(false);
                }
            }
            // Set the client's default address, hostname or ID address
            String defaultAddress;
            if (config instanceof RefreshableInstanceConfig) {
                // Refresh AWS data center info, and return up to date address
                defaultAddress = ((RefreshableInstanceConfig) config).resolveDefaultAddress(false);
            } else {
                defaultAddress = config.getHostName(false);
            }

            // fail safe
            if (defaultAddress == null || defaultAddress.isEmpty()) {
                defaultAddress = config.getIpAddress();
            }

            builder.setNamespace(config.getNamespace())
                    .setInstanceId(instanceId)
                    .setAppName(config.getAppname())
                    .setAppGroupName(config.getAppGroupName())
                    .setDataCenterInfo(config.getDataCenterInfo())
                    .setIPAddr(config.getIpAddress())
                    .setHostName(defaultAddress)
                    .setPort(config.getNonSecurePort())
                    .enablePort(PortType.UNSECURE, config.isNonSecurePortEnabled())
                    .setSecurePort(config.getSecurePort())
                    .enablePort(PortType.SECURE, config.getSecurePortEnabled())
                    .setVIPAddress(config.getVirtualHostName())
                    .setSecureVIPAddress(config.getSecureVirtualHostName())
                    .setHomePageUrl(config.getHomePageUrlPath(), config.getHomePageUrl())
                    .setStatusPageUrl(config.getStatusPageUrlPath(), config.getStatusPageUrl())
                    .setASGName(config.getASGName())
                    .setHealthCheckUrls(config.getHealthCheckUrlPath(),
                            config.getHealthCheckUrl(), config.getSecureHealthCheckUrl());


            // Start off with the STARTING state to avoid traffic
            if (!config.isInstanceEnabledOnit()) {
                InstanceStatus initialStatus = InstanceStatus.STARTING;
                LOG.info("Setting initial instance status as: {}", initialStatus);
                builder.setStatus(initialStatus);
            } else {
                LOG.info("Setting initial instance status as: {}. This may be too early for the instance to advertise "
                         + "itself as available. You would instead want to control this via a healthcheck handler.",
                         InstanceStatus.UP);
            }

            // Add any user specific metadata information
            for (Map.Entry<String, String> mapEntry : config.getMetadataMap().entrySet()) {
                String key = mapEntry.getKey();
                String value = mapEntry.getValue();
                // only add the metadata if the value is present
                if (value != null && !value.isEmpty()) {
                    builder.add(key, value);
                }
            }

            instanceInfo = builder.build();
            instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
        }
        return instanceInfo;
    }

Create EurekaClient

Then use eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig); Using the configuration of management class and eurekaclient to create eurekaclient, you can see that, like eurekaclient, the current server instance is registered and renewed in the registration center by creating the DiscoveryClient class. It can also be seen from here that there is no primary or secondary server instance, and each server also registers itself in the registration center as a client.

Create a registry of application instance information

Subsequently, create the registry through PeerAwareInstanceRegistryImpl

public PeerAwareInstanceRegistryImpl(
            EurekaServerConfig serverConfig,
            EurekaClientConfig clientConfig,
            ServerCodecs serverCodecs,
            EurekaClient eurekaClient
    ) {
        super(serverConfig, clientConfig, serverCodecs);
        this.eurekaClient = eurekaClient;
        this.numberOfReplicationsLastMin = new MeasuredRate(1000 * 60 * 1);
        // We first check if the instance is STARTING or DOWN, then we check explicit overrides,
        // then we check the status of a potentially existing lease.
        this.instanceStatusOverrideRule = new FirstMatchWinsCompositeRule(new DownOrStartingRule(),
                new OverrideExistsRule(overriddenInstanceStatusMap), new LeaseExistsRule());
    }

Initialize the attribute by calling the implementation of the parent class, and then create an instance state override rule. During initialization, three override rules are given. When none is satisfied, the default override rule execution result is returned.

Role of coverage status:

Call Eureka Server HTTP restful interface app / ${app_name} / ${install_id} / status to overwrite the change of application instance status, so as to actively and forcibly change the application instance status. Note that the status of the Eureka client application instance is not really modified, but the status of the application instance registered with Eureka server.

In this way, when Eureka client obtains the registration information, it configures Eureka Shouldfilteronlyuppinstances = true to filter out non {instancestatus Up the application instance of the application instance, so as to avoid transferring the instance, so as to suspend the service of the application instance (InstanceStatus.OUT_OF_SERVICE) without closing the application instance.

Therefore, in most cases, the purpose of calling this interface is to switch the application instance status between (InstanceStatus.UP) and (InstanceStatus.OUT_OF_SERVICE).

1, Override status rule:

Downorstarting rule: when the current instance state is not UP or out_ OF_ During service, execute the current rule and directly return the status of the current instance as the coverage status,

public class DownOrStartingRule implements InstanceStatusOverrideRule {

    @Override
    public StatusOverrideResult apply(InstanceInfo instanceInfo,
                                      Lease<InstanceInfo> existingLease,
                                      boolean isReplication) {
        /**
         * If the instance status is not running or suspended (starting or offline), it is not suitable for providing services and does not match
         */
        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;
    }

2, Overrideexistsrule: if the current instance state overrides the data, the existing override state is used as the current override state

3, Leaseexistsrule: when a non Server request is made, the nstancestatus of the application instance that matches the existing lease OUT_ OF_ Service or instanceinfo InstanceStatus. Up state

4, Default rule (AlwaysMatchInstanceStatusRule): always return the status of the current instance as the override status.

super(serverConfig, clientConfig, serverCodecs)

Next, let's look at what has been done in super. First, assign the attribute, create the recently cancelled and registered queue, and set the thread of getDeltaRetentionTask() to execute regularly at the configured time interval (30 seconds by default). The run() method of getDeltaRetentionTask is to traverse the recently changed queue information. If the instance update time in the queue exceeds a certain period of time (three minutes by default), it will be removed from the recently changed queue. When the client side initiates incremental acquisition of registration information, the recentlyChangedQueue is used to calculate the increment of the latest time and return it to the client side.

protected AbstractInstanceRegistry(EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs) {
        this.serverConfig = serverConfig;
        this.clientConfig = clientConfig;
        this.serverCodecs = serverCodecs;
        this.recentCanceledQueue = new CircularQueue<Pair<Long, String>>(1000);
        this.recentRegisteredQueue = new CircularQueue<Pair<Long, String>>(1000);

        this.renewsLastMin = new MeasuredRate(1000 * 60 * 1);

        /**
         * 30 Perform cleanup once per second
         */
        this.deltaRetentionTimer.schedule(getDeltaRetentionTask(),
                serverConfig.getDeltaRetentionTimerIntervalInMs(),
                serverConfig.getDeltaRetentionTimerIntervalInMs());
    }

private TimerTask getDeltaRetentionTask() {
        return new TimerTask() {

            @Override
            public void run() {
                Iterator<RecentlyChangedItem> it = recentlyChangedQueue.iterator();
                while (it.hasNext()) {
                    // Remove the data whose update time exceeds the current 3 minutes from the queue
                    if (it.next().getLastUpdateTime() <
                            System.currentTimeMillis() - serverConfig.getRetentionTimeInMSInDeltaQueue()) {
                        it.remove();
                    } else {
                        break;
                    }
                }
            }

        };
    }

The following steps are to create peereeurekanodes (Eureka server cluster node set) and Eureka server context (Eureka server context), put the context into the holder for easy access, and then go to the node that initializes the context. Focus on this

Initialize EurekaServerContext

The initialization code mainly does two things: start the newly created server cluster node set and initialize the application instance information registry.

public void initialize() {
        logger.info("Initializing ...");
        // Start Eureka server cluster node collection (cluster replication)
        peerEurekaNodes.start();
        try {
            // Initializes the registry of application instance information
            registry.init(peerEurekaNodes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        logger.info("Initialized");
    }

Start the Server cluster node collection

start(): first, create a scheduled task, then update the cluster node information, and create a class that implements the run method. The code of run() mainly updates the cluster node information, and then hand over the class to the scheduled task to execute every certain time. This ensures that the cluster information of the server is obtained during initialization, and a request is initiated every interval to update the purpose of the local registry server instance.

public void start() {
        // Create scheduled task
        taskExecutor = Executors.newSingleThreadScheduledExecutor(
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
                        thread.setDaemon(true);
                        return thread;
                    }
                }
        );
        try {
            // Initialize cluster node information
            updatePeerEurekaNodes(resolvePeerUrls());
            Runnable peersUpdateTask = new Runnable() {
                @Override
                public void run() {
                    try {
                        // Update cluster node information
                        updatePeerEurekaNodes(resolvePeerUrls());
                    } catch (Throwable e) {
                        logger.error("Cannot update the replica Nodes", e);
                    }

                }
            };
            /**
             * Scheduled task setting execution interval (default 10 minutes)
             */
            taskExecutor.scheduleWithFixedDelay(
                    peersUpdateTask,
                    serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                    serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                    TimeUnit.MILLISECONDS
            );
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        for (PeerEurekaNode node : peerEurekaNodes) {
            logger.info("Replica node URL:  {}", node.getServiceUrl());
        }
    }

Here's how the server initializes and updates cluster node information

resolvePeerUrls(): get the current instance information and visible area, and then resolve all server service URLS according to DNS or configuration information through getDiscoveryServiceUrls(). All other server services except the current server instance information.

protected List<String> resolvePeerUrls() {
        /**
         * Get current instance information and visible area
         */
        InstanceInfo myInfo = applicationInfoManager.getInfo();
        String zone = InstanceInfo.getZone(clientConfig.getAvailabilityZones(clientConfig.getRegion()), myInfo);
        // Gets a list of all eureka service URLs that the eureka client talks to
        List<String> replicaUrls = EndpointUtils
                .getDiscoveryServiceUrls(clientConfig, zone, new EndpointUtils.InstanceInfoBasedUrlRandomizer(myInfo));

        /**
         * Remove the URL itself, and the rest needs to be synchronized
         */
        int idx = 0;
        while (idx < replicaUrls.size()) {
            if (isThisMyUrl(replicaUrls.get(idx))) {
                replicaUrls.remove(idx);
            } else {
                idx++;
            }
        }
        return replicaUrls;
    }

public static List<String> getDiscoveryServiceUrls(EurekaClientConfig clientConfig, String zone, ServiceUrlRandomizer randomizer) {
        /**
         * Resolve all server services urls according to DNS or configuration information
         */
        boolean shouldUseDns = clientConfig.shouldUseDnsForFetchingServiceUrls();
        if (shouldUseDns) {
            return getServiceUrlsFromDNS(clientConfig, zone, clientConfig.shouldPreferSameZoneEureka(), randomizer);
        }
        return getServiceUrlsFromConfig(clientConfig, zone, clientConfig.shouldPreferSameZoneEureka());
    }

Update cluster node collection

The latest server node set has been obtained above. All new servers are removed from the previous server node, and the remaining servers are the servers to be offline. The latest server removes all previous server node sets, which is the new server node set this time.

If no new or offline server node is added this time, no operation will be performed. If the offline node set exists, shut down the offline eurekaNode and close the node's thread pool (cluster synchronization, explained below); If the new node set exists, create a node and add it to the temporary variable newNodeList, and then assign a value to the current server cluster node set. During the first initialization, the parsed servers are all new nodes to complete the property filling of the peereeurekanodes collection.

protected void updatePeerEurekaNodes(List<String> newPeerUrls) {
        if (newPeerUrls.isEmpty()) {
            logger.warn("The replica size seems to be empty. Check the route 53 DNS Registry");
            return;
        }

        /**
         * Calculate the list of services to be offline and added
         */
        Set<String> toShutdown = new HashSet<>(peerEurekaNodeUrls);
        toShutdown.removeAll(newPeerUrls);
        Set<String> toAdd = new HashSet<>(newPeerUrls);
        toAdd.removeAll(peerEurekaNodeUrls);

        /**
         * If you don't need to go offline or add, you can return directly
         */
        if (toShutdown.isEmpty() && toAdd.isEmpty()) { // No change
            return;
        }

        // Remove peers no long available
        List<PeerEurekaNode> newNodeList = new ArrayList<>(peerEurekaNodes);

        /**
         * Offline deletion
         */
        if (!toShutdown.isEmpty()) {
            logger.info("Removing no longer available peer nodes {}", toShutdown);
            int i = 0;
            while (i < newNodeList.size()) {
                PeerEurekaNode eurekaNode = newNodeList.get(i);
                if (toShutdown.contains(eurekaNode.getServiceUrl())) {
                    newNodeList.remove(i);
                    eurekaNode.shutDown();
                } else {
                    i++;
                }
            }
        }

        /**
         * Add to peereeurekanodes
         */
        // Add new peers
        if (!toAdd.isEmpty()) {
            logger.info("Adding new peer nodes {}", toAdd);
            for (String peerUrl : toAdd) {
                newNodeList.add(createPeerEurekaNode(peerUrl));
            }
        }

        this.peerEurekaNodes = newNodeList;
        this.peerEurekaNodeUrls = new HashSet<>(newPeerUrls);
    }

Initializes the registry of application instance information

Initialize the registration information according to the newly created cluster node set: the role of each step has been commented below, and the main codes are analyzed below

public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
        // Start the timing task of speed measurement and empty it every minute to realize speed measurement
        this.numberOfReplicationsLastMin.start();
        // Attribute assignment
        this.peerEurekaNodes = peerEurekaNodes;
        // Initialize response cache
        initializedResponseCache();
        // Update the renewal threshold through scheduled tasks at regular intervals
        scheduleRenewalThresholdUpdateTask();
        // Initialize remote server registration information
        initRemoteRegionRegistry();

        try {
            // Monitor registration
            Monitors.registerObject(this);
        } catch (Throwable e) {
            logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", e);
        }
    }

Initialize response cache

1. Create cache data with default size of 1000 and expiration of 180 seconds through CacheBuilder

2. If the current instance allows the use of readonly cache, start the scheduled task to update the data of readonlycahemap every 30 seconds. The implementation is to traverse readonlycahemap. If the value value is different from that of readWriteCacheMap, assign the latest value in readWriteCacheMap to readonly cache, so as to ensure that readonly data will not exist dirty data for too long.

ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
        this.serverConfig = serverConfig;
        this.serverCodecs = serverCodecs;
        this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();
        this.registry = registry;

        long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();
        // guava cache default capacity 1000180 seconds expired
        this.readWriteCacheMap =
                CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache())
                        .expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
                        .removalListener(new RemovalListener<Key, Value>() {
                            @Override
                            public void onRemoval(RemovalNotification<Key, Value> notification) {
                                Key removedKey = notification.getKey();
                                if (removedKey.hasRegions()) {
                                    Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
                                    regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
                                }
                            }
                        })
                        .build(new CacheLoader<Key, Value>() {
                            @Override
                            public Value load(Key key) throws Exception {
                                if (key.hasRegions()) {
                                    Key cloneWithNoRegions = key.cloneWithoutRegions();
                                    regionSpecificKeys.put(cloneWithNoRegions, key);
                                }
                                Value value = generatePayload(key);
                                return value;
                            }
                        });

        if (shouldUseReadOnlyResponseCache) {
            // Initialize scheduled tasks. Configure Eureka Responsecacheupdateintervalms: sets the task execution frequency. The default value is 30 * 1000 milliseconds
            // Responsible for updating the data in readwriterMap
            timer.schedule(getCacheUpdateTask(),
                    new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
                            + responseCacheUpdateIntervalMs),
                    responseCacheUpdateIntervalMs);
        }

        try {
            Monitors.registerObject(this);
        } catch (Throwable e) {
            logger.warn("Cannot register the JMX monitor for the InstanceRegistry", e);
        }
    }

The filling of readOnly data is to read the data through the response cache when other services initiate full, incremental and application information acquisition. If there is a cache in readOnlyMap, it will be returned directly. Otherwise, read readWriterCacheMap and assign the return value to readOnlyMap to avoid frequent calls to readWriterCacheMap.

Value getValue(final Key key, boolean useReadOnlyCache) {
        Value payload = null;
        try {
            if (useReadOnlyCache) {
                // Read readonlycahemap first. Cannot read. Read readWriteCacheMap and set it to readOnlyCacheMap
                final Value currentPayload = readOnlyCacheMap.get(key);
                if (currentPayload != null) {
                    payload = currentPayload;
                } else {
                    // Reset after expiration (the default is 180 seconds)
                    payload = readWriteCacheMap.get(key);
                    readOnlyCacheMap.put(key, payload);
                }
            } else {
                // Read readWriteCacheMap
                payload = readWriteCacheMap.get(key);
            }
        } catch (Throwable t) {
            logger.error("Cannot get value for key : {}", key, t);
        }
        return payload;
    }

So why do you do this to avoid frequent calls to readWriterCacheMap? readWriterCacheMap has been a cache with a capacity of 1000 for recording three minutes. When it misses the cache, you need to obtain specific data through the generatePayload() method. See the following code for the specific code:

 .build(new CacheLoader<Key, Value>() {
                            @Override
                            public Value load(Key key) throws Exception {
                                if (key.hasRegions()) {
                                    Key cloneWithNoRegions = key.cloneWithoutRegions();
                                    regionSpecificKeys.put(cloneWithNoRegions, key);
                                }
                                Value value = generatePayload(key);
                                return value;
                            }
                        });

// Service payload
private Value generatePayload(Key key) {
        Stopwatch tracer = null;
        try {
            String payload;
            switch (key.getEntityType()) {
                case Application:
                    boolean isRemoteRegionRequested = key.hasRegions();

                    if (ALL_APPS.equals(key.getName())) {
                        if (isRemoteRegionRequested) {
                            tracer = serializeAllAppsWithRemoteRegionTimer.start();
                            payload = getPayLoad(key, registry.getApplicationsFromMultipleRegions(key.getRegions()));
                        } else {
                            // Full acquisition
                            tracer = serializeAllAppsTimer.start();
                            // Construct the instance data to be cached according to the registered instance collection and key
                            payload = getPayLoad(key, registry.getApplications());
                        }
                    } else if (ALL_APPS_DELTA.equals(key.getName())) {
                        // Increment (to be completed)
                        if (isRemoteRegionRequested) {
                            tracer = serializeDeltaAppsWithRemoteRegionTimer.start();
                            versionDeltaWithRegions.incrementAndGet();
                            versionDeltaWithRegionsLegacy.incrementAndGet();
                            payload = getPayLoad(key,
                                    registry.getApplicationDeltasFromMultipleRegions(key.getRegions()));
                        } else {
                            tracer = serializeDeltaAppsTimer.start();
                            versionDelta.incrementAndGet();
                            versionDeltaLegacy.incrementAndGet();
                            payload = getPayLoad(key, registry.getApplicationDeltas());
                        }
                    } else {
                        tracer = serializeOneApptimer.start();
                        payload = getPayLoad(key, registry.getApplication(key.getName()));
                    }
                    break;
                case VIP:
                case SVIP:
                    tracer = serializeViptimer.start();
                    payload = getPayLoad(key, getApplicationsForVip(key, registry));
                    break;
                default:
                    logger.error("Unidentified entity type: {} found in the cache key.", key.getEntityType());
                    payload = "";
                    break;
            }
            return new Value(payload);
        } finally {
            if (tracer != null) {
                tracer.stop();
            }
        }
    }

The corresponding operations are completed according to different request types. There are many types. Only the full quantity is used as an example for parsing. The other implementations are similar: getPayLoad(): decode the data into a String through the server decoder, return it to the client, and ignore it. getApplicationsFromMultipleRegions(): add monitoring data, which can be ignored. Then read the registry information of the current server and add the lease instance information to the temporary variable apps. At this time, all the instance information of the current server has been obtained, but the local registry of the current server may not be the latest registration information due to network reasons, Therefore, the locally cached registry information of other servers (cached when the server cluster is synchronized, as explained below) is also added to the apps. The remote registry cache data is circulated. The apps obtains the app name. If it does not exist, the app information is created and all instances are added. Because the map and set data structures receive the instance information, there will be no multiple instances after repeated registration, Data duplication. After all apps are obtained, all instance States and state corresponding data are put into the map, and the hashcode of the application set is generated through the natural sorting of hashmap. Hashcode example: DOWN_2_UP_8_.

public Applications getApplicationsFromMultipleRegions(String[] remoteRegions) {

        boolean includeRemoteRegion = null != remoteRegions && remoteRegions.length != 0;

        logger.debug("Fetching applications registry with remote regions: {}, Regions argument {}",
                includeRemoteRegion, remoteRegions);
        // Add monitoring data of corresponding instruction
        if (includeRemoteRegion) {
            GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS.increment();
        } else {
            GET_ALL_CACHE_MISS.increment();
        }
        // Gets the collection of all application instances of the current server
        Applications apps = new Applications();
        apps.setVersion(1L);
        for (Entry<String, Map<String, Lease<InstanceInfo>>> entry : registry.entrySet()) {
            Application app = null;

            if (entry.getValue() != null) {
                for (Entry<String, Lease<InstanceInfo>> stringLeaseEntry : entry.getValue().entrySet()) {
                    Lease<InstanceInfo> lease = stringLeaseEntry.getValue();
                    if (app == null) {
                        app = new Application(lease.getHolder().getAppName());
                    }
                    app.addInstance(decorateInstanceInfo(lease));
                }
            }
            if (app != null) {
                apps.addApplication(app);
            }
        }
        // Add other active server registration data to the collection in turn
        if (includeRemoteRegion) {
            for (String remoteRegion : remoteRegions) {
                // Guess: should there be instance information cache of other clusters during cluster synchronization?
                RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion);
                if (null != remoteRegistry) {
                    Applications remoteApps = remoteRegistry.getApplications();
                    for (Application application : remoteApps.getRegisteredApplications()) {
                        if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) {
                            logger.info("Application {}  fetched from the remote region {}",
                                    application.getName(), remoteRegion);

                            Application appInstanceTillNow = apps.getRegisteredApplications(application.getName());
                            if (appInstanceTillNow == null) {
                                appInstanceTillNow = new Application(application.getName());
                                apps.addApplication(appInstanceTillNow);
                            }
                            for (InstanceInfo instanceInfo : application.getInstances()) {
                                appInstanceTillNow.addInstance(instanceInfo);
                            }
                        } else {
                            logger.debug("Application {} not fetched from the remote region {} as there exists a "
                                            + "whitelist and this app is not in the whitelist.",
                                    application.getName(), remoteRegion);
                        }
                    }
                } else {
                    logger.warn("No remote registry available for the remote region {}", remoteRegion);
                }
            }
        }
        // Set the application set hashcode, which can be used for subsequent matching to verify whether it has been changed (this variable is used to verify whether the registration information obtained incrementally is consistent (complete) with the full registration information of Eureka server)
        apps.setAppsHashCode(apps.getReconcileHashCode());
        return apps;
    }

Update renewal threshold:

Jump out and then initialize the registry. Next, the renewal threshold will be updated through a scheduled task.

The self-protection threshold of the current server is reset by executing a scheduled task every 15 minutes. First, obtain the values of all instances of the current server. When the data is greater than the expected number of clients * 0.85, or the self-protection mechanism is not enabled, update the expected client values and change the minimum threshold for renewal per minute. When the protection mechanism is enabled, if the running active instance data is less than the expected client * 0.85, no operation will be carried out. This is also the implementation of the server self-protection mechanism. If too many services are offline during this time period of the server task, the server will automatically carry out the self-protection mechanism. If the expected client value and the renewal threshold per minute are not modified, when the eviction instance timing task runs, the renewal of other services expires. The server judges that the number of instances is too small to protect the current registry information and will not evict (analyzed below).

 private void scheduleRenewalThresholdUpdateTask() {
        timer.schedule(new TimerTask() {
                           @Override
                           public void run() {
                               updateRenewalThreshold();
                           }
                       }, serverConfig.getRenewalThresholdUpdateIntervalMs(),
                serverConfig.getRenewalThresholdUpdateIntervalMs());
    }

private void updateRenewalThreshold() {
        try {
            // Calculate the number of application instances
            Applications apps = eurekaClient.getApplications();
            int count = 0;
            for (Application app : apps.getRegisteredApplications()) {
                for (InstanceInfo instance : app.getInstances()) {
                    if (this.isRegisterable(instance)) {
                        ++count;
                    }
                }
            }
            // If count > last instance count * 0.85, (the server does not enter the self-protection mechanism) or automatic protection is not enabled, update the instance count
            // After entering the self-protection mechanism, the current registry instance will be protected
            synchronized (lock) {
                // Update threshold only if the threshold is greater than the
                // current expected threshold or if self preservation is disabled.
                if ((count) > (serverConfig.getRenewalPercentThreshold() * expectedNumberOfClientsSendingRenews)
                        // Self protection mechanism configuration is not enabled
                        || (!this.isSelfPreservationModeEnabled())) {
                    this.expectedNumberOfClientsSendingRenews = count;
                    updateRenewsPerMinThreshold();
                }
            }
            logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
        } catch (Throwable e) {
            logger.error("Cannot update renewal threshold", e);
        }
    }

Unfinished update tomorrow

Topics: eureka Cloud Native