metadata
In addition to the normal infrastructure, Eureka supports custom metadata. The configuration is as follows
eureka:
instance:
metadata-map:
cluster: cl1
name: zhaozhen
Copy the code
Get the metadata code
List<ServiceInstance> list = discoveryClient.getInstances("zhao-service-resume");
ServiceInstance serviceInstance = list.get(0);
list.stream().forEach(s->{
System.out.println(s.getMetadata());
});
Copy the code
The breakpoint lets you know the specific metadata at call time. In practice, we can take different actions for different metadata configurations
availability
An interview question found on a technology website asks how Eureka can ensure usability. As we all know, Eureka uses AP mode, and the best way to achieve high availability is to use at least three Instances of Eureke Server to implement service registration between two. In order to synchronize the data so this involves the following aspects
- How does the Eureka client communicate with the Eureka Server
- How does eureka registration work on the client side and on the server side to achieve availability
- How does eureka renew/heartbeat implement availability on the client side and server side respectively
- How does eureka go offline
How does the Eureka client communicate with the Eureka Server
By querying various data and tracing the auto-configuration classes, the communication between Eureka and Eureka is carried out using the Jersey framework exposed interface similar to SpringMVC. The form of communication is basically similar to the way we make requests using HTTP. By injecting in EurekaServerAutoConfiguration FilterRegistrationBean realized into filter contains a specified package name of all the external interface of the Jersey
/** * Register the Jersey filter */ @Bean public FilterRegistrationBean jerseyFilterRegistration( javax.ws.rs.core.Application eurekaJerseyApp) { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new ServletContainer(eurekaJerseyApp)); bean.setOrder(Ordered.LOWEST_PRECEDENCE); bean.setUrlPatterns( Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*")); return bean; } /** * Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources * required by the Eureka server. */ @Bean public javax.ws.rs.core.Application jerseyApplication(Environment environment, ResourceLoader resourceLoader) { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider( false, environment); // Filter to include only classes that have a particular annotation. // provider.addIncludeFilter(new AnnotationTypeFilter(Path.class)); provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class)); // Find classes in Eureka packages (or subpackages) // Set<Class<? >> classes = new HashSet<>(); for (String basePackage : EUREKA_PACKAGES) { Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage); for (BeanDefinition bd : beans) { Class<? > cls = ClassUtils.resolveClassName(bd.getBeanClassName(), resourceLoader.getClassLoader()); classes.add(cls); } } // Construct the Jersey ResourceConfig // Map<String, Object> propsAndFeatures = new HashMap<>(); propsAndFeatures.put( // Skip static content used by the webapp ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX, EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*"); DefaultResourceConfig rc = new DefaultResourceConfig(classes); rc.setPropertiesAndFeatures(propsAndFeatures); return rc; }Copy the code
EUREKA_PACKAGES (private static final String[] EUREKA_PACKAGES = new String[] {” flix. Discovery “, “Com.net flix. Eureka”}); This is the Jersey framework’s concrete interface class
It should also be noted that eureka’s exposed dashboard is still in the form of a SpringMVC Controller. Concrete can be seen in the EurekaServerAutoConfiguration EurekaController injection
@Bean
@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
public EurekaController eurekaController() {
return new EurekaController(this.applicationInfoManager);
}
Copy the code
For those interested, consider the internal implementation of the subsequent EurekaController
How does eureka registration work on the client side and on the server side to achieve availability
Services renew (heartbeat) to the registry every 30 seconds (also known as registration). If they are not renewed, the lease expires after 90 seconds, and then the service is invalidated. The renewal every 30 seconds, which we call heartbeat detection, is first registered on the server, through the Jersey framework exposed interface above, through addInstance in ApplicationResource, In this process, another Eureka server also acts as a Eureka client, which also registersBy the method of register in the addInstance, all the way down debugging to PeerAwareInstanceRegistryImpl replicateInstanceActionsToPeers registration
/** * Replicates all instance changes to peer eureka nodes except for * replication traffic to this node. * */ 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); }}Copy the code
In this case, when you Register, you enter the Register
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<Void> execute() {
return replicationClient.register(info);
}
},
expiryTime
);
}
Copy the code
The default value for getLeaseRenewalOf(info) here is 90 seconds, which confirms the 90-second expiration
private static int getLeaseRenewalOf(InstanceInfo info) {
return (info.getLeaseInfo() == null ? Lease.DEFAULT_DURATION_IN_SECS : info.getLeaseInfo().getRenewalIntervalInSecs()) * 1000;
}
Copy the code
The initiating
@Override public EurekaHttpResponse<Void> 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(); }}}Copy the code
The address from which the request originated can be traced is in ApplicationsResource
@Path("{appId}")
public ApplicationResource getApplicationResource(
@PathParam("version") String version,
@PathParam("appId") String appId) {
CurrentRequestVersion.set(Version.toEnum(version));
return new ApplicationResource(appId, serverConfig, registry);
}
Copy the code
Here an ApplicationResource object is rebuilt. After analyzing this section, I still have doubts about how addInstance receives the request. After the breakpoint debugging, I find that This process is accomplished by introducing the EurekaServerInitializerConfiguration EurekaServerAutoConfiguration actually,
@Configuration
public class EurekaServerInitializerConfiguration
implements ServletContextAware, SmartLifecycle, Ordered {
}
Copy the code
EurekaServerInitializerConfiguration implements SmartLifecycle method, start method will container initialization time. And the contents of the start method
@Override
public void start() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//TODO: is this class even needed now?
eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
EurekaServerInitializerConfiguration.this.running = true;
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}
}).start();
}
Copy the code
The specific business content is in
public void contextInitialized(ServletContext context) { try { initEurekaEnvironment(); initEurekaServerContext(); context.setAttribute(EurekaServerContext.class.getName(), this.serverContext); } catch (Throwable e) { log.error("Cannot bootstrap eureka server :", e); throw new RuntimeException("Cannot bootstrap eureka server :", e); }}Copy the code
The first step initEurekaEnvironment is the initialization environment, the second step initEurekaServerContext is the business operation and the following operations are the most important
int registryCount = this.registry.syncUp();
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
Copy the code
OpenForTraffic is mainly used to prepare for service communication
@Override public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) { // Renewals happen every 30 seconds and for a minute it should be a factor of 2. this.expectedNumberOfClientsSendingRenews = count; updateRenewsPerMinThreshold(); logger.info("Got {} instances from neighboring DS node", count); logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold); this.startupTime = System.currentTimeMillis(); if (count > 0) { this.peerInstancesTransferEmptyOnStartup = false; } DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName(); boolean isAws = Name.Amazon == selfName; if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) { logger.info("Priming AWS connections for all replicas.." ); primeAwsReplicas(applicationInfoManager); } logger.info("Changing status to UP"); applicationInfoManager.setInstanceStatus(InstanceStatus.UP); super.postInit(); }Copy the code
Is cause for request addIntsance applicationInfoManager. SetInstanceStatus (InstanceStatus. UP); This method internally executes a bunch of events and one of them makes a request to addInstance
public synchronized void setInstanceStatus(InstanceStatus status) { InstanceStatus next = instanceStatusMapper.map(status); if (next == null) { return; } InstanceStatus prev = instanceInfo.setStatus(next); if (prev ! = null) { for (StatusChangeListener listener : listeners.values()) { try { listener.notify(new StatusChangeEvent(prev, next)); } catch (Exception e) { logger.warn("failed to notify listener: {}", listener.getId(), e); }}}}Copy the code
Inside the DiscoveryClient class
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
Copy the code
Point to the
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);
}
}
Copy the code
The last is where the request is made to addInstance
boolean register() throws Throwable { logger.info(PREFIX + "{}: registering service..." , appPathIdentifier); EurekaHttpResponse<Void> httpResponse; try { httpResponse = eurekaTransport.registrationClient.register(instanceInfo); } catch (Exception e) { logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e); throw e; } if (logger.isInfoEnabled()) { logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode()); } return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode(); }Copy the code
Making a request is to make it to the Instance method of ApplicationResource.
How does eureka renewal work on the client side and the server side to achieve availability
It can be inferred from the above registrations that the renewal/heartbeat interface may also be done in DiscoveryClient. After searching for HeatBeat, a method is injected into DiscoveryClient to initialize scheduled tasks
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
// Heartbeat timer
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
Copy the code
One of them is a timed task for the heartbeat. The default heartbeat interval renewalIntervalInSecs is 30 seconds
/** * The heartbeat task that renews the lease in the given intervals. */ private class HeartbeatThread implements Runnable { public void run() { if (renew()) { lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis(); }}}Copy the code
Renew method is the process of initiating calls to the server, which is basically the same as the above registration
Eureka offline service
Eureka is offline in EurekaClientAutoConfiguration injection EurekaClient defined when shutDown method. We can see that
@PreDestroy @Override public synchronized void shutdown() { if (isShutdown.compareAndSet(false, true)) { logger.info("Shutting down DiscoveryClient ..." ); if (statusChangeListener ! = null && applicationInfoManager ! = null) { applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId()); } cancelScheduledTasks(); // If APPINFO was registered if (applicationInfoManager ! = null && clientConfig.shouldRegisterWithEureka() && clientConfig.shouldUnregisterOnShutdown()) { applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN); unregister(); } if (eurekaTransport ! = null) { eurekaTransport.shutdown(); } heartbeatStalenessMonitor.shutdown(); registryStalenessMonitor.shutdown(); logger.info("Completed shut down of DiscoveryClient"); }}Copy the code
The same. The e state of a canceled scheduled task was executed. Another use of the above said applicationInfoManager. SetInstanceStatus () method on the event notification, in addition the unregister (); The registration was cancelled. Procedure eurekaTransport.shutdown(); Shut down the transmission.
This is what Eureka’s features look like in general. Some things may not be clear enough. Welcome to discuss with us
Welcome to search attention to my public number [micro view technology], and summary of classified interview questions github.com/zhendiao/Ja…