1, Eureka Client's work
- Application startup phase
- Read the configuration information interacting with Eureka Server and package it into Eureka clientconfig
- Read its own service instance configuration information and package it into EurekaInstanceConfig
- Pull the registry information from Eureka Server and cache it locally
- Service registration
- Initialize the scheduled tasks of sending heartbeat, cache refresh (pull registry information to update local cache) and on-demand registration (monitor the change of service instance information, decide whether to re initiate registration, and update service instance metadata in the registry)
- Application execution phase
- Regularly send heartbeat to Eureka Server and maintain the lease in the registry.
- Periodically pull the registry information from Eureka Server and update the local registry cache.
- Monitor the change of the application's own information. If it changes, it is necessary to re initiate the service registration.
- Application destruction phase
- Unregister its own service instance from Eureka Server.
2, Source code analysis
Eureka Client introduces dependencies through Starter. Spring Boot will use the following automatic configuration classes for the project:
1. EurekaClientAutoConfiguration: Eureka Client automatic configuration class, which is responsible for the configuration and initialization of key Beans in Eureka Client, such as ApplicationInfoManager and EurekaClientConfig.
2. Ribbon Eureka autoconfiguration: configuration related to ribbon load balancing.
3. Eureka discovery client configuration: configure the health checker for automatic registration and application.
2.1 read application configuration information
Spring Boot helps Eureka Client read and configure the properties of many necessary beans through Eureka discoveryclientconfig configuration class. Now list the property reading and configuration classes in Eureka discoveryclientconfiguration.
Class name | Role and introduction |
EurekaClientConfig | Encapsulate the configuration information required for the interaction between Eureka Client and Eureka Server. Spring Cloud provides EurekaClientConfigBean with a default configuration class. You can use the prefix Eureka. Config in the configuration file Client + attribute name for attribute override |
ApplicationInfoManager | As the application information manager, it manages the information class InstanceInfo of the service instance and the configuration information class EurekaInstanceConfig of the service instance |
InstanceInfo | Encapsulates the service instance metadata that will be sent to Eureka Server for service registration. It represents a service instance in the registration of Eureka Server. Other service instances can know the relevant information of the service instance through InstanceInfo to initiate service requests |
EurekaInstanceConfig | Encapsulates the configuration information of Eureka Client's own service instance, which is mainly used to build InstanceInfo. Usually, this information is contained in eureka.com in the configuration file Set under the instance prefix. Spring Cloud provides the default configuration through the EurekaInstanceConfigBean configuration class |
DiscoveryClient | The Spring Cloud defines a client interface for service discovery |
2.2 service discovery client
2.1. 1. Responsibilities of discoveryclient
DiscoveryClient is the core class of Eureka Client, including the key logic for interacting with Eureka Server. It has the following functions:
- Register the service instance to Eureka Server
- Send heartbeat update lease with Eureka Server
- Cancel the lease from Eureka Server when the service is shut down, and the service goes offline
- Query the list of service instances registered in Eureka Server
2.1.2 DiscoveryClient constructor
Initializes timing tasks such as sending heartbeat and cache refresh in the constructor
// com.netflix.discovery.DiscoveryClient#DiscoveryClient() // The corresponding configuration is Eureka Client. Fetch register, true indicates that Eureka Client will pull registry information from Eureka Server if (config.shouldFetchRegistry()) { this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L}); } else { this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC; } // The corresponding configuration is Eureka Client. Register with Eureka, true indicates that Eureka Client will register with Eureka Server if (config.shouldRegisterWithEureka()) { this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L}); } else { this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC; }
Then define a timer thread pool ScheduledExecutorService based on thread pool. The thread pool size is 2. One thread is used to send heartbeat and the other thread is used for cache refresh. At the same time, define the send heartbeat and cache refresh thread pool. The code is as follows:
// com.netflix.discovery.DiscoveryClient#DiscoveryClient() scheduler = Executors.newScheduledThreadPool(2, new ThreadFactoryBuilder() .setNameFormat("DiscoveryClient-%d") .setDaemon(true) .build()); heartbeatExecutor = new ThreadPoolExecutor( 1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactoryBuilder() .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d") .setDaemon(true) .build() ); // use direct handoff cacheRefreshExecutor = new ThreadPoolExecutor(...); // Internal class that encapsulates the Jersey client for http calls eurekaTransport = new EurekaTransport(); scheduleServerEndpointTask(eurekaTransport, args);
Then pull the registry information from Eureka Server. The code is as follows:
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) { fetchRegistryFromBackup(); }
After pulling the registry information in Eureka Server, the service instance will be registered. The code is as follows:
if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) { try { // Initiate service registration if (!register() ) { throw new IllegalStateException("Registration error at startup. Invalid server response."); } } catch (Throwable th) { logger.error("Registration error at startup: {}", th.getMessage()); throw new IllegalStateException(th); } } // Initialize scheduled tasks // finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch initScheduledTasks();
Finally, in the constructor of DiscoveryClient, we mainly do the following things in turn:
- Assignment of related configurations, such as ApplicationInfoManager, EurekaClientConfig, etc
- The initialization of the backup registry is not implemented by default
- Pull the information from the Eureka Server registry
- Preprocessing before registration
- Register yourself with Eureka Server
- Initialize timing tasks such as heartbeat, cache refresh and on-demand registration
2.3 pull registry information
com.netflix.discovery.DiscoveryClient#fetchRegistry
private boolean fetchRegistry(boolean forceFullRegistryFetch) { Stopwatch tracer = FETCH_REGISTRY_TIMER.start(); try { //If incremental pull is disabled or Applications is null, all pull is performed // If the delta is disabled or if it is the first time, get all // applications Applications applications = getApplications(); if (clientConfig.shouldDisableDelta() || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress())) || forceFullRegistryFetch || (applications == null) || (applications.getRegisteredApplications().size() == 0) || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta { // Full pull registry information getAndStoreFullRegistry(); } else { // Incremental pull registry information getAndUpdateDelta(applications); } // Compute application set consistency hash code applications.setAppsHashCode(applications.getReconcileHashCode()); // Print the total number of all service instances on the registry logTotalInstances(); } catch (Throwable e) { logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, e.getMessage(), e); return false; } finally { if (tracer != null) { tracer.stop(); } } // The cache refresh event is pushed before the remote instance state is updated, but Eureka does not provide a default event listener // Notify about cache refresh before updating the instance remote status onCacheRefreshed(); // Update the remote instance status based on the flushed data in the cache // Update remote status based on refreshed data held in the cache updateInstanceRemoteStatus(); // registry was fetched successfully, so return true return true; }
2.3. 1. Pull the registry information in full
com.netflix.discovery.DiscoveryClient#getAndStoreFullRegistry
Interface: http://localhost:20000/eureka/apps/
getAndStoreFullRegistry method may be called by multiple threads at the same time, resulting in the newly pulled registry being overwritten by the old registry and generating dirty data. For this, Eureka tracks the updated version of apps through currentUpdateGeneration of AtomicLong type. If the updated versions are inconsistent, the registration information pulled this time is outdated and does not need to be saved locally. After pulling the registry information, the obtained apps will be filtered, and only the service instance information in UP status will be retained.
2.3. 2. Incremental pull of registry information
com.netflix.discovery.DiscoveryClient#getAndUpdateDelta
Interface: http://localhost:20000/eureka/apps/delta
private void getAndUpdateDelta(Applications applications) throws Throwable { long currentUpdateGeneration = fetchRegistryGeneration.get(); Applications delta = null; EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get()); if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) { delta = httpResponse.getEntity(); } // If the incremental pull fails, the full pull is performed if (delta == null) { logger.warn("The server does not allow the delta revision to be applied because it is not safe. " + "Hence got the full registry."); getAndStoreFullRegistry(); } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) { logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode()); String reconcileHashCode = ""; if (fetchRegistryUpdateLock.tryLock()) { try { // Update local cache updateDelta(delta); // Compute application set consistency hash code reconcileHashCode = getReconcileHashCode(applications); } finally { fetchRegistryUpdateLock.unlock(); } } else { logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta"); } // Compare the application set consistency hash codes. If they are inconsistent, it will be considered that the incremental pull data is dirty, and a full pull will be initiated to update the local registry information // There is a diff in number of instances for some reason if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) { reconcileAndLogDifference(delta, reconcileHashCode); // this makes a remoteCall } } else { logger.warn("Not updating application delta as another thread is updating it already"); logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode()); } }
The general representation of appsHashCode is:
appsHashCode = ${status}_${count}_
By splicing the application status and quantity into a string, it represents the statistical information of the service instance status in the current registry. For a simple example, 10 application instances have the status of UP, 5 application instances have the status of DOWN, and the other status data is 0 (not represented). Then the form of appsHashCode will be:
appsHashCode = UP_10_DOWN_5_
2.4 service registration
com.netflix.discovery.DiscoveryClient#register
Interface: http://localhost:20000/eureka/apps/${APP_NAME}
/** * Register with the eureka service by making the appropriate REST call. */ boolean register() throws Throwable { logger.info(PREFIX + "{}: registering service...", appPathIdentifier); EurekaHttpResponse<Void> httpResponse; try { // Encapsulate its own service instance metadata into InstanceInfo and send it to Eureka Server to request service registration. When Eureka Server returns 204 status code, it indicates that the service registration is successful. httpResponse = eurekaTransport.registrationClient.register(instanceInfo); } catch (Exception e) { logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e); throw e; } if (logger.isInfoEnabled()) { logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode()); } return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode(); }
2.5 initialize scheduled tasks
com.netflix.discovery.DiscoveryClient#initScheduledTasks
In this method, three timer tasks are initialized. One is used to pull the registry information from Eureka Server and refresh the local cache; One is used to send heartbeat to Eureka Server; An operation for on-demand registration. The code is as follows:
/** * Initializes all scheduled tasks. */ private void initScheduledTasks() { if (clientConfig.shouldFetchRegistry()) { // Registry cache refresh timer // Get the refresh interval in the configuration file. The default is 30 seconds. You can use Eureka client. Register fetch interval seconds // registry cache refresh timer int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds(); int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); scheduler.schedule( new TimedSupervisorTask("cacheRefresh", scheduler, cacheRefreshExecutor, registryFetchIntervalSeconds, TimeUnit.SECONDS, expBackOffBound, new CacheRefreshThread() ), registryFetchIntervalSeconds, TimeUnit.SECONDS); } if (clientConfig.shouldRegisterWithEureka()) { int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs(); int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound(); logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs); // Send heartbeat timer. The default is to send a heartbeat every 30 seconds // Heartbeat timer scheduler.schedule( new TimedSupervisorTask("heartbeat", scheduler, heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread() ), renewalIntervalInSecs, TimeUnit.SECONDS); // Register timers on demand.... } else { logger.info("Not registering with Eureka server per configuration"); } }
2.5. 1 register scheduled tasks on demand
The function of on-demand registration timing task is to re initiate a registration request to Eureka Server when the InstanceInfo or status in Eureka Client changes, update the service instance information in the registry, and ensure that the service instance information in the Eureka Server registry is valid and available.
// Register timers on demand.... // InstanceInfo replicator // Regularly check and refresh the service instance information, check whether there are changes and whether re registration is required instanceInfoReplicator = new InstanceInfoReplicator( this, instanceInfo, clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2); // burstSize // Monitor the status change of the application, and initiate re registration in case of any change statusChangeListener = new ApplicationInfoManager.StatusChangeListener() { @Override public String getId() { return "statusChangeListener"; } @Override public void notify(StatusChangeEvent statusChangeEvent) { if (InstanceStatus.DOWN == statusChangeEvent.getStatus() || InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) { // log at warn level if DOWN was involved logger.warn("Saw local status change event {}", statusChangeEvent); } else { logger.info("Saw local status change event {}", statusChangeEvent); } instanceInfoReplicator.onDemandUpdate(); } }; if (clientConfig.shouldOnDemandUpdateStatusChange()) { // Register application status change monitor applicationInfoManager.registerStatusChangeListener(statusChangeListener); } // Start timer to register scheduled tasks on demand instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
2.6 service offline
com.netflix.discovery.DiscoveryClient#shutdown
/** * Shuts down Eureka Client. Also sends a deregistration request to the * eureka server. */ @PreDestroy @Override public synchronized void shutdown() { // Synchronization method if (isShutdown.compareAndSet(false, true)) { logger.info("Shutting down DiscoveryClient ..."); // Atomic operation to ensure that it will be performed only once if (statusChangeListener != null && applicationInfoManager != null) { // Unregister status listener applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId()); } // Cancel scheduled task cancelScheduledTasks(); // If APPINFO was registered if (applicationInfoManager != null && clientConfig.shouldRegisterWithEureka() && clientConfig.shouldUnregisterOnShutdown()) { // Service offline applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN); unregister(); } // Close the Jersey client if (eurekaTransport != null) { eurekaTransport.shutdown(); } // Close the related Monitor heartbeatStalenessMonitor.shutdown(); registryStalenessMonitor.shutdown(); logger.info("Completed shut down of DiscoveryClient"); } }