简介
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; } 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( PeerEurekaNode.HEADER_REPLICATION) String isReplication, ( "overriddenstatus") String overriddenStatus, ( "status") String status, ( "lastDirtyTimestamp") String lastDirtyTimestamp) { ( // ...... return response; } "status") (public Response statusUpdate( "value") String newStatus, ( PeerEurekaNode.HEADER_REPLICATION) String isReplication, ( "lastDirtyTimestamp") String lastDirtyTimestamp) { ( // ...... } "status") (public Response deleteStatusUpdate( PeerEurekaNode.HEADER_REPLICATION) String isReplication, ( "value") String newStatusValue, ( "lastDirtyTimestamp") String lastDirtyTimestamp) { ( // ...... }
以上三个方法中,当服务端检测到本地的instanceStatue需要更新的时候,也会更新Eureka Server本地的lastDirtyTimestamp ,下面针对
续约和注册讲一下在其中的用法。
续约
renew续约完成之后,会判断传入的lastDirtyTimestamp 和客户端本地的lastDirtyTimestamp 是否一致,如果客户端的值大,那么就会返回404错误,客户端就需要重新注册了, 具体机制可以查看
https://blog.csdn.net/u012394095/article/details/80755853
中的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(); } }