深入理解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错误,客户端就需要重新注册了, 具体机制可以查看深入理解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();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容