Spring Cloud之Eureka源碼分析2

本章主要介紹Eureka Client端源碼分析。
客戶端主要是向Server服務端發(fā)送Http請求,主要有注冊,心跳續(xù)約,獲取注冊信息等功能

實例配置

在分析源碼之前,需要查看下客戶端配置文件
application.yml

server:
  port: 8020
eureka:
  client:
    serviceUrl:
        defaultZone: http://localhost:8000/eureka/  #eureka服務端提供的注冊地址 參考服務端配置的這個路徑

  instance:
    instance-id: client-0 #此實例注冊到eureka服務端的唯一的實例ID
    prefer-ip-address: true #是否顯示IP地址
    leaseRenewalIntervalInSeconds: 10 #eureka客戶需要多長時間發(fā)送心跳給eureka服務器,表明它仍然活著,默認為30 秒 (與下面配置的單位都是秒)
    leaseExpirationDurationInSeconds: 30 #Eureka服務器在接收到實例的最后一次發(fā)出的心跳后,需要等待多久才可以將此實例刪除,默認為90秒

spring:
  application:
    name: service-client #此實例注冊到eureka服務端的name

訪問服務端localhost:8000的注冊信息


localhost.png

EurekaClientAutoConfiguration

自動配置類,首先要從依賴包的spring.factories文件看起

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration

其中最重要的是EurekaClientAutoConfiguration類


EurekaClientAutoConfiguration.png

只要類中存在EurekaClientConfig類所在的依賴包eureka-client-xx.jar就可以加載這個類


EurekaClientConfig.png

初始化EurekaClientAutoConfiguration類中的方法Bean加載到spring容器中:
1、EurekaAutoServiceRegistration 用來注冊

2、RefreshableEurekaClientConfiguration 用來開啟定時任務

注冊功能實現

1、EurekaAutoServiceRegistration
實例化EurekaAutoServiceRegistration對象,并放到spring容器中
org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration


EurekaAutoServiceRegistration.png

2、start
由于EurekaAutoServiceRegistration類實現了SmartLifecycle,SmartApplicationListener等接口,所以會在容器初始化完成之后調用EurekaAutoServiceRegistration#start方法。


start.png

調用EurekaServiceRegistry的注冊方法和發(fā)布InstanceRegisteredEvent事件
3、register
org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry#register
EurekaServiceRegistry.png

調用ApplicationInfoManager應用信息管理設置實例初始化狀態(tài)信息initialStatus
4、setInstanceStatus
com.netflix.appinfo.ApplicationInfoManager#setInstanceStatus
設置實例狀態(tài)信息并調用監(jiān)聽器notify方法


setInstanceStatus.png

5、notify
com.netflix.appinfo.ApplicationInfoManager.StatusChangeListener#notify
調用StatusChangeListener狀態(tài)改變監(jiān)聽器的notify方法。
這個對象實現在com.netflix.discovery.DiscoveryClient#initScheduledTasks方法內
image.png

6、onDemandUpdate
com.netflix.discovery.InstanceInfoReplicator#onDemandUpdate
image.png

7、run
由于InstanceInfoReplicator 類實現了Runnable接口,所以會調用這個run方法
image.png

8、register

com.netflix.discovery.DiscoveryClient#register
這里就到了注冊客戶端的地方


register.png

9、register
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#register
封裝http請求并調用到服務端方法
AbstractJerseyEurekaHttpClient.png

所以,封裝的url是:服務端的serviceUrl:defaultZone/apps/客戶端應用名
調到Eureka Server端
com.netflix.eureka.resources.ApplicationResource#addInstance
addInstance.png

定時刷新任務

包括緩存更新與心跳續(xù)約,通過RefreshableEurekaClientConfiguration類開始初始化,并最終通過initScheduledTasks方法開啟定時調度器任務

RefreshableEurekaClientConfiguration

EurekaClientAutoConfiguration.RefreshableEurekaClientConfiguration

