[Eureka Technical Guide] "Spring Cloud" allows you to understand Eureka's workflow and operating mechanism from the source code level (Part 2)

Principle review

  1. Eureka Server provides service registration service. After each node is started, it will be registered in Eureka Server. In this way, the service registry in Eureka Server will store the information of all available service nodes. The information of service nodes can be viewed intuitively in the interface. arrive.
  2. Eureka Client is a Java client that simplifies interaction with Eureka Server. The client also has a built-in load balancer that uses a round-robin load algorithm.
  3. After the application starts, it will send a heartbeat to Eureka Server (the default period is 30 seconds). If Eureka Server does not receive a heartbeat from a node in multiple heartbeat periods (default 3 heartbeat periods = 90 seconds), Eureka Server will The service node will be removed from the service registry.
  4. In the case of high availability: data synchronization between Eureka Servers will be completed through replication;
  5. Eureka Client has a caching mechanism. Even if all Eureka Servers are down, the client can still use the information in the cache to consume APIs of other services;

EurekaServer startup process analysis

EurekaServer handles service registration and cluster data replication

How is EurekaClient registered to EurekaServer?

Just hit a breakpoint in each method of org.springframework.cloud.netflix.eureka.server.InstanceRegistry, and now EurekaServer is already in Debug running state, so let's just find a microservice started by @EnableEurekaClient and try Let's try microservices and run them directly.

  • When it is started, the register method will definitely be called, so let’s continue to look down and wait and see;
Instance registration method mechanism
InstanceRegistry.register(final InstanceInfo info, final boolean isReplication) 方法进断点了。
复制代码
  • InstanceRegistry.register looks up along the stack information, the ApplicationResource.addInstance method is called, analyze addInstance;

ApplicationResource class

It mainly deals with receiving Http service requests.


public Response addInstance(InstanceInfo info,
                             String isReplication) {
    
    
    logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);

    if (isBlank(info.getId())) {
    
    
        return Response.status(400).entity("Missing instanceId").build();
    } else if (isBlank(info.getHostName())) {
    
    
        return Response.status(400).entity("Missing hostname").build();
    } else if (isBlank(info.getAppName())) {
    
    
        return Response.status(400).entity("Missing appName").build();
    } else if (!appName.equals(info.getAppName())) {
    
    
        return Response.status(400).entity("Mismatched appName, expecting " + appName + " but was " + info.getAppName()).build();
    } else if (info.getDataCenterInfo() == null) {
    
    
        return Response.status(400).entity("Missing dataCenterInfo").build();
    } else if (info.getDataCenterInfo().getName() == null) {
    
    
        return Response.status(400).entity("Missing dataCenterInfo Name").build();
    }

    DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
    if (dataCenterInfo instanceof UniqueIdentifier) {
    
    
        String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId();
        if (isBlank(dataCenterInfoId)) {
    
    
            boolean experimental = "true".equalsIgnoreCase(serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
            if (experimental) {
    
    
                String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
                return Response.status(400).entity(entity).build();
            } else if (dataCenterInfo instanceof AmazonInfo) {
    
    
                AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;
                String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);
                if (effectiveId == null) {
    
    
                 amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());
                }
            } else {
    
    
                logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
            }
        }
    }
    registry.register(info, "true".equals(isReplication));
    return Response.status(204).build();
}
复制代码
  • The wording here seems to be a bit different from the RESTFUL wording of our previous Controller. After a closer look, it turns out to be the Jersey RESTful framework, which is a product-level RESTful service and client framework. Similar to Struts, it can also be integrated with hibernate and spring frameworks.
  • See registry.register(info, “true”.equals(isReplication)); Registration, it turns out that after the EurekaClient client starts, it will call and directly transfer to the ApplicationResource.addInstance method through the Http(s) request, as long as it is related to registration , will call this method.
  • Then we go deep into registry.register(info, “true”.equals(isReplication)) to check;

public void register(final InstanceInfo info, final boolean isReplication) {
    
    
	handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);
	super.register(info, isReplication);
}
复制代码
  • handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication) 方法;
private void handleRegistration(InstanceInfo info, int leaseDuration,
		boolean isReplication) {
    
    
	log("register " + info.getAppName() + ", vip " + info.getVIPAddress()
			+ ", leaseDuration " + leaseDuration + ", isReplication "
			+ isReplication);
	publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration,
			isReplication));
}
复制代码
  • Then publish an event EurekaInstanceRegisteredEvent service registration event through ApplicationContext, you can add monitoring events to EurekaInstanceRegisteredEvent, then users can implement some business logic they want at this moment.
  • Then let's take a look at the super.register(info, isReplication) method, which is the method of the parent class PeerAwareInstanceRegistryImpl of InstanceRegistry.

