深入理解Eureka覆盖状态(九)

应用场景

在实际开发使用过程当中,在Eureka Admin控制台上,我们想强制下线某个服务 ,就需要用到覆盖状态的

概念,其实说白了,就是在给实例存储另外一个状态,当续约,注册的时候,以这个覆盖状态为准 。

覆盖状态

设置覆盖状态

程序入口: com.netflix.eureka.resources.InstanceResource

@PUT
@Path("status")
public Response statusUpdate(
        @QueryParam("value") String newStatus,
        @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
        @QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
    try {
        // 判断实例是否存在,不存在则更新失败
        if (registry.getInstanceByAppAndId(app.getName(), id) == null) {
            logger.warn("Instance not found: {}/{}", app.getName(), id);
            return Response.status(Status.NOT_FOUND).build();
        }
        // 更新状态
        boolean isSuccess = registry.statusUpdate(app.getName(), id,
                InstanceStatus.valueOf(newStatus), lastDirtyTimestamp,
                "true".equals(isReplication));

        if (isSuccess) {
            logger.info("Status updated: " + app.getName() + " - " + id
                    + " - " + newStatus);
            return Response.ok().build();
        } else {
            logger.warn("Unable to update status: " + app.getName() + " - "
                    + id + " - " + newStatus);
            return Response.serverError().build();
        }
    } catch (Throwable e) {
        logger.error("Error updating instance {} for status {}", id,
                newStatus);
        return Response.serverError().build();
    }
}

1.判断实例在服务端是否存在,不存在则返回更新失败

2.调用更新状态的接口

public boolean statusUpdate(final String appName, final String id,
                            final InstanceStatus newStatus, String lastDirtyTimestamp,
                            final boolean isReplication) {
    // 调用父类的状态更新
    if (super.statusUpdate(appName, id, newStatus, lastDirtyTimestamp, isReplication)) {
        // Eureka Server 集群同步
        replicateToPeers(Action.StatusUpdate, appName, id, null, newStatus, isReplication);
        return true;
    }
    return false;
}


public boolean statusUpdate(String appName, String id,
                            InstanceStatus newStatus, String lastDirtyTimestamp,
                            boolean isReplication) {
    try {
        //使用读锁
        read.lock();
        // 更新状态的次数 状态统计
        STATUS_UPDATE.increment(isReplication);
        // 从本地数据里面获取实例信息,
        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
        Lease<InstanceInfo> lease = null;
        if (gMap != null) {
            lease = gMap.get(id);
        }
        // 实例不存在,则直接返回,更细你失败
        if (lease == null) {
            return false;
        } else {
            // 执行一下lease的renew方法,里面主要是更新了这个instance的最后更新时间。
            lease.renew();
            // 获取instance实例信息
            InstanceInfo info = lease.getHolder();
            // Lease is always created with its instance info object.
            // This log statement is provided as a safeguard, in case this invariant is violated.
            if (info == null) {
                logger.error("Found Lease without a holder for instance id {}", id);
            }
            // 当instance信息不为空时
            if ((info != null) && !(info.getStatus().equals(newStatus))) {
                // Mark service as UP if needed
                // 如果新状态是UP的状态,那么启动一下serviceUp() , 主要是更新服务的注册时间。 
                if (InstanceStatus.UP.equals(newStatus)) {
                    lease.serviceUp();
                }
                // 将instance Id 和这个状态的映射信息放入覆盖缓存MAP里面去
                overriddenInstanceStatusMap.put(id, newStatus);
                // Set it for transfer of overridden status to replica on
                // replica start up
                // 设置覆盖状态到实例信息里面去
                info.setOverriddenStatus(newStatus);
                long replicaDirtyTimestamp = 0;
                if (lastDirtyTimestamp != null) {
                    replicaDirtyTimestamp = Long.valueOf(lastDirtyTimestamp);
                }
                //
                // 如果replicaDirtyTimestamp 的时间大于instance的getLastDirtyTimestamp() ,则更新一下
                // 此处主要作用是为了设置instance的status 为新状态
                if (replicaDirtyTimestamp > info.getLastDirtyTimestamp()) {
                    info.setLastDirtyTimestamp(replicaDirtyTimestamp);
                    info.setStatusWithoutDirty(newStatus);
                } else {
                    info.setStatus(newStatus);
                }
                // 将instance的变化放入变化队列里面去,客户端增量获取注册信息时,会用到。
                info.setActionType(ActionType.MODIFIED);
                recentlyChangedQueue.add(new RecentlyChangedItem(lease));
                info.setLastUpdatedTimestamp();
                // 缓存过期
                invalidateCache(appName, info.getVIPAddress(), info.getSecureVipAddress());
            }
            return true;
        }
    } finally {
        read.unlock();
    }
}

