Eureka-Client registration information to obtain incremental Eureka-Server

1 Overview

This article Share Eureka-Client registration information acquisition process increments to-Server Eureka .

Pre-reading: "Eureka parse the source code - Registration discovery application examples (vi) of get the whole amount."

FROM "depth profiling service discovery component Netflix Eureka"

Eureka-Client for registration information, is divided into the total amount of acquisition and incremental acquisition . Under the default configuration, when the Eureka-Client starts, the first to perform a full amount of acquisition were local cache registration information, and then every 30 Miao incremental acquisition refresh the local cache (not " normal " case will get the whole amount).

This article focuses on the incremental acquisition .

Spring Cloud recommended books :

  • Please support genuine. Download pirated, equal to write low-level initiative BUG .
  • Program ape DD - "Spring Cloud service combat micro"
  • Zhou Li - "Spring Cloud and Docker combat micro Services Architecture"
  • QI bought two books, Jingdong free shipping.

Recommended Spring Cloud Video :

  • Java Micro Services practice - Spring Boot
  • Java Micro Services practice - Spring Cloud
  • Java Micro Services practice - Spring Boot / Spring Cloud

2. Apply a set of consistent hashing code

Applications.appsHashCodeApplication of a set of consistent hashing code .

Incremental fetching set of applications registered (Applications), Eureka-Client will get to:

  1. Eureka-Server Application recent set of changes (registration, offline) of
  2. Eureka-Server application consistent set of hash code

Eureka-Client to change the set of applications and local caching application locally calculated hash code of the application set consistent set of merged. If the two hash codes are equal, meaning increment succeed; if not equal, means that the incremental acquisition has failed, Eureka-Client again and Eureka-Server full amount to obtain application set.

Eureka compare a set of consistent application of the hash code, and everyday we compare whether two objects are equal by similar hash code.

2.1 The formula

appsHashCode = ${status}_${count}_

  • Examples of the use state of each application ( status) + Number ( count) splicing the consistent hashing code. If the number is 0, the state of the application instance without splicing. Sort state string size .
  • For example, eight UP, 0 a DOWN, then appsHashCode = UP_8_. 8 UP, 2 th DOWN, then appsHashCode = DOWN_2_UP_8_.
  • Codes are as follows: // Applications.java public String getReconcileHashCode () {// set count key: application examples status TreeMap <String, AtomicInteger> instanceCountMap = new TreeMap <String, AtomicInteger> (); populateInstanceCountMap (instanceCountMap); // calculate hashcode return getReconcileHashCode (instanceCountMap);}
    • Piece count code Integer can be used, without using AtomicInteger.
    • Call the #populateInstanceCountMap()method, calculating the number of instances of each application state. Codes are as follows: // Applications.java public void populateInstanceCountMap (Map <String, AtomicInteger> instanceCountMap) {for (Application app: this.getRegisteredApplications ()) {for (InstanceInfo info: app.getInstancesAsIsFromEureka ()) {// count AtomicInteger instanceCount = instanceCountMap.computeIfAbsent (info.getStatus () name (), k -> new AtomicInteger (0).); instanceCount.incrementAndGet ();}}} public List <Application> getRegisteredApplications () {return new ArrayList <Application> (this.applications);} // Applications.java public List <InstanceInfo> getInstancesAsIsFromEureka () {synchronized (instances) {return new ArrayList <InstanceInfo> (this.instances);}}
  • 调用 #getReconcileHashCode() 方法,计算 hashcode 。实现代码如下: public static String getReconcileHashCode(Map<String, AtomicInteger> instanceCountMap) { StringBuilder reconcileHashCode = new StringBuilder(75); for (Map.Entry<String, AtomicInteger> mapEntry : instanceCountMap.entrySet()) { reconcileHashCode.append(mapEntry.getKey()).append(STATUS_DELIMITER) // status .append(mapEntry.getValue().get()).append(STATUS_DELIMITER); // count } return reconcileHashCode.toString(); }

2.2 rationality

In this section, we recommend that you completely understand the text, and then come back here in this section, it is recommended that you understand the full text, go back to where sections of this, it is recommended that you understand the full article, come back here

I just read a set of formulas apply consistent hashing algorithm, in the face of force to rip off the state. So streamlined way to really be able to check the consistency of the data it? I do not know how many readers have the same doubts with the author. Let's justify the algorithm (seriousness of nonsense).

Consistency hash value status + number to calculate, it is not likely to be as much as the total number of states, the actual distribution in different applications? So we list the model as follows:

 

UP

Application A

m

Application B

n

