在Eureka Client 源码解析这篇文章中,说到按需注册定时任务的时候,当时没有对 InstanceInfoReplicator#run 方法中的DiscoveryClient#refreshInstanceInfo 方法中的一些调用进行解析,因此这里来就这个来进行一下简单的解析。
首先在 DiscoveryClient#refreshInstanceInfo 方法中开始有两个方法的调用ApplicationInfoManager#refreshDataCenterInfoIfRequired 和 ApplicationInfoManager#refreshLeaseInfoIfRequired,本篇文章就将会就这两个方法的调用进行一些解析。
refreshDataCenterInfoIfRequired 方法
从方法命名上,可以看出这个方法是用来刷新数据中心信息,这个方法主要是获取主机名,并检查它是否有改变,如果有的话,就对整个DataCenterInfo 进行重新获取,并传给 server 端,用于下一次的心跳。但是这里面主要是用于亚马逊的数据中心,因此一般情况是用不到的,所以这里就做一个简单的解析。那么这里来看看这个方法的代码实现,如下:
/**
* Refetches the hostname to check if it has changed. If it has, the entire
* <code>DataCenterInfo</code> is refetched and passed on to the eureka
* server on next heartbeat.
*
* see {@link InstanceInfo#getHostName()} for explanation on why the hostname is used as the default address
*/
public void refreshDataCenterInfoIfRequired() {
// 获取主机名
String existingAddress = instanceInfo.getHostName();
String existingSpotInstanceAction = null;
// 判断是否属于亚马逊数据中心
if (instanceInfo.getDataCenterInfo() instanceof AmazonInfo) {
existingSpotInstanceAction = ((AmazonInfo) instanceInfo.getDataCenterInfo()).get(AmazonInfo.MetaDataKey.spotInstanceAction);
}
String newAddress;
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);
}
// 判断配置文件中的数据中心是否属于 AmazonInfo
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 );
}
}
}
refreshLeaseInfoIfRequired 方法
这个方法是用与刷新租约信息的,这里先看下这个方法的实现,代码如下:
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);
// 设置dirty
instanceInfo.setIsDirty();
}
}
迭代稳定性:一般情况下,对于多线程操作的共享数组、集合,我们对其元素进行修改操作时,不要直接对该数组、集合进行操作,而是创建一个临时数组、集合,将原数组、集合中的数据复制给临时数组、集合,然后再对这个临时数组进行修改操作,执行完毕后在将临时数组、集合赋值给原数组、集合。这个操作时为了保证迭代稳定性。这里的操作要保证互斥(加锁)。
这段代码不长,首先是通过 instanceInfo 来获取租约信息,那么先来看看LeaseInfo中有哪些元素,代码如下:
@JsonRootName("leaseInfo")
public class LeaseInfo {
// 续约时间间隔
public static final int DEFAULT_LEASE_RENEWAL_INTERVAL = 30;
// 如果 90 s(默认)没有发心跳,服务端就认为这个客户端挂了,就可以在服务端删除客户端的实例信息
public static final int DEFAULT_LEASE_DURATION = 90;
// Client settings
private int renewalIntervalInSecs = DEFAULT_LEASE_RENEWAL_INTERVAL;
private int durationInSecs = DEFAULT_LEASE_DURATION;
// Server populated
private long registrationTimestamp;
private long lastRenewalTimestamp;
private long evictionTimestamp;
private long serviceUpTimestamp;
// 省略部分代码
}
在ApplicationInfoManager#refreshLeaseInfoIfRequired方法中,还会获取租约过期间隔信息和租约续约的时间间隔,然后继续判断这两个是否符合标准。当符合标准的时候,便会重新设置租约信息和dirty,这里设置dirty是为一开始提到的InstanceInfoReplicator#run方法的后续做准备的,这里先看下设置dirty的方法,代码如下:
public synchronized void setIsDirty() {
isInstanceInfoDirty = true;
lastDirtyTimestamp = System.currentTimeMillis();
}
方法很简单,就是把dirty设置为true,并设置了一个时间戳,以备后续之用,这时就可以回顾一下InstanceInfoReplicator#run方法,代码如下:
public void run() {
try {
discoveryClient.refreshInstanceInfo();
// 获取 dirty 的时间戳
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
discoveryClient.register();
// 重置 dirty
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);
}
}
public synchronized void unsetIsDirty(long unsetDirtyTimestamp) {
// 如果 lastDirtyTimestamp <= unsetDirtyTimestamp ,表示前面的更新时成立的
if (lastDirtyTimestamp <= unsetDirtyTimestamp) {
isInstanceInfoDirty = false;
} else {
}
}
在前面设置dirty的时候,便设置了时间戳,因此这里的dirtyTimestamp便不会为null,所以这是便会执行discoveryClient.register()方法,当注册完毕后,便会执行finally中的任务。