Service household toilet mechanism

Enter the register(final InstanceInfo info, final boolean isReplication) method of the PeerAwareInstanceRegistryImpl class;


public void register(final InstanceInfo info, final boolean isReplication) {
    
    

    int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;

    if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
    
    
        leaseDuration = info.getLeaseInfo().getDurationInSecs();
    }

    super.register(info, leaseDuration, isReplication);

    replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
复制代码

Enter super.register(info, leaseDuration, isReplication), how to write to the registry of EurekaServer, enter the AbstractInstanceRegistry.register(InstanceInfo registrant, int leaseDuration, boolean isReplication) method.

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    
    
    try {
    
    
        read.lock();

        Map> gMap = registry.get(registrant.getAppName());
        REGISTER.increment(isReplication);
        if (gMap == null) {
    
    
            final ConcurrentHashMap> gNewMap = new ConcurrentHashMap>();
            gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
            if (gMap == null) {
    
    
                gMap = gNewMap;
            }
        }
        Lease existingLease = gMap.get(registrant.getId());

        if (existingLease != null && (existingLease.getHolder() != null)) {
    
    
            Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
            Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
            logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
            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");
                registrant = existingLease.getHolder();
            }
        } else {
    
    

            synchronized (lock) {
    
    
                if (this.expectedNumberOfRenewsPerMin > 0) {
    
    

                    this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
                    this.numberOfRenewsPerMinThreshold =
                            (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
                }
            }
            logger.debug("No previous lease information found; it is new registration");
        }
        Lease lease = new Lease(registrant, leaseDuration);
        if (existingLease != null) {
    
    
            lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
        }
        gMap.put(registrant.getId(), lease);
        synchronized (recentRegisteredQueue) {
    
    
            recentRegisteredQueue.add(new Pair(
                    System.currentTimeMillis(),
                    registrant.getAppName() + "(" + registrant.getId() + ")"));
        }

        if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
    
    
            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);
            registrant.setOverriddenStatus(overriddenStatusFromMap);
        }

        InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
        registrant.setStatusWithoutDirty(overriddenInstanceStatus);

        if (InstanceStatus.UP.equals(registrant.getStatus())) {
    
    
            lease.serviceUp();
        }
        registrant.setActionType(ActionType.ADDED);
        recentlyChangedQueue.add(new RecentlyChangedItem(lease));
        registrant.setLastUpdatedTimestamp();
        invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
        logger.info("Registered instance {}/{} with status {} (replication={})",
                registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
    } finally {
    
    
        read.unlock();
    }
}
复制代码
  • * I found that this method is a bit long, so I read it roughly. In addition to updating the registry, I also updated the cache and other things. If you are interested, you can read this method in depth;

replication between clusters

This method of replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication) .

private void replicateToPeers(Action action, String appName, String id,
                              InstanceInfo info ,
                              InstanceStatus newStatus , boolean isReplication) {
    
    
    Stopwatch tracer = action.getTimer().start();
    try {
    
    
        if (isReplication) {
    
    
            numberOfReplicationsLastMin.increment();
        }

        if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
    
    
            return;
        }

        for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
    
    

            if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
    
    
                continue;
            }

            replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
        }
    } finally {
    
    
        tracer.stop();
    }
}
复制代码
  • Whenever there is a registration request, first update the registry of EurekaServer, and then synchronize the information to other EurekaServer nodes;
  • Next, let's see how the node node performs the replication operation, and enter the replicateInstanceActionsToPeers method.
private void replicateInstanceActionsToPeers(Action action, String appName,
                                             String id, InstanceInfo info, InstanceStatus newStatus,
                                             PeerEurekaNode node) {
    
    
    try {
    
    
        InstanceInfo infoFromRegistry = null;
        CurrentRequestVersion.set(Version.V2);
        switch (action) {
    
    
            case Cancel:
                node.cancel(appName, id);
                break;
            case Heartbeat:
                InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
                infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
                break;
            case Register:
                node.register(info);
                break;
            case StatusUpdate:
                infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                node.statusUpdate(appName, id, newStatus, infoFromRegistry);
                break;
            case DeleteStatusOverride:
                infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                node.deleteStatusOverride(appName, id, infoFromRegistry);
                break;
        }
    } catch (Throwable t) {
    
    
        logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
    }
}
复制代码
  • The replication state operation between nodes is fully reflected here, so let's take the Register type node.register(info) to see, let's see how the node synchronizes information, enter node.register(info ) method to see;

Replication mechanism between peers

PeerEurekaNode.register(final InstanceInfo info) method to see how to synchronize data.