If application A is down at this time the original application example c a, c of the application B registered application instance a letter, then the number is still in the UP state includes m + n.

  • Under normal circumstances, Eureka-Client from the Eureka-Server to get the full incremental change and consolidation, this time as the application form, the two are the same, consistent hashing algorithm is reasonable .

 

UP (server)

UP (client)

Application A

m - c

m - c

Application B

n + c

n + c

  • [1] Under unusual circumstances, change record queue all expired. Eureka-Client that acquires from the Eureka-Server to the incremental change in the empty and combined, then the table shown in the following applications, both an application is not the same, the hash value is equal consistency, consistency hash algorithm unreasonable .

 

UP (server)

UP (client)

Application A

m - c

m

Application B

n + c

n

  • [2] under abnormal conditions, change history queue expires portion, such as application A and the application B are remaining change record w article. Eureka-Client that acquires from the Eureka-Server to increment of change and combined, both an application is not the same, then the application of the following table, the hash value is equal consistency, consistency hash algorithm unreasonable .

 

UP (server)

UP (client)

Application A

m - c

m - w

Application B

n + c

n + w

What? From the abnormal situation [1] [2] can be seen, consistent hashing algorithm turned out to be unreasonable , then we manually do one of the most streamlined experiments. Experiments are as follows:

  • Simulation scenarios: [1] abnormalities, m = n = c = 1. Simple and crude.
  • Special configuration
    • eureka.retentionTimeInMSInDeltaQueue = 1Change record queue length of 1 ms each record when survival. Eureka-Client requests to achieve less than full incremental changes.
    • eureka.deltaRetentionTimerIntervalInMs = 1, Each record change record expired queue timer task execution frequency of 1 ms. Eureka-Client requests to achieve less than full incremental changes.
    • eureka.shouldUseReadOnlyResponseCache = falseDisable response cache read-only cache. To avoid waiting for the cache refresh.
    • eureka.waitTimeInMsWhenSyncEmpty = 1
  • experiment procedure
    1. 00:00 start Eureka-Server
    2. 00:30 start the application A, registered with the Eureka-Server
    3. 01:00 start Eureka-Client, obtain registration information to the Eureka-Server, waiting to get application A
    4. 01:30 Close the application A. Immediately start the application B, register with the Eureka-Server
    5. Wait 5 minutes, Eureka-Client can not get to the application B
    6. At this time, the application table shown below, both an application is not the same, the hash value is equal consistency, consistency hash algorithm unreasonable.

 

UP (server)

UP (client)

Application A

0

1

Application B

1

0

? Conclusion ?

Of course exclude special extreme scenario, Eureka-Client from the Eureka-Server synchronization because the network has not led to an abnormal incremental changes, and just turned off and on the number of applications to meet the state statistics. Further, when changing the recording expired queue record length is 300 seconds, the incremental acquisition frequency of 30 seconds, the number of views acquired around 10. Therefore, the application of a set of consistent hashing code in most scenarios is reasonable . The author of YY , have to solve this tiny scene as follows:

  • A first, modified formula appsHashCode = MD5(${app_name}_${instance_id}_${status}_${count}_), increasing the number of sensitive application name and application examples.
  • The second, conducted once every N minutes to obtain the full amount of registration information.

ps: With disturbed heart I finished this section, if there is unreasonable, or if there are different points of view fat friends, welcome to explore. Thank you.

TODO [0027] [reflect]: The set of consistent hashing algorithms.

3. Eureka-Client initiated incremental acquisition