@Configuration(proxyBeanMethods = false)
    @ConditionalOnRefreshScope
    protected static class RefreshableEurekaClientConfiguration {

        @Autowired
        private ApplicationContext context;

        @Autowired
        private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;

        @Bean(destroyMethod = "shutdown")
        @ConditionalOnMissingBean(value = EurekaClient.class,
                search = SearchStrategy.CURRENT)
        @org.springframework.cloud.context.config.annotation.RefreshScope
        @Lazy
        public EurekaClient eurekaClient(ApplicationInfoManager manager,
                EurekaClientConfig config, EurekaInstanceConfig instance,
                @Autowired(required = false) HealthCheckHandler healthCheckHandler) {
            // If we use the proxy of the ApplicationInfoManager we could run into a
            // problem
            // when shutdown is called on the CloudEurekaClient where the
            // ApplicationInfoManager bean is
            // requested but wont be allowed because we are shutting down. To avoid this
            // we use the
            // object directly.
            ApplicationInfoManager appManager;
            if (AopUtils.isAopProxy(manager)) {
                appManager = ProxyUtils.getTargetObject(manager);
            }
            else {
                appManager = manager;
            }
            CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager,
                    config, this.optionalArgs, this.context);
            cloudEurekaClient.registerHealthCheck(healthCheckHandler);
            return cloudEurekaClient;
        }
}

這個類是程序實現定時刷新任務開始的地方,主要是通過new CloudEurekaClient()方法創(chuàng)建Cloud客戶端類(CloudEurekaClient)

CloudEurekaClient

下面主要查看CloudEurekaClient的調用鏈
1、CloudEurekaClient#CloudEurekaClient
org.springframework.cloud.netflix.eureka.CloudEurekaClient#CloudEurekaClient
調用CloudEurekaClient類的父類,和對applicationInfoManager、publisher、eurekaTransportField等屬性值賦值


CloudEurekaClient.png

2、DiscoveryClient#DiscoveryClient
com.netflix.discovery.DiscoveryClient#DiscoveryClient


DiscoveryClient.png

3、DiscoveryClient#DiscoveryClient
com.netflix.discovery.DiscoveryClient#DiscoveryClient
構造器類調用,傳入獲取備用服務器backupRegistryInstance實例的get方法。不在這里調用
public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, EndpointRandomizer randomizer) {
        this(applicationInfoManager, config, args, new Provider<BackupRegistry>() {
            private volatile BackupRegistry backupRegistryInstance;

            @Override
            public synchronized BackupRegistry get() {
                if (backupRegistryInstance == null) {
                    String backupRegistryClassName = config.getBackupRegistryImpl();
                    if (null != backupRegistryClassName) {
                        try {
                            backupRegistryInstance = (BackupRegistry) Class.forName(backupRegistryClassName).newInstance();
                            logger.info("Enabled backup registry of type {}", backupRegistryInstance.getClass());
                        } catch (InstantiationException e) {
                            logger.error("Error instantiating BackupRegistry.", e);
                        } catch (IllegalAccessException e) {
                            logger.error("Error instantiating BackupRegistry.", e);
                        } catch (ClassNotFoundException e) {
                            logger.error("Error instantiating BackupRegistry.", e);
                        }
                    }

                    if (backupRegistryInstance == null) {
                        logger.warn("Using default backup registry implementation which does not do anything.");
                        backupRegistryInstance = new NotImplementedRegistryImpl();
                    }
                }

                return backupRegistryInstance;
            }
        }, randomizer);
    }

4、DiscoveryClient#DiscoveryClient
定義調度器類、心跳執(zhí)行器和緩存刷新執(zhí)行器等的定義。
com.netflix.discovery.DiscoveryClient#DiscoveryClient

DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
        //  ........前半部分省略
        try {
            // default size of 2 - 1 each for heartbeat and cacheRefresh
            scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());

            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff

            cacheRefreshExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff

            eurekaTransport = new EurekaTransport();
            scheduleServerEndpointTask(eurekaTransport, args);

            AzToRegionMapper azToRegionMapper;
            if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
                azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);
            } else {
                azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);
            }
            if (null != remoteRegionsToFetch.get()) {
                azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));
            }
            instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
        } catch (Throwable e) {
            throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
        }

        if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
            fetchRegistryFromBackup();
        }

        // call and execute the pre registration handler before all background tasks (inc registration) is started
        if (this.preRegistrationHandler != null) {
            this.preRegistrationHandler.beforeRegistration();
        }

        if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
            try {
                if (!register() ) {
                    throw new IllegalStateException("Registration error at startup. Invalid server response.");
                }
            } catch (Throwable th) {
                logger.error("Registration error at startup: {}", th.getMessage());
                throw new IllegalStateException(th);
            }
        }

        // finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
        initScheduledTasks();

        try {
            Monitors.registerObject(this);
        } catch (Throwable e) {
            logger.warn("Cannot register timers", e);
        }

        // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
        // to work with DI'd DiscoveryClient
        DiscoveryManager.getInstance().setDiscoveryClient(this);
        DiscoveryManager.getInstance().setEurekaClientConfig(config);

        initTimestampMs = System.currentTimeMillis();
        logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                initTimestampMs, this.getApplications().size());
    }