public void register(final InstanceInfo info) throws Exception {
    
    

    long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
    batchingDispatcher.process(
            taskId("register", info),
            new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
    
    
                public EurekaHttpResponse execute() {
    
    
                    return replicationClient.register(info);
                }
            },
            expiryTime
    );
}
复制代码
  • This involves Eureka's task batch processing. Usually, the synchronization between peers needs to be called multiple times. To a certain extent, it has caused some delays in registration and offline. While highlighting the advantages, it is bound to cause some disadvantages, but these delays are still within the tolerance range of common sense.
  • Within the expiryTime timeout period, what batch processing needs to do is to merge tasks into a List, and then when sending a request, directly package the batch List and send the request out. In this case, in this batch List, it is possible A collection List containing a series of states such as cancellation, registration, heartbeat, and status.
  • Let's look at the source code again, batchingDispatcher.process is called like this, and then we will directly look at the TaskDispatchers.createBatchingTaskDispatcher method.
public static  TaskDispatcher createBatchingTaskDispatcher(String id,
                                                                             int maxBufferSize,
                                                                             int workloadSize,
                                                                             int workerCount,
                                                                             long maxBatchingDelay,
                                                                             long congestionRetryDelayMs,
                                                                             long networkFailureRetryMs,
                                                                             TaskProcessor taskProcessor) {
    
    
        final AcceptorExecutor acceptorExecutor = new AcceptorExecutor<>(
                id, maxBufferSize, workloadSize, maxBatchingDelay, congestionRetryDelayMs, networkFailureRetryMs
        );
        final TaskExecutors taskExecutor = TaskExecutors.batchExecutors(id, workerCount, taskProcessor, acceptorExecutor);
        return new TaskDispatcher() {
    
    

            public void process(ID id, T task, long expiryTime) {
    
    
                acceptorExecutor.process(id, task, expiryTime);
            }

            public void shutdown() {
    
    
                acceptorExecutor.shutdown();
                taskExecutor.shutdown();
            }
        };
    }
复制代码
  • The process method here will add the task to the queue. If there is a queue, there will be a queue. I will not explain how to get the task one by one. I will talk about how to trigger the task in the end. Enter final TaskExecutors
static  TaskExecutors batchExecutors(final String name,
                                                   int workerCount,
                                                   final TaskProcessor processor,
                                                   final AcceptorExecutor acceptorExecutor) {
    
    
    final AtomicBoolean isShutdown = new AtomicBoolean();
    final TaskExecutorMetrics metrics = new TaskExecutorMetrics(name);
    return new TaskExecutors<>(new WorkerRunnableFactory() {
    
    

        public WorkerRunnable create(int idx) {
    
    
            return new BatchWorkerRunnable<>("TaskBatchingWorker-" +name + '-' + idx, isShutdown, metrics, processor, acceptorExecutor);
        }
    }, workerCount, isShutdown);
}
复制代码
  • We found that the static method batchExecutors in the TaskExecutors class has an implementation class returned by BatchWorkerRunnable, so we enter the BatchWorkerRunnable class again to see what happened, and since it is Runnable, there must be a run method.

public void run() {
    
    
    try {
    
    
        while (!isShutdown.get()) {
    
    

            List> holders = getWork();
            metrics.registerExpiryTimes(holders);

            List tasks = getTasksOf(holders);

            ProcessingResult result = processor.process(tasks);
            switch (result) {
    
    
                case Success:
                    break;
                case Congestion:
                case TransientError:
                    taskDispatcher.reprocess(holders, result);
                    break;
                case PermanentError:
                    logger.warn("Discarding {} tasks of {} due to permanent error", holders.size(), workerName);
            }
            metrics.registerTaskResult(result, tasks.size());
        }
    } catch (InterruptedException e) {
    
    

    } catch (Throwable e) {
    
    

        logger.warn("Discovery WorkerThread error", e);
    }
}
复制代码
  • This is the run method of our BatchWorkerRunnable class. First, the semaphore release must be obtained to obtain the task set. Once the task set is obtained, then directly call the processor.process(tasks) method to request the Peer node to synchronize data. Next, we Take a look at the ReplicationTaskProcessor.process method;