In "Eureka parse the source code - Registration discovery application examples (vi) the total amount of access", "2.4 launched for registration information", the calling DiscoveryClient#getAndUpdateDelta(...)method, the incremental obtain registration information, and to refresh the local cache, codes are as follows:

  1: private void getAndUpdateDelta(Applications applications) throws Throwable {
  2:     long currentUpdateGeneration = fetchRegistryGeneration.get();
  3: 
  4: // increment for registration information
  5:     Applications delta = null;
  6:     EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
  7:     if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
  8:         delta = httpResponse.getEntity();
  9:     }
 10: 
 11:     if (delta == null) {
 12: // increment acquisition is empty, the whole amount of the acquisition
 13:         logger.warn("The server does not allow the delta revision to be applied because it is not safe. "
 14:                 + "Hence got the full registry.");
 15: getAndStoreFullRegistry ();
 16:     } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
 17:         logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
 18:         String reconcileHashCode = "";
 19:         if (fetchRegistryUpdateLock.tryLock()) {
 20:             try {
 21: // set of applications that will change the application set and the local cache are combined
 22:                 updateDelta(delta);
 23: // Calculate the local hash code consistent set of applications
 24:                 reconcileHashCode = getReconcileHashCode(applications);
 25:             } finally {
 26:                 fetchRegistryUpdateLock.unlock();
 27:             }
 28:         } else {
 29:             logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
 30:         }
 31:         // There is a diff in number of instances for some reason
 32:! If (reconcileHashCode.equals (delta.getAppsHashCode ()) // consistent hash values ​​are not equal
 33:                 || clientConfig.shouldLogDeltaDiff()) { //
 34:             reconcileAndLogDifference(delta, reconcileHashCode);  // this makes a remoteCall
 35:         }
 36:     } else {
 37:         logger.warn("Not updating application delta as another thread is updating it already");
 38:         logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode());
 39:     }
 40: }
  • 9, line 4 to: request increment for registration information, codes are as follows: // AbstractJerseyEurekaHttpClient.java @Override public EurekaHttpResponse <Applications > getDelta (String ... regions) {return getApplicationsInternal ( "apps / delta", regions); }
    • Call the AbstractJerseyEurekaHttpClient#getApplicationsInternal(...)method, GET requests Eureka-Server's apps/detlainterfaces, parameters regions, return to the format of JSON, achieve incremental obtain registration information.
  • 15, line 11 to: incremental acquisition fails, call the #getAndStoreFullRegistry()method, the full amount for registration information, and set to the local cache. This method "Eureka parse the source code - Registration discovery application examples (vi) the total amount of access", "2.4.1 full amount for registration information, and to set up a local cache," a detailed resolution.
  • Lines 16 to 35: processing the incremental results obtained.
    • Line 33: configuration eureka.printDeltaFullDiff, and whether to print the total amount of the incremental difference. Default:false . From the current code implementation point of view, there is no effect. Note : This parameter will lead to open each incremental acquisition after launch full amount of acquisition, do not open.
    • Line 16: TODO [0025]: the case of concurrent updates? ? ?
    • Line 19: TODO [0025]: the case of concurrent updates? ? ?
    • Line 21: calls the #updateDelta(...)method, the change set of applications and local caching of application set to merge.
    • The first 31-35 lines: consistent hash values are not equal, call the #reconcileAndLogDifference()method, the full amount for registration information, and to set up a local cache, and #getAndStoreFullRegistry()essentially similar.

3.1 The combined set of applications