這個方法主要流程:
①、這個方法前半部分是初始化屬性值。
②、根據客戶端client配置文件,config.shouldFetchRegistry()是否獲取注冊表信息和
config.shouldRegisterWithEureka()是否注冊到eureka上來對屬性賦值,或直接返回
③、初始化調度器scheduler、兩個線程池執(zhí)行器heartbeatExecutor(心跳續(xù)約)和cacheRefreshExecutor(緩存刷新,定時獲取注冊信息表)
④、在獲取服務注冊信息條件下,沒有獲取到信息或異常即fetchRegistry(false)返回false。可以從備用服務器獲取調用fetchRegistryFromBackup()方法,內部實現方法調用備用服務器類的get方法backupRegistryProvider.get()
⑤、初始化調度器任務方法initScheduledTasks()

初始化調度器任務initScheduledTasks

調度器任務包括:
1、定時刷新緩存注冊表信息,分為全量獲取和增量獲取
2、定時向服務端發(fā)送心跳續(xù)約
3、狀態(tài)改變監(jiān)聽器執(zhí)行
這里不僅包括這些定時任務,注冊也是在這里調用狀態(tài)改變監(jiān)聽器StatusChangeListener的notify方法
com.netflix.discovery.DiscoveryClient#initScheduledTasks

 /**
     * Initializes all scheduled tasks.
     */
    private void initScheduledTasks() {
        if (clientConfig.shouldFetchRegistry()) {
            // registry cache refresh timer
            int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
            int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "cacheRefresh",
                            scheduler,
                            cacheRefreshExecutor,
                            registryFetchIntervalSeconds,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new CacheRefreshThread()
                    ),
                    registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }

        if (clientConfig.shouldRegisterWithEureka()) {
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

            // Heartbeat timer
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);

            // InstanceInfo replicator
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize

            statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
                @Override
                public String getId() {
                    return "statusChangeListener";
                }

                @Override
                public void notify(StatusChangeEvent statusChangeEvent) {
                    if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
                            InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
                        // log at warn level if DOWN was involved
                        logger.warn("Saw local status change event {}", statusChangeEvent);
                    } else {
                        logger.info("Saw local status change event {}", statusChangeEvent);
                    }
                    instanceInfoReplicator.onDemandUpdate();
                }
            };

            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }

            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }
    }

1、心跳續(xù)約

1、initScheduledTasks
com.netflix.discovery.DiscoveryClient#initScheduledTasks
TimedSupervisorTask繼承了TimerTask,TimerTask實現了Runnable
TimedSupervisorTask類的構造方法

public TimedSupervisorTask(String name, ScheduledExecutorService scheduler, ThreadPoolExecutor executor,
                               int timeout, TimeUnit timeUnit, int expBackOffBound, Runnable task) {
        this.scheduler = scheduler;
        this.executor = executor;
        this.timeoutMillis = timeUnit.toMillis(timeout);
        this.task = task;
        this.delay = new AtomicLong(timeoutMillis);
        this.maxDelay = timeoutMillis * expBackOffBound;
        Monitors.registerObject(name, this);
    }
heartbeatExecutor.png

2、HeartbeatThread
執(zhí)行TimedSupervisorTask的task任務,在給定的間隔內執(zhí)行心跳續(xù)約任務
com.netflix.discovery.DiscoveryClient.HeartbeatThread


HeartbeatThread.png

