eureka源码系列 - 续租流程
@Author:zxw
@email:502513206@qq.com
@Jishou University
1.前言
在eureka中有3个定时任务,分别为心跳检测heartbeatExecutor,注册表拉取cacheRefreshExecutor,实例信息更新instanceInfoReplicator。
2.拉取注册中心服务
起点是由CacheRefreshThread
线程进行调用DiscoveryClient.refreshRegistry()
该方法的主要作用就是在拉取注册表前后进行一些操作。
void refreshRegistry() {
try {
boolean isFetchingRemoteRegionRegistries = isFetchingRemoteRegionRegistries();
boolean remoteRegionsModified = false;
// 确保对远程区域进行动态更改以获取数据。
String latestRemoteRegions = clientConfig.fetchRegistryForRemoteRegions();
if (null != latestRemoteRegions) {
String currentRemoteRegions = remoteRegionsToFetch.get();
if (!latestRemoteRegions.equals(currentRemoteRegions)) {
// Both remoteRegionsToFetch and AzToRegionMapper.regionsToFetch need to be in sync
synchronized (instanceRegionChecker.getAzToRegionMapper()) {
if (remoteRegionsToFetch.compareAndSet(currentRemoteRegions, latestRemoteRegions)) {
String[] remoteRegions = latestRemoteRegions.split(",");
remoteRegionsRef.set(remoteRegions);
instanceRegionChecker.getAzToRegionMapper().setRegionsToFetch(remoteRegions);
remoteRegionsModified = true;
} else {
logger.info("Remote regions to fetch modified concurrently," +
" ignoring change from {} to {}", currentRemoteRegions, latestRemoteRegions);
}
}
} else {
// Just refresh mapping to reflect any DNS/Property change
instanceRegionChecker.getAzToRegionMapper().refreshMapping();
}
}
// 拉取注册信息表
boolean success = fetchRegistry(remoteRegionsModified);
// 成功,更新当前时间
if (success) {
registrySize = localRegionApps.get().size();
lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
}
if (logger.isDebugEnabled()) {
// --- 记录log
}
} catch (Throwable e) {
logger.error("Cannot fetch registry from server", e);
}
}
DiscoverClient.fetchRegistry()
如果是一次从注册中心拉取注册表或者设置了全量注册或者关闭了增量注册,则会进行一次全量拉取。默认情况进行增量拉取
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
// 启动计时器
Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
try {
// 如果禁止增量获取或者第一次获取,则进行全量获取
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
{
// 是否关闭了增量
logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta());
//
logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress());
// 是否强制全量更新
logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
// 应用是为null
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() == -1));
// 进行全量更新
getAndStoreFullRegistry();
} else {
// 获取增量注册表信息更新到本地
getAndUpdateDelta(applications);
}
applications.setAppsHashCode(applications.getReconcileHashCode());
logTotalInstances();
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, e.getMessage(), e);
return false;
} finally {
if (tracer != null) {
tracer.stop();
}
}
// 每次刷新本地注册表缓存时(无论是否检测到更改)都调用此方法。
// 调用缓存事件CacheRefreshedEvent
onCacheRefreshed();
// 根据缓存中的数据更新远程状态
// 调用更新事件StatusChangeEvent
updateInstanceRemoteStatus();
// registry was fetched successfully, so return true
return true;
}
DiscoveryClient.getAndUpdateDelta()
从eureka注册中心获取增量注册表并且更新到本地
private void getAndUpdateDelta(Applications applications) throws Throwable {
// 获取应用id
long currentUpdateGeneration = fetchRegistryGeneration.get();
Applications delta = null;
// 远程调用
EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
// 判断是否调用成功
if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
delta = httpResponse.getEntity();
}
// 如果获取增量注册为空,则进行一次全量更新
if (delta == null) {
logger.warn("The server does not allow the delta revision to be applied because it is not safe. "
+ "Hence got the full registry.");
getAndStoreFullRegistry();
} else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
String reconcileHashCode = "";
if (fetchRegistryUpdateLock.tryLock()) {
try {
// 更新从eureka server拉取的信息到本地
updateDelta(delta);
reconcileHashCode = getReconcileHashCode(applications);
} finally {
fetchRegistryUpdateLock.unlock();
}
} else {
logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
}
// There is a diff in number of instances for some reason
if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) {
reconcileAndLogDifference(delta, reconcileHashCode); // this makes a remoteCall
}
} else {
logger.warn("Not updating application delta as another thread is updating it already");
logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode());
}
}
updateDelta(Applications delta)
在该方法中会循环
private void updateDelta(Applications delta) {
// 记录增量更新服务个数
int deltaCount = 0;
// 循环所有eureka实例
for (Application app : delta.getRegisteredApplications()) {
// 循环当前实例的实例信息
for (InstanceInfo instance : app.getInstances()) {
// 获取本地region中的Applications
Applications applications = getApplications();
// 获取实例所处region
String instanceRegion = instanceRegionChecker.getInstanceRegion(instance);
// 判断实例区域是否非本地区域
if (!instanceRegionChecker.isLocalRegion(instanceRegion)) {
Applications remoteApps = remoteRegionVsApps.get(instanceRegion);
if (null == remoteApps) {
remoteApps = new Applications();
remoteRegionVsApps.put(instanceRegion, remoteApps);
}
applications = remoteApps;
}
++deltaCount;
// 判断当前类型为ADDED/MODIFY/DELETED
if (ActionType.ADDED.equals(instance.getActionType())) {
// 从appNameApplicationMap缓存中获取实例信息
Application existingApp = applications.getRegisteredApplications(instance.getAppName());
// 如果当前实例不存在,则添加进appNameApplicationMap缓存中
if (existingApp == null) {
applications.addApplication(app);
}
logger.debug("Added instance {} to the existing apps in region {}", instance.getId(), instanceRegion);
applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
} else if (ActionType.MODIFIED.equals(instance.getActionType())) {
Application existingApp = applications.getRegisteredApplications(instance.getAppName());
if (existingApp == null) {
applications.addApplication(app);
}
logger.debug("Modified instance {} to the existing apps ", instance.getId());
applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
}
// 当实例信息剔除时,会将ActionType改为DELETED
else if (ActionType.DELETED.equals(instance.getActionType())) {
Application existingApp = applications.getRegisteredApplications(instance.getAppName());
// 如果为空
if (existingApp != null) {
logger.debug("Deleted instance {} to the existing apps ", instance.getId());
// 剔除实例信息
existingApp.removeInstance(instance);
/*
* We find all instance list from application(The status of instance status is not only the status is UP but also other status)
* if instance list is empty, we remove the application.
*/
if (existingApp.getInstancesAsIsFromEureka().isEmpty()) {
applications.removeApplication(existingApp);
}
}
}
}
}
logger.debug("The total number of instances fetched by the delta processor : {}", deltaCount);
getApplications().setVersion(delta.getVersion());
getApplications().shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
for (Applications applications : remoteRegionVsApps.values()) {
applications.setVersion(delta.getVersion());
// 在缓存中进行一些更新
applications.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
}
}
自此服务拉取的方法已经完成。该方法的主要核心流程(主讲增量获取getAndUpdateDelta()
)
- 判断是增量还是全量拉取注册表
- 远程调用获取applications
- 如果获取为空,则进行一次全量获取,否则进行一次增量更新
- 循环区域的Applications列表以及instanceInfo表(集群)
- 根据当前实例的状态判断进行ADDED、MODIFY、DELETED操作
- 更新缓存
- 调用回调缓存时间和实例更新事件
3.续约
不管是eureka client还是server,都会定期发送续租命令。跟之前的拉取注册中心服务一样,续约也是通过定时任务进行执行的。
private class HeartbeatThread implements Runnable {
public void run() {
if (renew()) {
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
renew()
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
// 远程调用获取实例信息
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
// 如果实例已过期,则重新注册
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
REREGISTER_COUNTER.increment();
logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
// 获取当前时间戳lastDirtyTimestamp
long timestamp = instanceInfo.setIsDirtyWithTime();
// 再次发起注册
boolean success = register();
if (success) {
// 将unsetDirty时间戳与lastDirty时间戳匹配的unflag重置为unset。
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;
}
}
续租的流程相当比较简单
- 发送请求获取实例信息,会从ResponseCache中获取缓存实例并返回响应信息
- 根据响应信息判断缓存是否已失效,如果失效则重新注册该实例
4.更新注册信息
eureka会定期向注册中心更新自己的实例以及续租信息
InstanceInfoReplicator.run()
public void run() {
try {
// 如果实力信息有更改,则标记为脏数据
discoveryClient.refreshInstanceInfo();
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
// 重新注册自身实力信息
if (dirtyTimestamp != null) {
discoveryClient.register();
// 重置dirty flag
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);
}
}
DiscoverClient.refreshInstanceInfo()
void refreshInstanceInfo() {
// 判断host地址是否更改
applicationInfoManager.refreshDataCenterInfoIfRequired();
// 判断租赁信息是否更改
applicationInfoManager.refreshLeaseInfoIfRequired();
InstanceStatus status;
try {
// 判断当前状态
status = getHealthCheckHandler().getStatus(instanceInfo.getStatus());
} catch (Exception e) {
logger.warn("Exception from healthcheckHandler.getStatus, setting status to DOWN", e);
status = InstanceStatus.DOWN;
}
// 更新状态
if (null != status) {
applicationInfoManager.setInstanceStatus(status);
}
}
``ApplicationInfoManager.refreshDataCenterInfoIfRequired`重新获取client的host地址,以判断其host是否发生迁移,如果迁移则将host和ip清空,并且进行脏数据标记,在下个心跳检查中传递到eureka注册中心中。
public void refreshDataCenterInfoIfRequired() {
// 获取client的host地址
String existingAddress = instanceInfo.getHostName();
String existingSpotInstanceAction = null;
if (instanceInfo.getDataCenterInfo() instanceof AmazonInfo) {
existingSpotInstanceAction = ((AmazonInfo) instanceInfo.getDataCenterInfo()).get(AmazonInfo.MetaDataKey.spotInstanceAction);
}
String newAddress;
// 从config中获取ipaddress
if (config instanceof RefreshableInstanceConfig) {
// Refresh data center info, and return up to date address
newAddress = ((RefreshableInstanceConfig) config).resolveDefaultAddress(true);
} else {
newAddress = config.getHostName(true);
}
String newIp = config.getIpAddress();
// 判断是否发生改变
if (newAddress != null && !newAddress.equals(existingAddress)) {
logger.warn("The address changed from : {} => {}", existingAddress, newAddress);
updateInstanceInfo(newAddress, newIp);
}
if (config.getDataCenterInfo() instanceof AmazonInfo) {
String newSpotInstanceAction = ((AmazonInfo) config.getDataCenterInfo()).get(AmazonInfo.MetaDataKey.spotInstanceAction);
if (newSpotInstanceAction != null && !newSpotInstanceAction.equals(existingSpotInstanceAction)) {
logger.info(String.format("The spot instance termination action changed from: %s => %s",
existingSpotInstanceAction,
newSpotInstanceAction));
updateInstanceInfo(null , null );
}
}
}
ApplicationInfoManager.refreshLeaseInfoIfRequired()
该方法主要判断eureka的租赁信息是否发生更改
public void refreshLeaseInfoIfRequired() {
// 获取租约信息
LeaseInfo leaseInfo = instanceInfo.getLeaseInfo();
if (leaseInfo == null) {
return;
}
// 判断当前租约配置信息是否更改
int currentLeaseDuration = config.getLeaseExpirationDurationInSeconds();
int currentLeaseRenewal = config.getLeaseRenewalIntervalInSeconds();
if (leaseInfo.getDurationInSecs() != currentLeaseDuration || leaseInfo.getRenewalIntervalInSecs() != currentLeaseRenewal) {
LeaseInfo newLeaseInfo = LeaseInfo.Builder.newBuilder()
.setRenewalIntervalInSecs(currentLeaseRenewal)
.setDurationInSecs(currentLeaseDuration)
.build();
instanceInfo.setLeaseInfo(newLeaseInfo);
instanceInfo.setIsDirty();
}
}
以上就是eureka续租、拉取注册表等功能实现的分析。