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

程序入口

com.netflix.discovery.DiscoverClient

 1@PreDestroy
 2@Override
 3public synchronized void shutdown() {
 4    if (isShutdown.compareAndSet(falsetrue)) {
 5        logger.info("Shutting down DiscoveryClient ...");
 6
 7        if (statusChangeListener != null && applicationInfoManager != null) {
 8            applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
 9        }
10        // 取消定时任务(心跳,缓存刷新等)
11        cancelScheduledTasks();
12
13        // 如果app注册过,那么需要取消注册,也就说需要主动下线
14        if (applicationInfoManager != null && clientConfig.shouldRegisterWithEureka()) {
15            // 设置实例的状态为DOWN
16            applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
17            // 执行下线
18            unregister();
19        }
20
21        if (eurekaTransport != null) {
22            eurekaTransport.shutdown();
23        }
24
25        heartbeatStalenessMonitor.shutdown();
26        registryStalenessMonitor.shutdown();
27
28        logger.info("Completed shut down of DiscoveryClient");
29    }
30}

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

unregister()

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

Eureka-Server

InstanceResource

com.netflix.eureka.resources.InstanceResource

 1@DELETE
 2public Response cancelLease(
 3        @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
 4    //执行下线请求
 5    boolean isSuccess = registry.cancel(app.getName(), id,
 6            "true".equals(isReplication));
 7
 8    if (isSuccess) {
 9        logger.debug("Found (Cancel): " + app.getName() + " - " + id);
10        return Response.ok().build();
11    } else {
12        logger.info("Not Found (Cancel): " + app.getName() + " - " + id);
13        return Response.status(Status.NOT_FOUND).build();
14    }
15}

InstanceRegistry

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

 1@Override
 2public boolean cancel(String appName, String serverId, boolean isReplication) {
 3   //发布取消事件
 4   handleCancelation(appName, serverId, isReplication);
 5   // 调用父类的取消方法
 6   return super.cancel(appName, serverId, isReplication);
 7}
 8private void handleCancelation(String appName, String id, boolean isReplication) {
 9   log("cancel " + appName + ", serverId " + id + ", isReplication " + isReplication);
10   publishEvent(new EurekaInstanceCanceledEvent(this, appName, id, isReplication));
11}

父类PeerAwareInstanceRegistryImpl

 1@Override
 2public boolean cancel(final String appName, final String id,
 3                      final boolean isReplication) {
 4    // 执行父类的取消方法
 5    if (super.cancel(appName, id, isReplication)) {
 6        //集群同步信息
 7        replicateToPeers(Action.Cancel, appName, id, nullnull, isReplication);
 8        // Eureka-Server的保护机制
 9        synchronized (lock) {
10            if (this.expectedNumberOfRenewsPerMin > 0) {
11                // Since the client wants to cancel it, reduce the threshold (1 for 30 seconds, 2 for a minute)
12                this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
13                this.numberOfRenewsPerMinThreshold =
14                        (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
15            }
16        }
17        return true;
18    }
19    return false;
20}

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

 1@Override
 2public boolean cancel(String appName, String id, boolean isReplication) {
 3    return internalCancel(appName, id, isReplication);
 4}
 5
 6/**
 7 * {@link #cancel(String, String, boolean)} method is overridden by {@link PeerAwareInstanceRegistry}, so each
 8 * cancel request is replicated to the peers. This is however not desired for expires which would be counted
 9 * in the remote peers as valid cancellations, so self preservation mode would not kick-in.
10 */
11protected boolean internalCancel(String appName, String id, boolean isReplication) {
12    try {
13        // 读锁
14        read.lock();
15        // 添加取消次数给监控信息,这里是个枚举类,收集了取消次数
16        CANCEL.increment(isReplication);
17        // 从本地的CurrentHashMap中,获取当前实例对应的Lease信息
18        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
19        Lease<InstanceInfo> leaseToCancel = null;
20        if (gMap != null) {
21            // 移除信息 , 如果客户端是集群模式,此处仅移除这个实例ID对应的信息
22            leaseToCancel = gMap.remove(id);
23        }
24        // 添加取消信息到取消队列,主要用于运维界面的信息统计
25        synchronized (recentCanceledQueue) {
26            recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
27        }
28        //移除这个实例ID对应的instance状态
29        InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
30        if (instanceStatus != null) {
31            logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
32        }
33        if (leaseToCancel == null) {
34            // 如果信息不存在,则说明这个实例从来没有注册过来,或者已经下线了。
35            CANCEL_NOT_FOUND.increment(isReplication);
36            logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
37            return false;
38        } else {
39            // 更新Lease实例信息里面的evictionTimestamp这个时间戳,标明下线时间
40            leaseToCancel.cancel();
41            InstanceInfo instanceInfo = leaseToCancel.getHolder();
42            String vip = null;
43            String svip = null;
44            // 获取VIP,SVIP,然后把instance的变化加入实例变化队列中
45            if (instanceInfo != null) {
46                instanceInfo.setActionType(ActionType.DELETED);
47                recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
48                instanceInfo.setLastUpdatedTimestamp();
49                vip = instanceInfo.getVIPAddress();
50                svip = instanceInfo.getSecureVipAddress();
51            }
52            // 显示的清楚缓存 , guava的API
53            invalidateCache(appName, vip, svip);
54            logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
55            return true;
56        }
57    } finally {
58        read.unlock();
59    }
60}

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

1public void cancel() {
2    if (evictionTimestamp <= 0) {
3        evictionTimestamp = System.currentTimeMillis();
4    }
5}

主动下线还是比较简单的

 

sharedCode源码交流群,欢迎喜欢阅读源码的朋友加群,添加下面的微信, 备注”加群“ 。 

阅读更多

没有更多推荐了,返回首页