步骤如下:

1.获取实例信息,判断instance信息是否为空

2.将instanceId和覆盖状态,放入overriddenInstanceStatusMap 这个缓存MAP中进行了存储 , 过期时间默认为 1小时

3.更新instance的状态

4.添加instance Change 的记录进入队列

5.清除缓存。

删除覆盖状态

程序入口: com.netflix.eureka.resources.InstanceResource

@DELETE
@Path("status")
public Response deleteStatusUpdate(
        @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
        @QueryParam("value") String newStatusValue,
        @QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
    try {
        // 判断应用是否为空
        if (registry.getInstanceByAppAndId(app.getName(), id) == null) {
            logger.warn("Instance not found: {}/{}", app.getName(), id);
            return Response.status(Status.NOT_FOUND).build();
        }
        // 删除覆盖状态
        InstanceStatus newStatus = newStatusValue == null ? InstanceStatus.UNKNOWN : InstanceStatus.valueOf(newStatusValue);
        boolean isSuccess = registry.deleteStatusOverride(app.getName(), id,
                newStatus, lastDirtyTimestamp, "true".equals(isReplication));

        if (isSuccess) {
            logger.info("Status override removed: " + app.getName() + " - " + id);
            return Response.ok().build();
        } else {
            logger.warn("Unable to remove status override: " + app.getName() + " - " + id);
            return Response.serverError().build();
        }
    } catch (Throwable e) {
        logger.error("Error removing instance's {} status override", id);
        return Response.serverError().build();
    }
}

步骤说明:

1.判断应用信息是否为空

2.执行删除覆盖状态的接口

@Override
public boolean deleteStatusOverride(String appName, String id,
                                    InstanceStatus newStatus,
                                    String lastDirtyTimestamp,
                                    boolean isReplication) {
    // 删除覆盖状态
    if (super.deleteStatusOverride(appName, id, newStatus, lastDirtyTimestamp, isReplication)) {
        // Eureka Server 集群同步
        replicateToPeers(Action.DeleteStatusOverride, appName, id, null, null, isReplication);
        return true;
    }
    return false;
}


@Override
public boolean deleteStatusOverride(String appName, String id,
                                    InstanceStatus newStatus,
                                    String lastDirtyTimestamp,
                                    boolean isReplication) {
    try {
        // 上读锁
        read.lock();
        // 覆盖状态删除次数统计
        STATUS_OVERRIDE_DELETE.increment(isReplication);
        // 从本地获取appName对应的实例信息
        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
        Lease<InstanceInfo> lease = null;
        if (gMap != null) {
            lease = gMap.get(id);
        }
        if (lease == null) {
            return false;
        } else {
            // 
            lease.renew();
            InstanceInfo info = lease.getHolder();

            // Lease is always created with its instance info object.
            // This log statement is provided as a safeguard, in case this invariant is violated.
            if (info == null) {
                logger.error("Found Lease without a holder for instance id {}", id);
            }
            // 将这个instanceID 对应的覆盖状态信息从MAP中移除
            InstanceStatus currentOverride = overriddenInstanceStatusMap.remove(id);
            if (currentOverride != null && info != null) {  
                // 设置instanceInfo的覆盖状态为UNKONW, 这是初始状态
                info.setOverriddenStatus(InstanceStatus.UNKNOWN);
                // 设置instance的status为最新的状态
                info.setStatus(newStatus);
                long replicaDirtyTimestamp = 0;
                if (lastDirtyTimestamp != null) {
                    replicaDirtyTimestamp = Long.valueOf(lastDirtyTimestamp);
                }
                // If the replication's dirty timestamp is more than the existing one, just update
                // it to the replica's.
                if (replicaDirtyTimestamp > info.getLastDirtyTimestamp()) {
                    info.setLastDirtyTimestamp(replicaDirtyTimestamp);
                }
                // 将instance的变化放入变化队列里面去,客户端增量获取注册信息时,会用到。
                info.setActionType(ActionType.MODIFIED);
                recentlyChangedQueue.add(new RecentlyChangedItem(lease));
                // 更新instance的最后更新时间
                info.setLastUpdatedTimestamp();
                // 缓存过期
                invalidateCache(appName, info.getVIPAddress(), info.getSecureVipAddress());
            }
            return true;
        }
    } finally {
        read.unlock();
    }
}

