Eureka-11丨服务实例下线

Posted by jiefang on November 29, 2020

服务实例下线

shutdown

DiscoveryClient中的shutdown()方法是服务实例关闭方法,会关闭instanceInfoReplicator、心跳线程池、缓存刷新线程池和定时任务线程池,会调用服务下线方法unregister(),关闭心跳监控和注册监控。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    @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");
        }
    }

unregister()

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

1
2
3
4
5
6
7
8
9
10
11
12
    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);
            }
        }
    }

server端处理

ApplicationsResource.getApplicationResource()->ApplicationResource.getInstanceInfo()->InstanceResource.cancelLease()->AbstractInstanceRegistry.cancel()->AbstractInstanceRegistry.internalCancel()registry移除注册表的实例信息。移除的实例放入recentChangedQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    protected boolean internalCancel(String appName, String id, boolean isReplication) {
        try {
            //读锁
            read.lock();
            CANCEL.increment(isReplication);
            //从注册表获取服务实例的map
            Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
            Lease<InstanceInfo> leaseToCancel = null;
            if (gMap != null) {
                //服务实例map移除此id的实例
                leaseToCancel = gMap.remove(id);
            }
            //移除的服务实例放入取消queue
            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 {
                //修改取消时间戳
                leaseToCancel.cancel();
                InstanceInfo instanceInfo = leaseToCancel.getHolder();
                String vip = null;
                String svip = null;
                if (instanceInfo != null) {
                    instanceInfo.setActionType(ActionType.DELETED);
                    //取消的实例放入最近变化queue,用于eureka client增量拉取
                    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();
        }
    }

总结

  1. 在注册中心,将服务实例从注册表中移除,下线的服务放入recentChangedQueue中;
  2. 每个eureka client务都会定时拉取增量注册表,此时可以从recentChangedQueue中感知到下线的服务实例,然后在自己本地缓存中删除那个下线的服务实例;