简介
lastDirtyTimestamp在Eureka中承载了比较重要的作用,在续约,设置覆盖状态,删除覆盖状态的
时候都有用到。
定义: 实例的最后修改时间
Eureka Client
EurekaClient在系统启动的时候,会启动一个定时任务,每40秒执行一次,该定时任务负责比对
客户端的信息,如果发生改变则更新lastDirtyTimestamp的值,同时对Eureka Server 重新发起注册。
public void run() {
try {
// 该方法负责比对客户端存在的instance信息和实际的信息,是否发生改变
// 比如: 客户端的状态,IP,配置信息发生改变
discoveryClient.refreshInstanceInfo();
// 判断信息是否改变,是否需要重新注册。通过isInstanceInfoDirty这个布尔值判断
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);
}
}
步骤说明:
1.判断客户端的状态,IP,配置信息是否发生改变,如果发生改变则会调用setIsDirty() , 设置最后更新时间
lastDirtyTimestamp , 同时设置isInstanceInfoDirty = true
public synchronized void setIsDirty() {
isInstanceInfoDirty = true;
lastDirtyTimestamp = System.currentTimeMillis();
}
- 判断isInstanceInfoDirty 是否为true, 是的话,则返回最后修改时间
public synchronized Long isDirtyWithTime() {
if (isInstanceInfoDirty) {
return lastDirtyTimestamp;
} else {
return null;
}
}
- 如果isDirtyWithTime()不为null, 则需要重新发起注册
4.卸载isInstanceInfoDirty 的状态,修改为false
接下来重点讲一下refreshInstanceInfo方法。
void refreshInstanceInfo() {
// 1. 判断数据中心的数据是否发生改变,
applicationInfoManager.refreshDataCenterInfoIfRequired();
// 2.判断配置是否发生改变
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);
}
}
步骤说明:
1.refreshDataCenterInfoIfRequired()方法,里面主要是判断应用的IP地址是否发生改变。
public void refreshDataCenterInfoIfRequired() {
// 获取当前的地址
String existingAddress = instanceInfo.getHostName();
// 获取当前实际的地址
String newAddress;
if (config instanceof RefreshableInstanceConfig) {
newAddress = ((RefreshableInstanceConfig) config).resolveDefaultAddress(true);
} else {
newAddress = config.getHostName(true);
}
String newIp = config.getIpAddress();
// 判断新旧地址是否一致,如果不一致,则进入if结构
if (newAddress != null && !newAddress.equals(existingAddress)) {
logger.warn("The address changed from : {} => {}", existingAddress, newAddress);
InstanceInfo.Builder builder = new InstanceInfo.Builder(instanceInfo);
builder.setHostName(newAddress).setIPAddr(newIp).setDataCenterInfo(config.getDataCenterInfo());
// 该方法最为重要,表示信息已经发生改变,
instanceInfo.setIsDirty();
}
}
// InstanceInfo.java
public synchronized void setIsDirty() {
isInstanceInfoDirty = true;
lastDirtyTimestamp = System.currentTimeMillis();
}
如上代码所示,如果hostName发生改变,则会更新本地的Instance的信息,同时调用setIsDirty()方法,表示信息已经被改变。
更新isInstanceInfoDirty = true ,同时设置lastDirtyTimestamp 为系统当前时间
2.refreshLeaseInfoIfRequired() 判断配置中的,“租约过期时间” , “续约时间” 是否发生改变,如果发生改变了,那么就需要
更新本地instance的信息,同时调用setIsDirty()方法表示信息已经被改变,需要重新注册
public void refreshLeaseInfoIfRequired() {
LeaseInfo leaseInfo = instanceInfo.getLeaseInfo();
if (leaseInfo == null) {
return;
}
// 租约过期时间,默认90秒
int currentLeaseDuration = config.getLeaseExpirationDurationInSeconds();
// 续约时间,默认30秒
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();
}
}
- 调用健康检查器,获取当前的instance的状态com.netflix.appinfo.HealthCheckCallbackToHandlerBridge
public class HealthCheckCallbackToHandlerBridge implements HealthCheckHandler {
private final HealthCheckCallback callback;
public HealthCheckCallbackToHandlerBridge() {
callback = null;
}
public HealthCheckCallbackToHandlerBridge(HealthCheckCallback callback) {
this.callback = callback;
}
@Override
public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus currentStatus) {
// 判断instance的状态。
if (null == callback || InstanceInfo.InstanceStatus.STARTING == currentStatus
|| InstanceInfo.InstanceStatus.OUT_OF_SERVICE == currentStatus) { // Do not go to healthcheck handler if the status is starting or OOS.
return currentStatus;
}
return callback.isHealthy() ? InstanceInfo.InstanceStatus.UP : InstanceInfo.InstanceStatus.DOWN;
}
}
由上可知, 健康检查器中的getStatus方法,判断步骤
判断callback是否为空 , 如果为空,则以当前实例的状态为准(默认为null , 如果我们想自己实现自定义的健康检查,可以设置起来)
判断传入的当前实例的状态是否等于STARTING, OUT_OF_SERVICE 这两个状态,如果等于,则以当前实例的状态为准
使用callback.isHealty()判断实例的健康状态,然后返回UP或则DOWN
- 调用applicationInfoManager.setInstanceStatus(status); , 设置实例的状态,如果传入的状态和实例的状态一直,则不会修改。
如果状态不一致,则会修改instance的状态,同时调用setIsDirty() 表示信息发生改变。
public synchronized InstanceStatus setStatus(InstanceStatus status) {
// 判断状态是否一致,一致则不更新
if (this.status != status) {
InstanceStatus prev = this.status;
this.status = status;
//调用该方法,表示信息发生修改
setIsDirty();
return prev;
}
return null;
}
综合以上代码分析,我们可以发现,当客户端的信息发生任何改变的时候,都会调用setIsDirty() , 更新isInstanceInfoDirty = true ,
同时设置lastDirtyTimestamp 为系统当前时间 , 由此可见,lastDirtyTimestamp 的定义为“实例的最后修改时间”
Eureka Server 用途
服务端在接收renew , stateUpdate, deleteStatusUpdate 的时候,都会要求客户端传入lastDirtyTimestamp 这个参数 ,注册的时候也会对这个值做对比
public Response renewLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("overriddenstatus") String overriddenStatus,
@QueryParam("status") String status,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
// ......
return response;
}
@Path("status")
public Response statusUpdate(
@QueryParam("value") String newStatus,
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
// ......
}
@DELETE
@Path("status")
public Response deleteStatusUpdate(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("value") String newStatusValue,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
// ......
}
以上三个方法中,当服务端检测到本地的instanceStatue需要更新的时候,也会更新Eureka Server本地的lastDirtyTimestamp ,下面针对
续约和注册讲一下在其中的用法。
续约
renew续约完成之后,会判断传入的lastDirtyTimestamp 和客户端本地的lastDirtyTimestamp 是否一致,如果客户端的值大,那么就会返回404错误,客户端就需要重新注册了, 具体机制可以查看深入理解Eureka 心跳续约(三)中的Eureka Server接收心跳那一小结。
注册
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
// .... 省略代码
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
//如果Eureka Server中该实例已经存在
if (existingLease != null && (existingLease.getHolder() != null)) {
// 比较lastDirtyTimestamp , 以lastDirtyTimestamp大的为准
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
registrant = existingLease.getHolder();
}
}
// .... 省略代码
} finally {
read.unlock();
}
}