public ProcessingResult process(List tasks) {
    
    
    ReplicationList list = createReplicationListOf(tasks);
    try {
    
    

        EurekaHttpResponse response = replicationClient.submitBatchUpdates(list);
        int statusCode = response.getStatusCode();
        if (!isSuccess(statusCode)) {
    
    
            if (statusCode == 503) {
    
    
                logger.warn("Server busy (503) HTTP status code received from the peer {}; rescheduling tasks after delay", peerId);
                return ProcessingResult.Congestion;
            } else {
    
    

                logger.error("Batch update failure with HTTP status code {}; discarding {} replication tasks", statusCode, tasks.size());
                return ProcessingResult.PermanentError;
            }
        } else {
    
    
            handleBatchResponse(tasks, response.getEntity().getResponseList());
        }
    } catch (Throwable e) {
    
    
        if (isNetworkConnectException(e)) {
    
    
            logNetworkErrorSample(null, e);
            return ProcessingResult.TransientError;
        } else {
    
    
            logger.error("Not re-trying this exception because it does not seem to be a network exception", e);
            return ProcessingResult.PermanentError;
        }
    }
    return ProcessingResult.Success;
}
复制代码
  • It feels like we are about to see the truth, so we can't wait to enter the JerseyReplicationClient.submitBatchUpdates(ReplicationList replicationList) method to see what's going on.

public EurekaHttpResponse submitBatchUpdates(ReplicationList replicationList) {
    
    
    ClientResponse response = null;
    try {
    
    
        response = jerseyApacheClient.resource(serviceUrl)

                .path(PeerEurekaNode.BATCH_URL_PATH)
                .accept(MediaType.APPLICATION_JSON_TYPE)
                .type(MediaType.APPLICATION_JSON_TYPE)
                .post(ClientResponse.class, replicationList);
        if (!isSuccess(response.getStatus())) {
    
    
            return anEurekaHttpResponse(response.getStatus(), ReplicationListResponse.class).build();
        }
        ReplicationListResponse batchResponse = response.getEntity(ReplicationListResponse.class);
        return anEurekaHttpResponse(response.getStatus(), batchResponse).type(MediaType.APPLICATION_JSON_TYPE).build();
    } finally {
    
    
        if (response != null) {
    
    
            response.close();
        }
    }
}
复制代码
  • Seeing the relative path address, let's search for a string like "batch" to see if there is a corresponding receiving method or it is entered by the @Path annotation; under the eureka-core-1.4.12.jar package, we found it Enter the words @Path("batch") directly, and find that this is the method batchReplication of the PeerReplicationResource class. Let's enter this method to have a look.

public Response batchReplication(ReplicationList replicationList) {
    
    
    try {
    
    
        ReplicationListResponse batchResponse = new ReplicationListResponse();

        for (ReplicationInstance instanceInfo : replicationList.getReplicationList()) {
    
    
            try {
    
    
                batchResponse.addResponse(dispatch(instanceInfo));
            } catch (Exception e) {
    
    
                batchResponse.addResponse(new ReplicationInstanceResponse(Status.INTERNAL_SERVER_ERROR.getStatusCode(), null));
                logger.error(instanceInfo.getAction() + " request processing failed for batch item "
                        + instanceInfo.getAppName() + '/' + instanceInfo.getId(), e);
            }
        }
        return Response.ok(batchResponse).build();
    } catch (Throwable e) {
    
    
        logger.error("Cannot execute batch Request", e);
        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
    }
}
复制代码
  • Seeing that the traversal task is processed once in a loop, I feel exhilarated unconsciously. The key point of victory is coming soon. Let's enter the PeerReplicationResource.dispatch method to have a look.
private ReplicationInstanceResponse dispatch(ReplicationInstance instanceInfo) {
    
    
    ApplicationResource applicationResource = createApplicationResource(instanceInfo);
    InstanceResource resource = createInstanceResource(instanceInfo, applicationResource);

    String lastDirtyTimestamp = toString(instanceInfo.getLastDirtyTimestamp());
    String overriddenStatus = toString(instanceInfo.getOverriddenStatus());
    String instanceStatus = toString(instanceInfo.getStatus());

    Builder singleResponseBuilder = new Builder();
    switch (instanceInfo.getAction()) {
    
    
        case Register:
            singleResponseBuilder = handleRegister(instanceInfo, applicationResource);
            break;
        case Heartbeat:
            singleResponseBuilder = handleHeartbeat(resource, lastDirtyTimestamp, overriddenStatus, instanceStatus);
            break;
        case Cancel:
            singleResponseBuilder = handleCancel(resource);
            break;
        case StatusUpdate:
            singleResponseBuilder = handleStatusUpdate(instanceInfo, resource);
            break;
        case DeleteStatusOverride:
            singleResponseBuilder = handleDeleteStatusOverride(instanceInfo, resource);
            break;
    }
    return singleResponseBuilder.build();
}
复制代码
  • Just grab a type, let's also look at the Register type, and enter PeerReplicationResource.handleRegister to see.