Call the #updateDelta(...)method, the change set of applications and local caching of application set to merge. Codes are as follows:

  1: private void updateDelta(Applications delta) {
  2:     int deltaCount = 0;
  3: (Application app: delta.getRegisteredApplications ()) for {application set (change) // loop increment
  4:         for (InstanceInfo instance : app.getInstances()) {
  5:             Applications applications = getApplications();
  6:             // TODO[0009]:RemoteRegionRegistry
  7:             String instanceRegion = instanceRegionChecker.getInstanceRegion(instance);
  8:             if (!instanceRegionChecker.isLocalRegion(instanceRegion)) {
  9:                 Applications remoteApps = remoteRegionVsApps.get(instanceRegion);
 10:                 if (null == remoteApps) {
 11:                     remoteApps = new Applications();
 12:                     remoteRegionVsApps.put(instanceRegion, remoteApps);
 13:                 }
 14:                 applications = remoteApps;
 15:             }
 16: 
 17: part count ++;
 18:             if (ActionType.ADDED.equals(instance.getActionType())) { // 添加
 19:                 Application existingApp = applications.getRegisteredApplications(instance.getAppName());
 20:                 if (existingApp == null) {
 21:                     applications.addApplication(app);
 22:                 }
 23:                 logger.debug("Added instance {} to the existing apps in region {}", instance.getId(), instanceRegion);
 24:                 applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
 25:             } else if (ActionType.MODIFIED.equals(instance.getActionType())) { // 修改
 26:                 Application existingApp = applications.getRegisteredApplications(instance.getAppName());
 27:                 if (existingApp == null) {
 28:                     applications.addApplication(app);
 29:                 }
 30:                 logger.debug("Modified instance {} to the existing apps ", instance.getId());
 31: 
 32:                 applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
 33:             } else if (ActionType.DELETED.equals(instance.getActionType())) { // 删除
 34:                 Application existingApp = applications.getRegisteredApplications(instance.getAppName());
 35:                 if (existingApp == null) {
 36:                     applications.addApplication(app);
 37:                 }
 38:                 logger.debug("Deleted instance {} to the existing apps ", instance.getId());
 39:                 applications.getRegisteredApplications(instance.getAppName()).removeInstance(instance);
 40:             }
 41:         }
 42:     }
 43:     logger.debug("The total number of instances fetched by the delta processor : {}", deltaCount);
 44: 
 45: getApplications () SETVERSION (delta.getVersion ());.
 46: // filtered disrupted set of applications
 47:     getApplications().shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
 48: 
 49:     // TODO[0009]:RemoteRegionRegistry
 50:     for (Applications applications : remoteRegionVsApps.values()) {
 51:         applications.setVersion(delta.getVersion());
 52:         applications.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
 53:     }
 54: }
  • Line 15 to 6: TODO [0009]: RemoteRegionRegistry
  • Lines 18 to 24: when adding (ADDED) application example, invoke Application#addInstance(...)method implementation code as follows: // Application.java public void addInstance (InstanceInfo i) {// add to the application instance mapping instancesMap.put (i.getId () , i); synchronized (instances) {// remove the existing instance instances.remove (i); // Add a new instance instances.add (i); // set isDirty, currently used only for `#toString ()` The method of printing, no business logic isDirty = true;}} // InstanceInfo.java @Override public int hashCode () {// calculation using only ID hashcode String id = getId (); return (id == null) 31:? ( id.hashCode () + 31);} @Override public boolean equals (Object obj) {// only Comparative ID if (this == obj) { return true;} if (obj == null) {return false;} if (! getClass () = obj.getClass ( )) {return false;} InstanceInfo other = (InstanceInfo) obj; String id = getId ();! if (id == null) {if (other.getId () = null ) {return false;}} else if) {return false (id.equals (other.getId ()!);} return true;}
  • The first 25 to 32 lines: modifying (the MODIFIED) application example, the same call Application#addInstance(...)method.
  • 33 to line 40: delete (DELETED) application example, invoke Application#removeInstance(...)methods, codes are as follows: public void removeInstance (InstanceInfo i) {removeInstance (i, true);} private void removeInstance (InstanceInfo i, boolean markAsDirty) {// application examples mapping removed instancesMap.remove (i.getId ()); synchronized (instances) {// remove application examples instances.remove (i); if (markAsDirty ) {// set isDirty, currently used only for `# toString () `method of printing, no business logic isDirty = true;}}}
  • Line 47: calling Applications#shuffleInstances(...)method, according to the configuration eureka.shouldFilterOnlyUpInstances = true(default: true) filter retaining only the application instance is ON state (UP), and to randomly disrupt the application example sequence. After the upset, realize randomness calling application services. Relatively easy to understand the code, click on the link to see the methods.
  • Lines 53 to 49: TODO [0009]: RemoteRegionRegistry

4. Eureka-Server receives the full amount acquisition

The total amount acquisition request receiving 3.1

com.netflix.eureka.resources.ApplicationsResourceProcessing all requests application operating Resource (Controller).

Receiving the acquisition request incremental mapping ApplicationsResource#getContainers()method.

  • And "Eureka source code parsing - registration application examples found (six) of the total amount of access" "to receive the full amount of 3.1 acquisition request" is similar to not repeat long-winded friends.
  • Click on the link to see the method with Chinese comments code.

3.2 Lease recent change record queue

AbstractInstanceRegistry.recentlyChangedQueueRecently lease change record queue. Codes are as follows:

// AbstractInstanceRegistry.java
/**
* Recent lease change record queue
*/
private ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue = new ConcurrentLinkedQueue<RecentlyChangedItem>();

/**
* Recent changes recorded lease
*/
private static final class RecentlyChangedItem {
   /**
    * Last updated time stamp
    */
   private long lastUpdateTime;
   /**
    * Lease
    */
   private Lease<InstanceInfo> leaseInfo;

   public RecentlyChangedItem(Lease<InstanceInfo> lease) {
       this.leaseInfo = lease;
       lastUpdateTime = System.currentTimeMillis();
   }

   public long getLastUpdateTime() {
       return this.lastUpdateTime;
   }

   public Lease<InstanceInfo> getLeaseInfo() {
       return this.leaseInfo;
   }
}
  • When an application instance is registered, offline, status changes, create a lease recently changed record (RecentlyChangedItem) to the queue.
  • Timing background task sequential scan queue, when lastUpdateTimecarried out to remove more than a certain length of time. Codes are as follows: // AbstractInstanceRegistry.java this.deltaRetentionTimer.schedule (getDeltaRetentionTask () , serverConfig.getDeltaRetentionTimerIntervalInMs (), serverConfig.getDeltaRetentionTimerIntervalInMs ()); private TimerTask getDeltaRetentionTask () {return new TimerTask () {@Override public void run ( ) {Iterator & lt; RecentlyChangedItem & gt; it = recentlyChangedQueue.iterator (); while (it.hasNext ()) {if (it.next () getLastUpdateTime () & lt;. System.currentTimeMillis () - serverConfig.getRetentionTimeInMSInDeltaQueue ()) {it .remove ();} else {break ;}}}};}
    • Configuration eureka.deltaRetentionTimerIntervalInMs, removing the lease expired queue timer task execution frequency change history, unit: ms. Default: 30 * 1000 milliseconds.
    • Configuration eureka.retentionTimeInMSInDeltaQueue, change records expired lease length, unit: milliseconds. Default: 3 * 60 * 1000 milliseconds.