3、renew
續(xù)約任務,續(xù)約成功更新lastSuccessfulHeartbeatTimestamp參數。通過REST方式進行續(xù)訂
com.netflix.discovery.DiscoveryClient#renew


renew.png

4、sendHeartBeat
拼接http請求,發(fā)送心跳
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#sendHeartBeat
sendHeartBeat.png

客戶端appName=SERVICE-CLIENT,id是instance-id屬性值client-0

服務端調用到renewLease方法續(xù)約,appName和id與客戶端傳過來的相同
com.netflix.eureka.resources.InstanceResource#renewLease


image.png

全量拉取和增量拉取

在定時刷新緩存實現獲取注冊信息,分為全量拉取和增量拉取
創(chuàng)建TimedSupervisorTask調度任務類,傳入cacheRefreshExecutor執(zhí)行器、CacheRefreshThread任務類、從服務端獲取注冊信息的時間間隔RegistryFetchIntervalSeconds等參數信息

image.png

1、run
定時執(zhí)行CacheRefreshThread類的run方法
CacheRefreshThread.png

2、refreshRegistry
首先對remoteRegionsModified參數進行判斷,這樣可以確保對遠程區(qū)域進行動態(tài)更改時可以獲取數據。如果更改則remoteRegionsModified=true,只進行全量拉取
com.netflix.discovery.DiscoveryClient#refreshRegistry
refreshRegistry.png

3、fetchRegistry
com.netflix.discovery.DiscoveryClient#fetchRegistry
這個方法是決定了使用那種方式拉取

private boolean fetchRegistry(boolean forceFullRegistryFetch) {
        Stopwatch tracer = FETCH_REGISTRY_TIMER.start();

        try {
            // If the delta is disabled or if it is the first time, get all
            // applications
            Applications applications = getApplications();

            if (clientConfig.shouldDisableDelta()
                    || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
                    || forceFullRegistryFetch
                    || (applications == null)
                    || (applications.getRegisteredApplications().size() == 0)
                    || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
            {
                logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta());
                logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress());
                logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
                logger.info("Application is null : {}", (applications == null));
                logger.info("Registered Applications size is zero : {}",
                        (applications.getRegisteredApplications().size() == 0));
                logger.info("Application version is -1: {}", (applications.getVersion() == -1));
                getAndStoreFullRegistry();
            } else {
                getAndUpdateDelta(applications);
            }
            applications.setAppsHashCode(applications.getReconcileHashCode());
            logTotalInstances();
        } catch (Throwable e) {
            logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, e.getMessage(), e);
            return false;
        } finally {
            if (tracer != null) {
                tracer.stop();
            }
        }

        // Notify about cache refresh before updating the instance remote status
        onCacheRefreshed();

        // Update remote status based on refreshed data held in the cache
        updateInstanceRemoteStatus();

        // registry was fetched successfully, so return true
        return true;
    }

全量拉取條件(任意一個)
①、disable-delta屬性值是true 關閉增量拉取
②、registry-refresh-single-vip-address 屬性vip地址的值不為空
③、forceFullRegistryFetch 為true 傳過來的變量值
④、localRegionApps的applications是null 當前區(qū)域應用
⑤、applications的數量是0
⑥、applications的版本是-1

增量拉取

實現增量拉取的條件是不符合全量拉取,調用getAndUpdateDelta方法
com.netflix.discovery.DiscoveryClient#getAndUpdateDelta

private void getAndUpdateDelta(Applications applications) throws Throwable {
        long currentUpdateGeneration = fetchRegistryGeneration.get();

        Applications delta = null;
        EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
        if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
            delta = httpResponse.getEntity();
        }

        if (delta == null) {
            logger.warn("The server does not allow the delta revision to be applied because it is not safe. "
                    + "Hence got the full registry.");
            getAndStoreFullRegistry();
        } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
            logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
            String reconcileHashCode = "";
            if (fetchRegistryUpdateLock.tryLock()) {
                try {
                    updateDelta(delta);
                    reconcileHashCode = getReconcileHashCode(applications);
                } finally {
                    fetchRegistryUpdateLock.unlock();
                }
            } else {
                logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
            }
            // There is a diff in number of instances for some reason
            if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) {
                reconcileAndLogDifference(delta, reconcileHashCode);  // this makes a remoteCall
            }
        } else {
            logger.warn("Not updating application delta as another thread is updating it already");
            logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode());
        }
    }