private static Builder handleRegister(ReplicationInstance instanceInfo, ApplicationResource applicationResource) {
    
    

    applicationResource.addInstance(instanceInfo.getInstanceInfo(), REPLICATION);
    return new Builder().setStatusCode(Status.OK.getStatusCode());
}
复制代码
  • The synchronization journey of the Peer node is finally over, and finally called back to the ApplicationResource.addInstance method. This method is finally registered and called after the EurekaClient is started. However, the information synchronization of the Peer node also calls this method, just through a variable isReplication Whether it is true or false to determine whether it is node replication. The rest of the ApplicationResource.addInstance process has been mentioned earlier. I believe everyone has understood how the registration process is reversed, including how batch tasks handle information synchronization between EurekaServer nodes.

EurekaClient startup process analysis

Change the operating mode

Run runs the discovery-eureka service, Debug runs the provider-user service, first observe the log first;

2017-10-23 19:43:07.688  INFO 1488 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2017-10-23 19:43:07.694  INFO 1488 --- [           main] o.s.c.n.eureka.InstanceInfoFactory       : Setting initial instance status as: STARTING
2017-10-23 19:43:07.874  INFO 1488 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
2017-10-23 19:43:07.874  INFO 1488 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
2017-10-23 19:43:07.971  INFO 1488 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML encoding codec XStreamXml
2017-10-23 19:43:07.971  INFO 1488 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML decoding codec XStreamXml
2017-10-23 19:43:08.134  INFO 1488 --- [           main] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2017-10-23 19:43:08.344  INFO 1488 --- [           main] com.netflix.discovery.DiscoveryClient    : Disable delta property : false
2017-10-23 19:43:08.344  INFO 1488 --- [           main] com.netflix.discovery.DiscoveryClient    : Single vip registry refresh property : null
2017-10-23 19:43:08.344  INFO 1488 --- [           main] com.netflix.discovery.DiscoveryClient    : Force full registry fetch : false
2017-10-23 19:43:08.344  INFO 1488 --- [           main] com.netflix.discovery.DiscoveryClient    : Application is null : false
2017-10-23 19:43:08.344  INFO 1488 --- [           main] com.netflix.discovery.DiscoveryClient    : Registered Applications size is zero : true
2017-10-23 19:43:08.344  INFO 1488 --- [           main] com.netflix.discovery.DiscoveryClient    : Application version is -1: true
2017-10-23 19:43:08.345  INFO 1488 --- [           main] com.netflix.discovery.DiscoveryClient    : Getting all instance registry info from the eureka server
2017-10-23 19:43:08.630  INFO 1488 --- [           main] com.netflix.discovery.DiscoveryClient    : The response status is 200
2017-10-23 19:43:08.631  INFO 1488 --- [           main] com.netflix.discovery.DiscoveryClient    : Starting heartbeat executor: renew interval is: 30
2017-10-23 19:43:08.634  INFO 1488 --- [           main] c.n.discovery.InstanceInfoReplicator     : InstanceInfoReplicator onDemand update allowed rate per min is 4
2017-10-23 19:43:08.637  INFO 1488 --- [           main] com.netflix.discovery.DiscoveryClient    : Discovery Client initialized at timestamp 1508758988637 with initial instances count: 0
2017-10-23 19:43:08.657  INFO 1488 --- [           main] c.n.e.EurekaDiscoveryClientConfiguration : Registering application springms-provider-user with eureka with status UP
2017-10-23 19:43:08.658  INFO 1488 --- [           main] com.netflix.discovery.DiscoveryClient    : Saw local status change event StatusChangeEvent [timestamp=1508758988658, current=UP, previous=STARTING]
2017-10-23 19:43:08.659  INFO 1488 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_SPRINGMS-PROVIDER-USER/springms-provider-user:192.168.3.101:7900: registering service...

2017-10-23 19:43:08.768  INFO 1488 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 7900 (http)
2017-10-23 19:43:08.768  INFO 1488 --- [           main] c.n.e.EurekaDiscoveryClientConfiguration : Updating port to 7900
2017-10-23 19:43:08.773  INFO 1488 --- [           main] c.s.cloud.MsProviderUserApplication      : Started ProviderApplication in 882.1 seconds (JVM running for 10.398)
复制代码

Service provider main body loading process

  • [1]: Check the log carefully, first the DefaultLifecycleProcessor class processes some beans, and then it will definitely call some start methods that implement the SmartLifecycle class;
  • [2]: Then initialize and set the status of EurekaClient to STARTING, and initialize the format used for encoding, which ones use JSON and which ones use XML;
  • 【3】: Immediately after that, it prints that the mandatory registration information status is false, the registered application size is 0, the client sends a heartbeat renewal, and the heartbeat renewal interval is 30 seconds, and finally prints that the Client initialization is complete;

EnableEurekaClient component.


public  EnableEurekaClient {
    
    }
复制代码

@EnableEurekaClient

This annotation class also uses the annotation @EnableDiscoveryClient, so we need to go to this annotation class to see.


public  EnableDiscoveryClient {
    
    }
