深入理解lastDirtyTimestamp(十二)

简介

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();
}
  1. 判断isInstanceInfoDirty 是否为true, 是的话,则返回最后修改时间


public synchronized Long isDirtyWithTime() {
    if (isInstanceInfoDirty) {
        return lastDirtyTimestamp;
    } else {
        return null;
    }
}
  1. 如果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();
    }
}
  1. 调用健康检查器,获取当前的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

  1. 调用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错误,客户端就需要重新注册了, 具体机制可以查看

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();
    }
}

猜你喜欢

转载自blog.csdn.net/u012394095/article/details/80966441