這個方法實現了增量拉取的請求實現,及對拉取增量結果的處理
1、getDelta
eurekaTransport.queryClient.getDelta(remoteRegionsRef.get())的具體實現是通過AbstractJerseyEurekaHttpClient類實現的
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#getDelta

getDelta.png

拼接apps/delta開頭的請求
2、getApplicationsInternal
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#getApplicationsInternal
image.png

拼接的http請求是:http://localhost:8000/eureka/apps/delta然后調用到Eureka Server服務端
3、getContainerDifferential
服務端通過getContainerDifferential接收
com.netflix.eureka.resources.ApplicationsResource#getContainerDifferential
getContainerDifferential1.png

getContainerDifferential2.png

無論是調用到responseCache.getGZIP(cacheKey)方法,還是responseCache.get(cacheKey)方法。最終都是調用到Eureka Server的查詢的三種緩存,最終會經過讀寫緩存readWriteCacheMap處理。
4、readWriteCacheMap
調用到ResponseCacheImpl構造方法中readWriteCacheMap讀寫緩存的創(chuàng)建中,如果緩存中沒有會調用generatePayload方法
com.netflix.eureka.registry.ResponseCacheImpl#readWriteCacheMap
image.png

5、generatePayload
com.netflix.eureka.registry.ResponseCacheImpl#generatePayload
根據傳入的Key參數處理不同的情況
generatePayload.png

key = { eurekaAccept = "full",entityType = "Application",hashKey = "ApplicationALL_APPS_DELTAJSONV2full", requestVersion = "V2", requestType = "JSON", regions = null, entityName = "ALL_APPS_DELTA"}
entityType 可分為Application、VIP、SVIP、default等情況
在entityType 是Application情況下EntityName分為:"ALL_APPS"(全量拉取)和"ALL_APPS_DELTA"(增量拉取)
在全量或增量拉取下有isRemoteRegionRequested參數判斷是否具有遠程區(qū)域請求
本次處理是:增量拉取的沒有遠程區(qū)域請求,并調用getPayLoad方法加載是否含有增量數據
6、getApplicationDeltas
獲取增量實例
com.netflix.eureka.registry.AbstractInstanceRegistry#getApplicationDeltas

public Applications getApplicationDeltas() {
        GET_ALL_CACHE_MISS_DELTA.increment();
        Applications apps = new Applications();
        apps.setVersion(responseCache.getVersionDelta().get());
        Map<String, Application> applicationInstancesMap = new HashMap<String, Application>();
        try {
            write.lock();
            Iterator<RecentlyChangedItem> iter = this.recentlyChangedQueue.iterator();
            logger.debug("The number of elements in the delta queue is : {}",
                    this.recentlyChangedQueue.size());
            while (iter.hasNext()) {
                Lease<InstanceInfo> lease = iter.next().getLeaseInfo();
                InstanceInfo instanceInfo = lease.getHolder();
                logger.debug(
                        "The instance id {} is found with status {} and actiontype {}",
                        instanceInfo.getId(), instanceInfo.getStatus().name(), instanceInfo.getActionType().name());
                Application app = applicationInstancesMap.get(instanceInfo
                        .getAppName());
                if (app == null) {
                    app = new Application(instanceInfo.getAppName());
                    applicationInstancesMap.put(instanceInfo.getAppName(), app);
                    apps.addApplication(app);
                }
                app.addInstance(new InstanceInfo(decorateInstanceInfo(lease)));
            }

            boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();

            if (!disableTransparentFallback) {
                Applications allAppsInLocalRegion = getApplications(false);

                for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) {
                    Applications applications = remoteRegistry.getApplicationDeltas();
                    for (Application application : applications.getRegisteredApplications()) {
                        Application appInLocalRegistry =
                                allAppsInLocalRegion.getRegisteredApplications(application.getName());
                        if (appInLocalRegistry == null) {
                            apps.addApplication(application);
                        }
                    }
                }
            }

            Applications allApps = getApplications(!disableTransparentFallback);
            apps.setAppsHashCode(allApps.getReconcileHashCode());
            return apps;
        } finally {
            write.unlock();
        }
    }