复制代码

@EnableDiscoveryClient

This annotation class has a special annotation @Import , so we guess, is most of the logic here written in this EnableDiscoveryClientImportSelector class?

EnableDiscoveryClientImportSelector


public class EnableDiscoveryClientImportSelector
		extends SpringFactoryImportSelector {
    
    

	protected boolean isEnabled() {
    
    
		return new RelaxedPropertyResolver(getEnvironment()).getProperty(
				"spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
	}

	protected boolean hasDefaultFactory() {
    
    
		return true;
	}
}
复制代码

The EnableDiscoveryClientImportSelector class inherits the SpringFactoryImportSelector class, but rewrites an isEnabled() method, the default value returns true, why does it return true .


public String[] selectImports(AnnotationMetadata metadata) {
    
    
    if (!isEnabled()) {
    
    
		return new String[0];
	}
	AnnotationAttributes attributes = AnnotationAttributes.fromMap(
			metadata.getAnnotationAttributes(this.annotationClass.getName(), true));
	Assert.notNull(attributes, "No " + getSimpleName() + " attributes found. Is "
			+ metadata.getClassName() + " annotated with @" + getSimpleName() + "?");

	List factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader
			.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
	if (factories.isEmpty() && !hasDefaultFactory()) {
    
    
		throw new IllegalStateException("Annotation @" + getSimpleName()
				+ " found, but there are no implementations. Did you forget to include a starter?");
	}
	if (factories.size() > 1) {
    
    

		log.warn("More than one implementation " + "of @" + getSimpleName()
				+ " (now relying on @Conditionals to pick one): " + factories);
	}
	return factories.toArray(new String[factories.size()]);
}
复制代码

EnableDiscoveryClientImportSelector.selectImports

First, some attributes are obtained through annotations, and then some class names are loaded. Let's enter the loadFactoryNames method to have a look.

public static List loadFactoryNames(Class factoryClass, ClassLoader classLoader) {
    
    
	String factoryClassName = factoryClass.getName();
	try {
    
    

		Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		List result = new ArrayList();
		while (urls.hasMoreElements()) {
    
    
			URL url = urls.nextElement();
			Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
			String factoryClassNames = properties.getProperty(factoryClassName);
  result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
		}
		return result;
	}
	catch (IOException ex) {
    
    
		throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
				"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}
复制代码

A configuration file is loaded, what is written in the configuration file? Open the spring.factories file of the jar package where the SpringFactoryImportSelector file is located.


org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.client.CommonsClientAutoConfiguration,\
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
org.springframework.cloud.commons.util.UtilAutoConfiguration

org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.client.HostInfoEnvironmentPostProcessor
复制代码

They are all class names with Configuration suffixes, so these are piles of loaded configuration file classes. There is only one class name path in the factories object, which is org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration.

EurekaDiscoveryClientConfiguration


public class EurekaDiscoveryClientConfiguration implements SmartLifecycle, Ordered {
    
    

	public void start() {
    
    

		if (this.port.get() != 0 && this.instanceConfig.getNonSecurePort() == 0) {
    
    
			this.instanceConfig.setNonSecurePort(this.port.get());
		}

		if (!this.running.get() && this.instanceConfig.getNonSecurePort() > 0) {
    
    
			maybeInitializeClient();
			if (log.isInfoEnabled()) {
    
    
				log.info("Registering application " + this.instanceConfig.getAppname()
						+ " with eureka with status "
						+ this.instanceConfig.getInitialStatus());
			}
			this.applicationInfoManager
					.setInstanceStatus(this.instanceConfig.getInitialStatus());
			if (this.healthCheckHandler != null) {
    
    
				this.eurekaClient.registerHealthCheck(this.healthCheckHandler);
			}
			this.context.publishEvent(
					new InstanceRegisteredEvent<>(this, this.instanceConfig));
			this.running.set(true);
		}
	}
复制代码
  • First of all, seeing that this class implements the SmartLifecycle interface, then the start method must be implemented, and this start method feels that it should be loaded and executed. this.applicationInfoManager.setInstanceStatus(this.instanceConfig.getInitialStatus()) This code has an observer mode callback.

public synchronized void setInstanceStatus(InstanceStatus status) {
    
    
    InstanceStatus prev = instanceInfo.setStatus(status);
    if (prev != null) {
    
    
        for (StatusChangeListener listener : listeners.values()) {
    
    
            try {
    
    
                listener.notify(new StatusChangeEvent(prev, status));
            } catch (Exception e) {
    
    
                logger.warn("failed to notify listener: {}", listener.getId(), e);
            }
        }
    }
}
复制代码
  • This method will call back all the places that implement the StatusChangeListener class due to the change of the status, and the premise must be registered in the listeners first.
  • So, we concluded that if we want to call back, there must be a place to register this event first, and this registration must be executed in advance before the start method, so we must first find the method registered to listeners in the ApplicationInfoManager class .
public void registerStatusChangeListener(StatusChangeListener listener) {
    
    
    listeners.put(listener.getId(), listener);
}
复制代码
  • So we reversed to find the place where registerStatusChangeListener was called.
  • Unfortunately, there is only one place to be called, which is the DiscoveryClient.initScheduledTasks method, and the initScheduledTasks method is called in the constructor of DiscoveryClient. At the same time, we also mark the place where initScheduledTasks and initScheduledTasks are called. breakpoint.

Sure enough, the EurekaDiscoveryClientConfiguration.start method was called, and then this.applicationInfoManager.setInstanceStatus(this.instanceConfig.getInitialStatus()) also entered the breakpoint, and then went down, and entered the notify callback in the DiscoveryClient.initScheduledTasks method.

  • Seeing that the breakpoints pass through our above analysis in turn, and then conform to the order of log printing, so we should now take a good look at what great things DiscoveryClient.initScheduledTasks does. However, after thinking about it, it is better to look at the constructor that initScheduledTasks is called.

The constructor of DiscoveryClient annotated with @Inject.


DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, DiscoveryClientOptionalArgs args, Provider backupRegistryProvider) {
    
    
    if (args != null) {
    
    
        this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
        this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
        this.eventListeners.addAll(args.getEventListeners());
    } else {
    
    
        this.healthCheckCallbackProvider = null;
        this.healthCheckHandlerProvider = null;
    }

    this.applicationInfoManager = applicationInfoManager;
    InstanceInfo myInfo = applicationInfoManager.getInfo();

    clientConfig = config;
    staticClientConfig = clientConfig;
    transportConfig = config.getTransportConfig();
    instanceInfo = myInfo;
    if (myInfo != null) {
    
    
        appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
    } else {
    
    
        logger.warn("Setting instanceInfo to a passed in null value");
    }

    this.backupRegistryProvider = backupRegistryProvider;

    this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo);
    localRegionApps.set(new Applications());

    fetchRegistryGeneration = new AtomicLong(0);

    remoteRegionsToFetch = new AtomicReference(clientConfig.fetchRegistryForRemoteRegions());
    remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(","));

    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;
    }

    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;
    }

    if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
    
    
        logger.info("Client configured to neither register nor query for data.");
        scheduler = null;
        heartbeatExecutor = null;
        cacheRefreshExecutor = null;
        eurekaTransport = null;
        instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());

        DiscoveryManager.getInstance().setDiscoveryClient(this);
        DiscoveryManager.getInstance().setEurekaClientConfig(config);

        initTimestampMs = System.currentTimeMillis();

        logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                initTimestampMs, this.getApplications().size());
        return;
    }

    try {
    
    

        scheduler = Executors.newScheduledThreadPool(3,
                new ThreadFactoryBuilder()
                        .setNameFormat("DiscoveryClient-%d")
                        .setDaemon(true)
                        .build());

        heartbeatExecutor = new ThreadPoolExecutor(
                1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                new SynchronousQueue(),
                new ThreadFactoryBuilder()
                        .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                        .setDaemon(true)
                        .build()
        );

        cacheRefreshExecutor = new ThreadPoolExecutor(
                1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                new SynchronousQueue(),
                new ThreadFactoryBuilder()
                        .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                        .setDaemon(true)
                        .build()
        );

        eurekaTransport = new EurekaTransport();
        scheduleServerEndpointTask(eurekaTransport, args);

        AzToRegionMapper azToRegionMapper;
        if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
    
    
            azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);
        } else {
    
    
            azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);
        }
        if (null != remoteRegionsToFetch.get()) {
    
    
            azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));
        }
        instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
    } catch (Throwable e) {
    
    
        throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
    }

    if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
    
    
        fetchRegistryFromBackup();
    }

    initScheduledTasks();
    try {
    
    
        Monitors.registerObject(this);
    } catch (Throwable e) {
    
    
        logger.warn("Cannot register timers", e);
    }

    DiscoveryManager.getInstance().setDiscoveryClient(this);
    DiscoveryManager.getInstance().setEurekaClientConfig(config);

    initTimestampMs = System.currentTimeMillis();
    logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
            initTimestampMs, this.getApplications().size());
}
复制代码
  • Looking down, the initScheduledTasks method, as the name suggests, is to initialize the scheduling task, so the content here should be the highlight, let's take a look.
