美文网首页
深入理解Eureka主动下线(四)

深入理解Eureka主动下线(四)

作者: sharedCode | 来源:发表于2018-08-08 10:23 被阅读0次

    程序入口

    com.netflix.discovery.DiscoverClient

    @PreDestroy
    @Override
    public synchronized void shutdown() {
        if (isShutdown.compareAndSet(false, true)) {
            logger.info("Shutting down DiscoveryClient ...");
     
            if (statusChangeListener != null && applicationInfoManager != null) {
                applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
            }
            // 取消定时任务(心跳,缓存刷新等)
            cancelScheduledTasks();
     
            // 如果app注册过,那么需要取消注册,也就说需要主动下线
            if (applicationInfoManager != null && clientConfig.shouldRegisterWithEureka()) {
                // 设置实例的状态为DOWN
                applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
                // 执行下线
                unregister();
            }
     
            if (eurekaTransport != null) {
                eurekaTransport.shutdown();
            }
     
            heartbeatStalenessMonitor.shutdown();
            registryStalenessMonitor.shutdown();
     
            logger.info("Completed shut down of DiscoveryClient");
        }
    }
    

    在这个类被容器销毁的时候,会执行这个方法,执行主动下线的代码

    unregister()

    void unregister() {
        // 如果是非注册的实例,那么eurekaTransport可能是为空的,所以做一下判断
        if(eurekaTransport != null && eurekaTransport.registrationClient != null) {
            try {
                logger.info("Unregistering ...");
                // 发送HTTP请求,到服务端,请求下线 , 接口地址:  "apps/" + appName + '/' + id;
                EurekaHttpResponse<Void> httpResponse = eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
                logger.info(PREFIX + appPathIdentifier + " - deregister  status: " + httpResponse.getStatusCode());
            } catch (Exception e) {
                logger.error(PREFIX + appPathIdentifier + " - de-registration failed" + e.getMessage(), e);
            }
        }
    }
    

    Eureka-Server

    InstanceResource

    com.netflix.eureka.resources.InstanceResource

    @DELETE
    public Response cancelLease(
            @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
        //执行下线请求
        boolean isSuccess = registry.cancel(app.getName(), id,
                "true".equals(isReplication));
     
        if (isSuccess) {
            logger.debug("Found (Cancel): " + app.getName() + " - " + id);
            return Response.ok().build();
        } else {
            logger.info("Not Found (Cancel): " + app.getName() + " - " + id);
            return Response.status(Status.NOT_FOUND).build();
        }
    }
    

    InstanceRegistry

    org.springframework.cloud.netflix.eureka.server.InstanceRegistry

    @Override
    public boolean cancel(String appName, String serverId, boolean isReplication) {
       //发布取消事件
       handleCancelation(appName, serverId, isReplication);
       // 调用父类的取消方法
       return super.cancel(appName, serverId, isReplication);
    }
    private void handleCancelation(String appName, String id, boolean isReplication) {
       log("cancel " + appName + ", serverId " + id + ", isReplication " + isReplication);
       publishEvent(new EurekaInstanceCanceledEvent(this, appName, id, isReplication));
    }
    

    父类PeerAwareInstanceRegistryImpl

    @Override
    public boolean cancel(final String appName, final String id,
                          final boolean isReplication) {
        // 执行父类的取消方法
        if (super.cancel(appName, id, isReplication)) {
            //集群同步信息
            replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
            // Eureka-Server的保护机制
            synchronized (lock) {
                if (this.expectedNumberOfRenewsPerMin > 0) {
                    // Since the client wants to cancel it, reduce the threshold (1 for 30 seconds, 2 for a minute)
                    this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
                    this.numberOfRenewsPerMinThreshold =
                            (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
                }
            }
            return true;
        }
        return false;
    }
    

    PeerAwareInstanceRegistryImpl 的父类AbstractInstanceRegistry中是
    取消下线的主要逻辑。

    @Override
    public boolean cancel(String appName, String id, boolean isReplication) {
        return internalCancel(appName, id, isReplication);
    }
     
    /**
     * {@link #cancel(String, String, boolean)} method is overridden by {@link PeerAwareInstanceRegistry}, so each
     * cancel request is replicated to the peers. This is however not desired for expires which would be counted
     * in the remote peers as valid cancellations, so self preservation mode would not kick-in.
     */
    protected boolean internalCancel(String appName, String id, boolean isReplication) {
        try {
            // 读锁
            read.lock();
            // 添加取消次数给监控信息,这里是个枚举类,收集了取消次数
            CANCEL.increment(isReplication);
            // 从本地的CurrentHashMap中,获取当前实例对应的Lease信息
            Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
            Lease<InstanceInfo> leaseToCancel = null;
            if (gMap != null) {
                // 移除信息 , 如果客户端是集群模式,此处仅移除这个实例ID对应的信息
                leaseToCancel = gMap.remove(id);
            }
            // 添加取消信息到取消队列,主要用于运维界面的信息统计
            synchronized (recentCanceledQueue) {
                recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
            }
            //移除这个实例ID对应的instance状态
            InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
            if (instanceStatus != null) {
                logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
            }
            if (leaseToCancel == null) {
                // 如果信息不存在,则说明这个实例从来没有注册过来,或者已经下线了。
                CANCEL_NOT_FOUND.increment(isReplication);
                logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
                return false;
            } else {
                // 更新Lease实例信息里面的evictionTimestamp这个时间戳,标明下线时间
                leaseToCancel.cancel();
                InstanceInfo instanceInfo = leaseToCancel.getHolder();
                String vip = null;
                String svip = null;
                // 获取VIP,SVIP,然后把instance的变化加入实例变化队列中
                if (instanceInfo != null) {
                    instanceInfo.setActionType(ActionType.DELETED);
                    recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
                    instanceInfo.setLastUpdatedTimestamp();
                    vip = instanceInfo.getVIPAddress();
                    svip = instanceInfo.getSecureVipAddress();
                }
                // 显示的清楚缓存 , guava的API
                invalidateCache(appName, vip, svip);
                logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
                return true;
            }
        } finally {
            read.unlock();
        }
    }
    

    综上可以看到,首先是从本地的CurrentHashMap中获取当前appName对应的的Map信息,最后通过机器ID,获取要下线的机器
    对应的Lease,修改Lease的evictionTimestamp , 也就是设置下线时间为当前时间点。

    public void cancel() {
        if (evictionTimestamp <= 0) {
            evictionTimestamp = System.currentTimeMillis();
        }
    }
    

    主动下线还是比较简单的

    相关文章

      网友评论

          本文标题:深入理解Eureka主动下线(四)

          本文链接:https://www.haomeiwen.com/subject/olstbftx.html