Eureka源码分析--服务下线(7)

152 阅读2分钟

Eureka源码分析--服务下线(7)

前言

有了client对server的主动心跳续约说明“我还活着”,那么当client主动下线的时候需不需要对server说一声呢?当然要了,毕竟client是一个good boy。

client端的流程

在DiscoveryClient类中有一个标志性单词,shutdown方法,但是这个方法需要自己手动掉才会运行

public synchronized void shutdown() {
        if (isShutdown.compareAndSet(false, true)) {
            logger.info("Shutting down DiscoveryClient ...");

            if (statusChangeListener != null && applicationInfoManager != null) {
                applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
            }

            //停止各种线程的调用
            cancelScheduledTasks();

            // If APPINFO was registered
            if (applicationInfoManager != null
                    && clientConfig.shouldRegisterWithEureka()
                    && clientConfig.shouldUnregisterOnShutdown()) {
                //设置实例状态
                applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
                //向server端发送通知
                unregister();
            }

            if (eurekaTransport != null) {
                eurekaTransport.shutdown();
            }

            heartbeatStalenessMonitor.shutdown();
            registryStalenessMonitor.shutdown();

            Monitors.unregisterObject(this);

            logger.info("Completed shut down of DiscoveryClient");
        }
    }
 void unregister() {
        // It can be null if shouldRegisterWithEureka == false
        if(eurekaTransport != null && eurekaTransport.registrationClient != null) {
            try {
                logger.info("Unregistering ...");
                EurekaHttpResponse<Void> httpResponse = eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
                logger.info(PREFIX + "{} - deregister  status: {}", appPathIdentifier, httpResponse.getStatusCode());
            } catch (Exception e) {
                logger.error(PREFIX + "{} - de-registration failed{}", appPathIdentifier, e.getMessage(), e);
            }
        }
    }

整个client端的停机并不复杂,就是先把启动时创建的各种线程都停掉,在向server发送一个下线通知,client端就结束了

server接收下线通知

client最后会请求到eureka-core下InstanceResource中的cancelLease()方法

该方法会根据服务名称和实例id进行一系列操作,最后会调用到AbstractInstanceRegistery.internalCancel()方法

这个方法在服务server进行服务剔除的时候已经分析过,其实就是一个方法被公用了

 protected boolean internalCancel(String appName, String id, boolean isReplication) {
        //加读锁
        read.lock();
        try {
            CANCEL.increment(isReplication);
            //获得要清除的服务实例
            Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
            Lease<InstanceInfo> leaseToCancel = null;
            if (gMap != null) {
                leaseToCancel = gMap.remove(id);
            }
            //添加进入清除队列
            recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
            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 {
                //设置失效时间
                leaseToCancel.cancel();
                InstanceInfo instanceInfo = leaseToCancel.getHolder();
                String vip = null;
                String svip = null;
                if (instanceInfo != null) {
                    //设置失效状态
                    instanceInfo.setActionType(ActionType.DELETED);
                    //加入最近修改队列
                    recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
                    //设置最后操作时间
                    instanceInfo.setLastUpdatedTimestamp();
                    vip = instanceInfo.getVIPAddress();
                    svip = instanceInfo.getSecureVipAddress();
                }
                invalidateCache(appName, vip, svip);
                logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
            }
        } finally {
            read.unlock();
        }

        synchronized (lock) {
            if (this.expectedNumberOfClientsSendingRenews > 0) {
                // Since the client wants to cancel it, reduce the number of clients to send renews.
                //更新每分钟期望收到心跳次数的阈值
                this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
                updateRenewsPerMinThreshold();
            }
        }

        return true;
    }

总结

服务下线在client做的事情相对较简单,而server对应的处理方法也和定时剔除失效实例的方法一致,这也就体现了代码的复用性,也更好的展示了一段好的逻辑应该是怎么样的。