This article is based on Zhu Rongxin’s book “Spring Cloud Micro Service Architecture Progress”, which is strongly recommended to read.
Eureka is an open source service governance component of Netflix. Micro service names are used instead of IP addresses in the invocation of micro services between internal networks. Therefore, components like Eureka are needed to maintain the status of services. Spring Cloud integrates Eureka and can be used out of the box using the Spring ecosystem.
In addition to Eureka, Spring Cloud integrates other Netflix components, collectively called Spring Cloud Netflix, Spring Cloud Netflix includes Eureka for service governance, Zuul for routing, Ribbon for client load balancing, and Hystrix for fuse.
In addition to the above, there is also the declarative Http client Feign, which is also open source by Netflix. Spring Cloud supports Spring MVC annotations called OpenFeign on top of Feign.
Without further ado, microservices are inseparable from service governance. This chapter discusses the use and source code of Eureka and the implementation principle of cluster.
The overview
Above is the official architecture of Eureka. There are the following characters.
- Application Service: This is your business system server (microservice)
- Eureka Client: This is the Eureka Client, can be understood as a JAR package, embedded in your business system Application Service, used to register information with the Eureka server
- Application Client: This is your business system Client, embedded with the Eureka Client to get information from the Eureka server about the Application Service you want to call, and then the Application Client makes a call to the Application Service
- Eureka Server: A Eureka Server that manages the status of all microservices
- Us-east-1c: Eureka was originally designed to be used in the distributed system of Amazon cloud service AWS. Therefore, the concept of AWS Region and Availability Zone is introduced. A Region contains multiple zones. In the figure above, US-East-1C, US-EAST-1D, and US-East-1E all belong to zones. The three zones belong to the US-East-1 Region
The Eureka Client provides the following functions:
- Register yourself with Eureka Server
- Renew Eureka Server contracts periodically
- Client Offline
- Get registry
Accordingly, Eureka Server provides the following functions:
- Provide service registration
- Receiving Service Heartbeat
- Service offline
- Gets service instance information in the registry
- Service to eliminate
- Cluster synchronization
The preparatory work
To help track problems, you can turn on the DEBUG log of the Netflix package.
logging:
level:
com.netflix: DEBUG
Copy the code
The eureka-client package contains a meta-INF/Spring. factories () file. The eureka-client package contains a meta-INF/Spring. factories () file.
Analysis steps
By printing Eureka DEBUG logs, you can see the first Eureka log:
The 10:48:11 2019-11-24. 115648-235 the INFO [main] com.net flix. Discovery. DiscoveryClient: Initializing EurekainRegion us - east - 1 10:48:13 2019-11-24. 115648-030 the INFO [main] C.N.D.P rovider. DiscoveryJerseyProvider: Using JSON encoding codec LegacyJacksonJson 2019-11-24 10:48:13.030 INFO 115648 -- [main] c.n.d.provider.DiscoveryJerseyProvider : INFO 115648 -- [main] c.n.d.provider.DiscoveryJerseyProvider : Using XML encoding codec XStreamXmlCopy the code
As a class above com.net flix. Discovery. DisconveryClient print log, here to play a breakpoint, the debug mode to restart the application. View the call stack in IDEA or Eclipse as shown below:
Can be seen from the above, this method entry just the above analysis, we are Eureka EurekaClientAutoConfiguration triggered automatic configuration of the class. The source code is as follows:
// Prints logs
logger.info("Initializing Eureka in region {}".this.clientConfig.getRegion());
// If both register-with-eureka and fetch-registry are false, the service list will not be registered and pulled
if(! config.shouldRegisterWithEureka() && ! config.shouldFetchRegistry()) { logger.info("Client configured to neither register nor query for data.");
this.scheduler = null;
this.heartbeatExecutor = null;
this.cacheRefreshExecutor = null;
this.eurekaTransport = null;
this.instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), this.clientConfig.getRegion());
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config);
this.initTimestampMs = System.currentTimeMillis();
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}".this.initTimestampMs, this.getApplications().size());
} else {
Copy the code
Similarly, other source code can be analyzed using DEBUG logs and breakpoints. I will not repeat the following.
Eureka Client source code analysis
Out of the box, Eureka simplifies the development work of developers by hiding a lot of work interacting with Eureka Server and doing it autonomously. Different tasks are performed at different stages of the application, as shown below.
Code from above
logger.info("Initializing Eureka in region {}".this.clientConfig.getRegion());
Copy the code
Step by step, you can find that the Eureka Client performs the following steps:
1. Pull the full registration information from the Eureka server
The code is in the DiscoveryClient#fetchRegistry method.
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
Stopwatch tracer = this.FETCH_REGISTRY_TIMER.start();
label122: {
boolean var4;
try {
Applications applications = this.getApplications();
// If incremental pull is disabled, or Applications is null, full pull is performed
if (!this.clientConfig.shouldDisableDelta() && Strings.isNullOrEmpty(this.clientConfig.getRegistryRefreshSingleVipAddress()) && ! forceFullRegistryFetch && applications ! =null&& applications.getRegisteredApplications().size() ! =0&& applications.getVersion() ! = -1L) {
this.getAndUpdateDelta(applications);
} else {
logger.info("Disable delta property : {}".this.clientConfig.shouldDisableDelta());
logger.info("Single vip registry refresh property : {}".this.clientConfig.getRegistryRefreshSingleVipAddress());
logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
logger.info("Application is null : {}", applications == null);
logger.info("Registered Applications size is zero : {}", applications.getRegisteredApplications().size() == 0);
logger.info("Application version is -1: {}", applications.getVersion() == -1L);
// Full pull registry information
this.getAndStoreFullRegistry();
}
applications.setAppsHashCode(applications.getReconcileHashCode());
this.logTotalInstances();
break label122;
} catch (Throwable var8) {
logger.error("DiscoveryClient_{} - was unable to refresh its cache! status = {}".new Object[]{this.appPathIdentifier, var8.getMessage(), var8});
var4 = false;
} finally {
if(tracer ! =null) { tracer.stop(); }}return var4;
}
this.onCacheRefreshed();
this.updateInstanceRemoteStatus();
return true;
}
Copy the code
The code for pulling the registry address is:
// The urlPath is passed in /apps
WebResource webResource = this.jerseyClient.resource(this.serviceUrl).path(urlPath);
Copy the code
More than this. ServiceUrl defaults to http://localhost:8761/eureka so registry address is http://localhost:8761/eureka//apps
In order to avoid the length of this article being too long, it is suggested to see Zhu Rongxin’s book “The Progress of Spring Cloud Micro-service Architecture” to understand the principle of this part.
Here is a summary of the Eureka Client startup process:
- Pull registry information: After Eureka is started, it fully pulls the registry information of the server, saves it to the local cache, and then incrementally pulls the registry information. The default address is:
http://localhost:8761/eureka/apps
- Service registration: Registers its own service metadata after pulling the registry information. The default address is:
http://localhost:8761/eureka/apps/{app-name}
- Initialize scheduled tasks: Initialize three timer tasks, one to pull the registry to refresh the cache, one to send heartbeat, and one to register on demand when application metadata changes. The heartbeat occurs 30 seconds by default, and the address is:
http://localhost:8761/eureka/apps/{app-name}/{instance-info-id}
Parameters include Status (current service status). By default, status information is obtained using Spring Boot Actuator. - Service offline: Address:
http://localhost:8761/eureka/apps/{app-name}/{instance-info-id}
The HTTP method is delete.
Eureka Server source code parsing
As an out-of-the-box service registry, Eureka Server is also a Eureka Client that pulls registries, registers services, and sends heartbeats to other Eureka servers in its configuration file.
The com.Net flix package is set to DEBUG level according to the analysis steps of the above source code, which can be seen in the log as follows:
The 2019-11-25 11:14:23. 237032-816 the INFO [main] C.N.E.R egistry. AbstractInstanceRegistry: Finished initializing remote region registries. All known remote regions: [] the 2019-11-25 11:14:23. 817 INFO - 237032 [main] C.N.E ureka. DefaultEurekaServerContext: An Initialized 11:14:23 2019-11-25. 237032-846 the INFO [main] O.S.B.A.E.W eb. EndpointLinksResolver: Exposing 2 endpoint(s) beneath base path'/actuator'The 2019-11-25 11:14:23. 237032-866 the INFO [main] S.B.A.E.W.S.W ebMvcEndpointHandlerMapping: Mapped"{[/actuator/health],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
Copy the code
Look at the method AbstractInstanceRegistry#register, which is the basis for providing service registration functionality. The source code is as follows:
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock();
// Categorize the service instance cluster by appName
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);
if (gMap == null) {
final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) { gMap = gNewMap; }}// Get the instance lease based on the instance ID
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
// Retain the last dirty timestamp without overwriting it, if there is already a lease
if(existingLease ! =null&& (existingLease.getHolder() ! =null)) {
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
// this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
// InstanceInfo instead of the server local copy.
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
" than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant"); registrant = existingLease.getHolder(); }}else {
// The lease does not exist and hence it is a new registration
// If the lease does not exist, this is a new registered instance
synchronized (lock) {
if (this.expectedNumberOfRenewsPerMin > 0) {
// Since the client wants to cancel it, reduce the threshold
/ / (1
// for 30 seconds, 2 for a minute)
// Self-protection mechanism
this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}
}
logger.debug("No previous lease information found; it is new registration");
}
// Create a new lease
Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
if(existingLease ! =null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
gMap.put(registrant.getId(), lease);
synchronized (recentRegisteredQueue) {
recentRegisteredQueue.add(new Pair<Long, String>(
System.currentTimeMillis(),
registrant.getAppName() + "(" + registrant.getId() + ")"));
}
// This is where the initial state transfer of overridden status happens
if(! InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) { 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);
}
// Set the status based on the overridden status rules
InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
// If the lease is registered with UP status, set lease service up timestamp
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(); }}Copy the code
In order to avoid the length of this article being too long, it is suggested to see Zhu Rongxin’s book “The Progress of Spring Cloud Micro-service Architecture” to understand the principle of this part.
Here is a summary of the entire process of Eureka Server startup:
- Provide service registration
- Receiving Service Heartbeat
- Service to eliminate
- Service offline
- Cluster synchronization: When Eureka Server starts, it pulls the registry from its peer node. When each Eureka Server performs operations on the local registry, it traverses the peer nodes of the Eureka Server and sends synchronization requests.
- Provides information about obtaining a registry.
Above, the end of this article.
If you need help or like my article, you are welcome to pay attention to my public account “Donggudonggua”. If you search on wechat, you will be hooked.