Netflix Eureka源码分析(16)——停止服务实例时的服务下线以及实例摘除机制源码剖析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/A_Story_Donkey/article/details/82971297

本来现在就是一个注册中心,还有很多个服务,在线上跑着,各个服务都会时不时来一个心跳,一切都很好,但是现在的话是这样子的。如果某个服务现在要停机,或者是重启,首先就会关闭,此时会发生什么样的事情呢?

eureka client关闭的话,服务停止,需要你自己去调用EurekaClient的shutdown(),将服务实例停止,所以说呢,我们重点就是从EurekaClient的shutdown()方法开始入手来看。

比如说你如果eureka client也是跟着一个web容器来启动的,ContextListener,里面有一个contextDestroyed(),在这个方法里,你就调用eureka client的shutdown()方法就可以了。

 

(1)DiscoveryClient中的shutdown()方法,需要你自己调用这个方法

@Singleton
public class DiscoveryClient implements EurekaClient {

    /**
     * Shuts down Eureka Client. Also sends a deregistration request to the
     * eureka server.
     */
    @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();

            // If APPINFO was registered
            if (applicationInfoManager != null && clientConfig.shouldRegisterWithEureka()) {
                applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
                unregister();
            }

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

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

            logger.info("Completed shut down of DiscoveryClient");
        }
    }

}

(2)DiscoveryClient中的unregister()方法中,取消注册,调用EurekaHttpClient的cancel()方法,http://localhost:8080/v2/apps/ServiceA/i-00000-1,delete请求

@Singleton
public class DiscoveryClient implements EurekaClient {

    /**
     * unregister w/ the eureka service.
     */
    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 + appPathIdentifier + " - deregister  status: " + httpResponse.getStatusCode());
            } catch (Exception e) {
                logger.error(PREFIX + appPathIdentifier + " - de-registration failed" + e.getMessage(), e);
            }
        }
    }

}
public abstract class AbstractJersey2EurekaHttpClient implements EurekaHttpClient {

    @Override
    public EurekaHttpResponse<Void> cancel(String appName, String id) {
        String urlPath = "apps/" + appName + '/' + id;
        Response response = null;
        try {
            Builder resourceBuilder = jerseyClient.target(serviceUrl).path(urlPath).request();
            addExtraProperties(resourceBuilder);
            addExtraHeaders(resourceBuilder);
            response = resourceBuilder.delete();
            return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey2 HTTP DELETE {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                response.close();
            }
        }
    }
}

(3)会在eureka core中的InstanceResource中,调用注册表的cancelLease()方法,调用父类的canel()方法,interlCancel()方法

@Produces({"application/xml", "application/json"})
public class 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();
        }
    }
}
public abstract class AbstractInstanceRegistry implements InstanceRegistry {

    @Override
    public boolean cancel(String appName, String id, boolean isReplication) {
        return internalCancel(appName, id, isReplication);
    }
}

(4)将服务实例从eureka server的map结构的注册表中移除掉

public abstract class AbstractInstanceRegistry implements InstanceRegistry {

    protected boolean internalCancel(String appName, String id, boolean isReplication) {
        try {
            read.lock();
            CANCEL.increment(isReplication);
            Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
            Lease<InstanceInfo> leaseToCancel = null;
            if (gMap != null) {
                leaseToCancel = gMap.remove(id);
            }
            synchronized (recentCanceledQueue) {
                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 {
                //最最核心的是调用了Lease的cancel()方法
                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);
                return true;
            }
        } finally {
            read.unlock();
        }
    }
}

(5)最最核心的是调用了Lease的cancel()方法,里面保存了一个evictionTimestamp,就是服务实例被清理掉,服务实例下线的时间戳

public class Lease<T> {

    /**
     * Cancels the lease by updating the eviction time.
     * 通过更新服务实例下线时间戳取消租约
     */
    public void cancel() {
        if (evictionTimestamp <= 0) {
            evictionTimestamp = System.currentTimeMillis();
        }
    }
}

(6)将服务实例放入最近变化的队列中去,让所有的eureka client下一次拉取增量注册表的时候,可以拉取到这个服务实例下线的这么一个变化

                if (instanceInfo != null) {
                    //设置针对此服务实例的行为是DELETED
                    instanceInfo.setActionType(ActionType.DELETED);
                    //将服务实例放入最近变化的队列中
                    recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
                    instanceInfo.setLastUpdatedTimestamp();
                    vip = instanceInfo.getVIPAddress();
                    svip = instanceInfo.getSecureVipAddress();
                }

(7)服务实例变更过了,必须将之前的缓存都清理掉,从readWriteCacheMap中清理掉

    invalidateCache(appName, vip, svip);

(8)然后定时过期的一个过程,就是有一个定时的任务,每隔30秒,将readWriteCacheMap和readOnlyCacheMap进行一个同步

(9)下次所有的eureka client来拉取增量注册表的时候,都会发现readOnlyCacheMap里没有,会找readWriteCacheMap也会发现没有,然后就会从注册表里抓取增量注册表,此时就会将上面那个recentCHangedQuuee中的记录返回

 

总结:

服务实例下线机制:

(1)在注册中心,先将服务实例从注册表map中移除,然后将下线的服务放入recentChangedQueue中去

(2)每个服务都会定时拉取增量注册表,此时可以从recentChangedQueue中感知到下线的服务实例,然后就可以在自己本地缓存中删除那个下线的服务实例

(3)服务下线的逻辑 流程图

猜你喜欢

转载自blog.csdn.net/A_Story_Donkey/article/details/82971297
今日推荐