Eureka server启动流程图和源码分析
流程图
源码解析
由流程图看出,Eureka client启动时大致会做三件事,服务注册,心跳续约,服务更新,本质上都是通过模拟http请求,调用服务端提供的对应接口来实现
服务注册
通过服务端提供的“服务注册”接口,将当前client信息注册到注册中心。client初始化时,会将服务注册线程延迟40秒执行,并且每次注册结束后,会再次将当前线程延迟30秒(可配置)执行。这样当eureca服务内存丢失后(如重启服务),在一定时间内还能得到client信息。
配置如下:
eureka:
client:
initial-instance-info-replication-interval-seconds: 20 #首次注册延迟20秒
instance-info-replication-interval-seconds: 60 #以后每次60秒注册一次
registry-fetch-interval-seconds: 30 #心跳续约间隔时间
首次注册源码如下:
//client初始化时会调用此方法
public void start(int initialDelayMs) {
if (started.compareAndSet(false, true)) {
instanceInfo.setIsDirty(); // for initial register
//scheduler为ScheduledExecutorService延迟线程池,会在initialDelayMs秒后执行一次线程任务
//this为当前类,实现了Runnable接口
Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
当前类的run方法源码如下:
public void run() {
try {
//更新实例信息
discoveryClient.refreshInstanceInfo();
//若心跳续约异常,则dirtyTimestamp不为null
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 {
//将当前线程类放在延迟replicationIntervalSeconds秒的延迟线程池里执行
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
心跳续约
每30秒调用服务端提供的心跳续约接口,服务端接收到请求,会修改最后续约时间为当前系统时间。
//可通过配置register-with-eureka进行控制
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);
...
HeartbeatThread的run方法会调用renew(),源码如下:
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
//模拟http请求,调用服务端提供的“接收心跳续约”接口
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
//404异常处理
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
REREGISTER_COUNTER.increment();
logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
long timestamp = instanceInfo.setIsDirtyWithTime();
boolean success = register();
if (success) {
instanceInfo.unsetIsDirty(timestamp);
}
return success;
}
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
return false;
}
}
服务更新
每30秒(可配置)调用服务端提供的获取实例接口,首次会调用获取全量实例,以后调用“获取增量实例”增量更新。“获取增量实例”接口会返回其所有实例的按一定规则拼接的hashcode,客户端更新后也会以相同规则拼接hashcode,做数据一致性校验
//通过fetch-registry: true 参数控制
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);
}
CacheRefreshThread的run()方法会调用refreshRegistry方法,源码如下:
//模拟http请求,调用服务端提供的“获取全量实例”接口
boolean success = fetchRegistry(remoteRegionsModified);
if (success) {
registrySize = localRegionApps.get().size();
lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
}
fetchRegistry()方法源码如下:
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
try {
// If the delta is disabled or if it is the first time, get all
// applications
Applications applications = getApplications();
//如果首次调用
if (clientConfig.shouldDisableDelta()
|| (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
|| forceFullRegistryFetch
|| (applications == null)
|| (applications.getRegisteredApplications().size() == 0)
|| (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
{
...
//全量更新
getAndStoreFullRegistry();
} else {
//增量更新
getAndUpdateDelta(applications);
}
...
return true;
}