3.3 cache reads

In "Eureka parse the source code - Registration discovery application examples (vi) the total amount of access", "3.3 cache reads", in the #generatePayload()method, the call AbstractInstanceRegistry#getApplicationDeltas(...)method to get a collection of recent changes in the application, codes are as follows:

// AbstractInstanceRegistry.java
  1: public Applications getApplicationDeltas() {
  2: // add an incremental number to get monitor
  3:     GET_ALL_CACHE_MISS_DELTA.increment();
  4: // initialize set of application changes
  5:     Applications apps = new Applications();
  6:     apps.setVersion(responseCache.getVersionDelta().get());
  7:     Map<String, Application> applicationInstancesMap = new HashMap<String, Application>();
  8:     try {
  9: // Get a write lock
 10:         write.lock();
 11: // Get the most recent lease change record queue
 12:         Iterator<RecentlyChangedItem> iter = this.recentlyChangedQueue.iterator();
 13:         logger.debug("The number of elements in the delta queue is :" + this.recentlyChangedQueue.size());
 14: // set of applications assembled change
 15:         while (iter.hasNext()) {
 16:             Lease<InstanceInfo> lease = iter.next().getLeaseInfo();
 17:             InstanceInfo instanceInfo = lease.getHolder();
 18:             Object[] args = {instanceInfo.getId(), instanceInfo.getStatus().name(), instanceInfo.getActionType().name()};
 19:             logger.debug("The instance id %s is found with status %s and actiontype %s", args);
 20:             Application app = applicationInstancesMap.get(instanceInfo.getAppName());
 21:             if (app == null) {
 22:                 app = new Application(instanceInfo.getAppName());
 23:                 applicationInstancesMap.put(instanceInfo.getAppName(), app);
 24:                 apps.addApplication(app);
 25:             }
 26:             app.addInstance(decorateInstanceInfo(lease));
 27:         }
 28: 
 29:         // TODO[0009]:RemoteRegionRegistry
 30:         boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();
 31:         if (!disableTransparentFallback) {
 32:             Applications allAppsInLocalRegion = getApplications(false);
 33: 
 34:             for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) {
 35:                 Applications applications = remoteRegistry.getApplicationDeltas();
 36:                 for (Application application : applications.getRegisteredApplications()) {
 37:                     Application appInLocalRegistry =
 38:                             allAppsInLocalRegion.getRegisteredApplications(application.getName());
 39:                     if (appInLocalRegistry == null) {
 40:                         apps.addApplication(application);
 41:                     }
 42:                 }
 43:             }
 44:         }
 45: 
 46: // Get the application sets the full amount by which the hash value is calculated consistency
 47:         Applications allApps = getApplications(!disableTransparentFallback);
 48:         apps.setAppsHashCode(allApps.getReconcileHashCode());
 49:         return apps;
 50:     } finally {
 51:         write.unlock();
 52:     }
 53: }
  • To 3, line 2: Add the number of times to get incremental monitoring. With Netflix Servo implement monitoring information collection.
  • Line 4: (incremental) change in set of applications initialize ( apps).
  • Line 9: Get a write lock. In "Eureka parse the source code - Registration application examples found (nine) years of Meng Meng is the read-write lock" detailed analysis.
  • 13, line 11 to: Get the most recent lease change record queue ( 最近租约变更记录队列).
  • Lines 14 to 27: The collection assembly changes ( apps).
  • Lines 44 to 29: TODO [0009]: RemoteRegionRegistry
  • 48, line 46: Calling the #getApplications(...)method to obtain the full amount of the set of applications ( allApps), the "Eureka parse the source code - Registration discovery application examples (vi) the total amount of access", "3.3.1 to be registered set of applications" have a detailed analysis. After the adoption of allAppscomputing consistent hashing value. With this set of applications hash value of the whole amount, Eureka-Client after obtaining incremental set of applications and merge, you can compare it.
  • Line 51: the release write lock.

Guess you like

Origin blog.csdn.net/rubbertree/article/details/90294252