从头学习SpringCloud(四)Eureka服务注册
序
这篇文章来看一下DiscoveryClient类是怎么实现服务注册的。
DiscoveryClient类有一个initScheduledTasks方法。下面是initScheduledTasks的源码。
/**
* Initializes all scheduled tasks.
*/
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
cacheRefreshTask = new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
);
scheduler.schedule(
cacheRefreshTask,
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
heartbeatTask = new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
);
scheduler.schedule(
heartbeatTask,
renewalIntervalInSecs, TimeUnit.SECONDS);
// InstanceInfo replicator
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
注释说明这个方法初始化了所有的规定任务。服务注册也是在这个方法里。
有一段if分支是clientConfig.shouldRegisterWithEureka(),判断是否发起注册,其实就是获取配置文件中的registration.enabled,系统默认为true。
在这个分支内会创建一个InstanceInfoReplicator类,注释说明是“复制实例信息”。该类的完整代码如下。
/**
* A task for updating and replicating the local instanceinfo to the remote server. Properties of this task are:
* - configured with a single update thread to guarantee sequential update to the remote server
* - update tasks can be scheduled on-demand via onDemandUpdate()
* - task processing is rate limited by burstSize
* - a new update task is always scheduled automatically after an earlier update task. However if an on-demand task
* is started, the scheduled automatic update task is discarded (and a new one will be scheduled after the new
* on-demand update).
*
* @author dliu
*/
翻译一下这个类的注释:这个任务用来更新和复制本地的实例信息到远程服务器。这个类的特征是:配置一个单独的更新线程来保证顺序更新到远程服务器;任务更新可以通过"onDemandUpdate()"按需安排;任务处理速率受burstSize限制;在较早的更新任务之后,新的更新任务始终会被自动计划。 但是,如果启动了某项按需任务,则计划的自动更新任务将被废弃(并且,按需更新之后将安排新的计划任务)。
InstanceInfoReplicator的start方法来开启应用实例信息复制器。
public void start(int initialDelayMs) {
if (started.compareAndSet(false, true)) {
instanceInfo.setIsDirty(); // for initial register
Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
执行 instanceInfo.setIsDirty() 代码块,因为 InstanceInfo 刚被创建时,在 Eureka-Server 不存在,也会被注册。
InstanceInfoReplicator会执行一个定时任务,定时检查 InstanceInfo 的状态( status ) 属性是否发生变化。若是,发起注册。具体工作看一下run()方法的实现。run()方法中有一句discoveryClient.register();这个就是注册的方法了。代码如下。
/**
* Register with the eureka service by making the appropriate REST call.
*/
boolean register() throws Throwable {
logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
EurekaHttpResponse<Void> httpResponse;
try {
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();
}
调用 DiscoveryClient#refreshInstanceInfo() 方法,刷新应用实例信息。
调用 DiscoveryClient#register() 方法,Eureka-Client 向 Eureka-Server 注册应用实例。
调用 ScheduledExecutorService#schedule(...) 方法,再次延迟执行任务,并设置 scheduledPeriodicRef。通过这样的方式,不断循环定时执行任务。