步骤说明:

1.从本地的CurrentHashMap中获取appName对应的应用信息,然后通过instanceId获取该应用对应的机器

2.移除overriddenInstanceStatusMap中该instanceId对应的覆盖状态

3.设置instanceInfo的覆盖状态为UNKNOW,这个是他的初始状态。

  1. 设置instance的status为新状态

5.添加instance Change 的记录进入队列

6.清除缓存。

实际应用

客户端注册

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    try {
        
        // .....省略N多代码
        // 判断instance的的覆盖状态是否等于UNKONW (默认状态下就是等于UNKONW)
        if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
            // 如果不等于,则说明被修改过,放入overriddenInstanceStatusMap
            logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
                            + "overrides", registrant.getOverriddenStatus(), registrant.getId());
            if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
                logger.info("Not found overridden id {} and hence adding it", registrant.getId());
                overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
            }
        }
        // overriddenInstanceStatusMap 里面是否存在这个instanceId的覆盖状态
        InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
        // 如果存在,则设置进去
        if (overriddenStatusFromMap != null) {
            logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
            registrant.setOverriddenStatus(overriddenStatusFromMap);
        }

        // 获取instance的状态,并设置进去
        InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
        registrant.setStatusWithoutDirty(overriddenInstanceStatus);

     
        // .....省略N多代码
    } finally {
        read.unlock();
    }
}

续约

public boolean renew(String appName, String id, boolean isReplication) {
    RENEW.increment(isReplication);
    Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
    Lease<InstanceInfo> leaseToRenew = null;
    if (gMap != null) {
        leaseToRenew = gMap.get(id);
    }
    if (leaseToRenew == null) {
        RENEW_NOT_FOUND.increment(isReplication);
        logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
        return false;
    } else {
        InstanceInfo instanceInfo = leaseToRenew.getHolder();
        if (instanceInfo != null) {
            // 获取应用实例的最终状态
            InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
                    instanceInfo, leaseToRenew, isReplication);
            // 如果应用实例的最终状态为UNKONW,则无法续约。 
            if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
                logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"
                        + "; re-register required", instanceInfo.getId());
                RENEW_NOT_FOUND.increment(isReplication);
                return false;
            }
            // 应用实例的状态和最终的状态不一致,则以最终状态为准
            if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
                Object[] args = {
                        instanceInfo.getStatus().name(),
                        instanceInfo.getOverriddenStatus().name(),
                        instanceInfo.getId()
                };
               
                instanceInfo.setStatus(overriddenInstanceStatus);
            }
        }
        renewsLastMin.increment();
        leaseToRenew.renew();
        return true;
    }
}

步骤说明:

1.获取应用实例的最终状态

2.如果最终状态为UNKONW,则无法续约,返回false ,存在UNKONW的可能性,在deleteStatusOverride()的时候

存在UNKONW的可能性

3.应用实例的状态和最终状态不一致,以最终状态为准。

下线

protected boolean internalCancel(String appName, String id, boolean isReplication) {
    try {
        // ..... 省略N多代码
        // 将覆盖状态移除
        InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
        // ..... 省略N多代码
    } finally {
        read.unlock();
    }
}

步骤说明:

1.移除覆盖状态

过期

和下线一致

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

推荐阅读更多精彩内容