private void initScheduledTasks() {
    
    
    if (clientConfig.shouldFetchRegistry()) {
    
    

        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);

        scheduler.schedule(
                new TimedSupervisorTask(
                        "heartbeat",
                        scheduler,
                        heartbeatExecutor,
                        renewalIntervalInSecs,
                        TimeUnit.SECONDS,
                        expBackOffBound,
                        new HeartbeatThread()
                ),
                renewalIntervalInSecs, TimeUnit.SECONDS);

        instanceInfoReplicator = new InstanceInfoReplicator(
                this,
                instanceInfo,
                clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                2);

        statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
    
    

            public String getId() {
    
    
                return "statusChangeListener";
            }

            public void notify(StatusChangeEvent statusChangeEvent) {
    
    
                if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
                        InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
    
    

                    logger.warn("Saw local status change event {}", statusChangeEvent);
                } else {
    
    
                    logger.info("Saw local status change event {}", statusChangeEvent);
                }

                instanceInfoReplicator.onDemandUpdate();
            }
        };

        if (clientConfig.shouldOnDemandUpdateStatusChange()) {
    
    
            applicationInfoManager.registerStatusChangeListener(statusChangeListener);
        }

        instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
    } else {
    
    
        logger.info("Not registering with Eureka server per configuration");
    }
}
复制代码
  • After analyzing this method from top to bottom, we have done some things we most want to know about EurekaClient, such as timing tasks to obtain registration information, timing tasks to refresh cache, timing tasks to renew contracts with heartbeat, timing tasks to synchronize data center data, and status change monitoring callback etc. But only did not see the registration, how is this going on?
  • instanceInfoReplicator.onDemandUpdate() is when the state changes.