這個方法主要是遍歷recentlyChangedQueue存在的數據放入到Applications對象中。所以recentlyChangedQueue隊列中存在什么數據就很重要,因此我們需要了解最新更新隊列recentlyChangedQueue是如何放入的及放入那些數據,及其的移除的原理。
在這個方法最后 apps.setAppsHashCode設置了當前服務端所有注冊信息的HashCode,所以這個增量對象存儲了最新的狀態(tài)HashCode值。
7、客戶端獲取增量數據的處理
還是在getAndUpdateDelta方法內,對服務端傳輸過來數據,獲取當前服務端的增量數據部分
com.netflix.discovery.DiscoveryClient#getAndUpdateDelta


getAndUpdateDelta.png

這個方法的主要過程是:
如果增量數據部分為空,則執(zhí)行全量拉取。
對當前服務的注冊信息表執(zhí)行updateDelta(delta)方法,對當前注冊實例的增加刪除或修改操作
當前更新后的服務注冊表的HashCode值與增量對象存儲的最新的狀態(tài)HashCode值比較,如果不相等 則執(zhí)行全量拉取

recentlyChangedQueue

最新更新隊列ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue
com.netflix.eureka.registry.AbstractInstanceRegistry#AbstractInstanceRegistry類在構建創(chuàng)建注冊表時創(chuàng)建了recentlyChangedQueue隊列,并創(chuàng)建了一個增量調度任務方法getDeltaRetentionTask方法


AbstractInstanceRegistry.png

com.netflix.eureka.registry.AbstractInstanceRegistry#getDeltaRetentionTask
對recentlyChangedQueue隊列中對最近改變的隊列在一定時間范圍retentionTimeInMSInDeltaQueue=180000ms(3分鐘)外的進行定時清除(30s清除一次)


getDeltaRetentionTask.png

recentlyChangedQueue隊列添加條件:
1、注冊時register
2、下線時Cancel

3、statusUpdate
4、deleteStatusOverride
getDeltaRetentionTask進行定時清除

全量拉取

全量拉取與增量拉取過程類似
全量拉取調用getAndStoreFullRegistry方法
1、getAndStoreFullRegistry
com.netflix.discovery.DiscoveryClient#getAndStoreFullRegistry


getAndStoreFullRegistry.png

2、getApplications
com.netflix.discovery.shared.transport.EurekaHttpClient#getApplications


EurekaHttpClient.png

3、getApplications
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#getApplications
getApplications.png

拼接的Http請求是:apps/
4、getApplicationsInternal

com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#getApplicationsInternal


getApplicationsInternal.png

5、getContainers
調用到Eureka Server服務端的getContainers方法,這里調用與增量拉取類似。查詢經過三層緩存結構,具體過程可參考增量查詢與三層緩存結構。
com.netflix.eureka.resources.ApplicationsResource#getContainers
getContainers.png

如果acceptEncoding包含值gzip將會調用ResponseCacheImpl#getGZIP方法獲取
ResponseCacheImpl.png

否則調用ResponseCacheImpl#get(Key)方法
最終會調用到讀寫緩存的generatePayload方法處理
6、generatePayload
com.netflix.eureka.registry.ResponseCacheImpl#generatePayload
Key結構中的EntityName是"ALL_APPS"全量查詢。
generatePayload.png

7、getApplications
獲取注冊表所有的注冊信息

com.netflix.eureka.registry.AbstractInstanceRegistry#getApplications()


getApplications.png

8、結果處理
放入到當前對象localRegionApps緩存中
image.png

如果含有獲取遠程區(qū)域注冊信息FetchingRemoteRegionRegistries,只需要分到不同的索引位置
image.png

總結:

Eureka Client客戶端與Eureka Server服務端是關聯(lián)的,不能分開處理。關鍵點有以下幾點:
客戶端主要了解的是如何向服務端發(fā)起請求及服務端接收接口的對應
客戶端的定時任務有哪些:心跳及緩存獲取
客戶端注冊的實現,緩存獲取原理
緩存獲取中的增量拉取與全量拉取的區(qū)別
增量拉取在服務端的實現原理,由一個狀態(tài)更改隊列實現的

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374