public boolean onDemandUpdate() {
    
    
    if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {
    
    
        scheduler.submit(new Runnable() {
    
    

            public void run() {
    
    
                logger.debug("Executing on-demand update of local InstanceInfo");

                Future latestPeriodic = scheduledPeriodicRef.get();
                if (latestPeriodic != null && !latestPeriodic.isDone()) {
    
    
                    logger.debug("Canceling the latest scheduled update, it will be rescheduled at the end of on demand update");
                    latestPeriodic.cancel(false);
                }

                InstanceInfoReplicator.this.run();
            }
        });
        return true;
    } else {
    
    
        logger.warn("Ignoring onDemand update due to rate limiter");
        return false;
    }
}
复制代码
  • The onDemandUpdate method, only the InstanceInfoReplicator.this.run() method is still useful, and it is still the run method. Feeling that the InstanceInfoReplicator class still implements the Runnable interface? After looking at this class, it really implements the Runnable interface.
  • This method should be where the registry we are looking for is located.
public void run() {
    
    
    try {
    
    
        discoveryClient.refreshInstanceInfo();
        Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
        if (dirtyTimestamp != null) {
    
    
            discoveryClient.register();
            instanceInfo.unsetIsDirty(dirtyTimestamp);
        }
    } catch (Throwable t) {
    
    
        logger.warn("There was a problem with the instance info replicator", t);
    } finally {
    
    
        Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
        scheduledPeriodicRef.set(next);
    }
}
复制代码
  • discoveryClient.register() This register method, the original registration method is this.
boolean register() throws Throwable {
    
    
    logger.info(PREFIX + appPathIdentifier + ": registering service...");
    EurekaHttpResponse httpResponse;
    try {
    
    
        httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
    } catch (Exception e) {
    
    
        logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);
        throw e;
    }
    if (logger.isInfoEnabled()) {
    
    
        logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
    }
    return httpResponse.getStatusCode() == 204;
}
复制代码
  • It turned out that the client request object encapsulated by EurekaHttpClient was called to register, and then we continued to explore the registrationClient.register method, so we came to the AbstractJerseyEurekaHttpClient.register method.

public EurekaHttpResponse register(InstanceInfo info) {
    
    
    String urlPath = "apps/" + info.getAppName();
    ClientResponse response = null;
    try {
    
    
        Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
        addExtraHeaders(resourceBuilder);
        response = resourceBuilder
                .header("Accept-Encoding", "gzip")
                .type(MediaType.APPLICATION_JSON_TYPE)
                .accept(MediaType.APPLICATION_JSON)

                .post(ClientResponse.class, info);
        return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
    } finally {
    
    
        if (logger.isDebugEnabled()) {
    
    
            logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                    response == null ? "N/A" : response.getStatus());
        }
        if (response != null) {
    
    
            response.close();
        }
    }
}
复制代码
  • * The Jersey RESTful framework is called to make the request, and then the EurekaServer will receive the client's registration request in the ApplicationResource.addInstance method, so how our EurekaClient is registered ends here.

share resources

Information sharing
To obtain the above resources, please visit the open source project and click to jump

Guess you like

Origin blog.csdn.net/star20100906/article/details/132212732