SpringCloud輪詢拉取注冊表與服務(wù)發(fā)現(xiàn)流程詳解
一、前言
上一篇我們討論了關(guān)于周期性任務(wù)的一些應(yīng)用等,本篇文章我們來探究一下這些內(nèi)容:周期性刷新注冊表?全量拉取注冊表還是增量拉取注冊表、更新本地緩存?服務(wù)發(fā)現(xiàn)的入口、獲取本地服務(wù)列表?
二、輪詢拉取注冊表
1、構(gòu)造初始化
同樣是在Spring容器初始化的過程中初始化的,基于SpringBoot自動裝配集成。上一節(jié)也講了一部分,這里補充:
@Singleton public class DiscoveryClient implements EurekaClient { ....省略n行代碼...... private final ScheduledExecutorService scheduler; // additional executors for supervised subtasks監(jiān)督子任務(wù)的附加執(zhí)行器 private final ThreadPoolExecutor heartbeatExecutor; private final ThreadPoolExecutor cacheRefreshExecutor; private TimedSupervisorTask cacheRefreshTask; private TimedSupervisorTask heartbeatTask; ....省略n行代碼...... // Spring容器初始化時候調(diào)用 public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) { // 調(diào)用下面重載方法 this(applicationInfoManager, config, args, ResolverUtils::randomize); } public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, EndpointRandomizer randomizer) { this(applicationInfoManager, config, args, new Provider<BackupRegistry>() { ....省略n行代碼...... } @Inject DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) { try { // default size of 2 - 1 each for heartbeat and cacheRefresh心跳和緩存刷新的默認(rèn)大小分別為2-1 scheduler = Executors.newScheduledThreadPool(2, new ThreadFactoryBuilder() .setNameFormat("DiscoveryClient-%d") .setDaemon(true) .build()); // 心跳執(zhí)行者 heartbeatExecutor = new ThreadPoolExecutor( 1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactoryBuilder() .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d") .setDaemon(true) .build() ); // use direct handoff // 緩存刷新執(zhí)行者 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(); ....省略n行代碼...... } catch (Throwable e) { throw new RuntimeException("Failed to initialize DiscoveryClient!", e); } // 默認(rèn)true,可更改配置不建議 if (clientConfig.shouldFetchRegistry()) { try {// 初始化注冊表 boolean primaryFetchRegistryResult = fetchRegistry(false); // 下面主要打印失敗日志,初始化時控制臺可見是處理成功的 if (!primaryFetchRegistryResult) { // 從主服務(wù)器初始注冊表提取失敗 logger.info("Initial registry fetch from primary servers failed"); } boolean backupFetchRegistryResult = true; if (!primaryFetchRegistryResult && !fetchRegistryFromBackup()) { // 如果所有的eureka服務(wù)器網(wǎng)址都無法訪問,從備份注冊表中獲取注冊表信息也失敗。 backupFetchRegistryResult = false; // 從備份服務(wù)器初始注冊表提取失敗 logger.info("Initial registry fetch from backup servers failed"); } if (!primaryFetchRegistryResult && !backupFetchRegistryResult && clientConfig.shouldEnforceFetchRegistryAtInit()) { // 在啟動時獲取注冊表錯誤。初始獲取失敗。 throw new IllegalStateException("Fetch registry error at startup. Initial fetch failed."); } } catch (Throwable th) { logger.error("Fetch registry error at startup: {}", th.getMessage()); throw new IllegalStateException(th); } } ....省略n行代碼...... // 最后,初始化調(diào)度任務(wù)(例如,集群解析器、 heartbeat、 instanceInfo replicator、 fetch initScheduledTasks(); ....省略n行代碼...... }
主要邏輯:
- 初始化緩存刷新執(zhí)行器,用于周期性執(zhí)行任務(wù),下面繼續(xù)分析
- 默認(rèn)需要刷新注冊表,默認(rèn)不使用全量拉取,但是初始化時使用下面2分析:會調(diào)用注冊中心完成注冊表初始化,返回是否刷新成功;如果從主服務(wù)器初始注冊表提取失敗打印日志;如果所有的eureka服務(wù)器網(wǎng)址都無法訪問,從備份注冊表中獲取注冊表信息也失敗打印日志;在啟動時獲取注冊表錯誤,拋異常。
- 可見在2中沒有特殊原因的話,一般是使用全量拉取注冊表初始化成功了,否則的話拋異常
緩存刷新
大部分邏輯在前面的章節(jié)已經(jīng)分析,TimedSupervisorTask跟發(fā)送心跳服務(wù)續(xù)約邏輯是一樣的,這里補充刷新本地服務(wù)列表任務(wù)。
private void initScheduledTasks() { // 默認(rèn)true,可更改配置不建議 if (clientConfig.shouldFetchRegistry()) { // registry cache refresh timer注冊表緩存刷新計時器 // 默認(rèn)30 int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds(); int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); cacheRefreshTask = new TimedSupervisorTask( "cacheRefresh", scheduler, cacheRefreshExecutor, registryFetchIntervalSeconds, TimeUnit.SECONDS, expBackOffBound, new CacheRefreshThread() ); scheduler.schedule( cacheRefreshTask, registryFetchIntervalSeconds, TimeUnit.SECONDS); } if (clientConfig.shouldRegisterWithEureka()) { /* LeaseInfo: public static final int DEFAULT_LEASE_RENEWAL_INTERVAL = 30; // Client settings private int renewalIntervalInSecs = DEFAULT_LEASE_RENEWAL_INTERVAL; */ // 默認(rèn)30 int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs(); int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound(); logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs); // Heartbeat timer心跳任務(wù) heartbeatTask = new TimedSupervisorTask( "heartbeat", scheduler, heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread() ); // 默認(rèn)的情況下會每隔30秒向注冊中心 (eureka.instance.lease-renewal-interval-in-seconds)發(fā)送一次心跳來進(jìn)行服務(wù)續(xù)約 scheduler.schedule( heartbeatTask, renewalIntervalInSecs, TimeUnit.SECONDS); // InstanceInfo replicator實例信息復(fù)制任務(wù) instanceInfoReplicator = new InstanceInfoReplicator( this, instanceInfo, clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2); // burstSize // 狀態(tài)變更監(jiān)聽者 statusChangeListener = new ApplicationInfoManager.StatusChangeListener() { @Override public String getId() { return "statusChangeListener"; } @Override public void notify(StatusChangeEvent statusChangeEvent) { // Saw local status change event StatusChangeEvent [timestamp=1668595102513, current=UP, previous=STARTING] logger.info("Saw local status change event {}", statusChangeEvent); instanceInfoReplicator.onDemandUpdate(); } }; // 初始化狀態(tài)變更監(jiān)聽者 if (clientConfig.shouldOnDemandUpdateStatusChange()) { applicationInfoManager.registerStatusChangeListener(statusChangeListener); } // 3.2 定時刷新服務(wù)實例信息和檢查應(yīng)用狀態(tài)的變化,在服務(wù)實例信息發(fā)生改變的情況下向server重新發(fā)起注冊 instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds()); } else { logger.info("Not registering with Eureka server per configuration"); } }
主要邏輯:
默認(rèn)需要刷新注冊表,要達(dá)到服務(wù)高可用。
1)clientConfig.getRegistryFetchIntervalSeconds()獲取注冊表刷新時間,默認(rèn)30秒,可在配置文件更改;
2)初始化cacheRefreshTask為TimedSupervisorTask類型,跟心跳任務(wù)一樣處理邏輯,我們這節(jié)就只分析CacheRefreshThread刷新緩存邏輯,見3
2、刷新注冊表
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如果 delta 被禁用或者是第一次,那么獲取所有的應(yīng)用程序 Applications applications = getApplications(); // shouldDisableDelta默認(rèn)false 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 false: {}", clientConfig.shouldDisableDelta()); logger.info("Single vip registry refresh property null: {}", clientConfig.getRegistryRefreshSingleVipAddress()); logger.info("Force full registry fetch false: {}", forceFullRegistryFetch); logger.info("Application is null false: {}", (applications == null)); logger.info("Registered Applications size is zero true: {}", (applications.getRegisteredApplications().size() == 0)); logger.info("Application version is -1true: {}", (applications.getVersion() == -1)); // 全量拉取 getAndStoreFullRegistry(); } else { // 增量拉取 getAndUpdateDelta(applications); } applications.setAppsHashCode(applications.getReconcileHashCode()); logTotalInstances(); } catch (Throwable e) { logger.info(PREFIX + "{} - was unable to refresh its cache! This periodic background refresh will be retried in {} seconds. status = {} stacktrace = {}", appPathIdentifier, clientConfig.getRegistryFetchIntervalSeconds(), e.getMessage(), ExceptionUtils.getStackTrace(e)); return false; } finally { if (tracer != null) { tracer.stop(); } } // Notify about cache refresh before updating the instance remote status // 在更新實例遠(yuǎn)程狀態(tài)之前通知緩存刷新 onCacheRefreshed(); // Update remote status based on refreshed data held in the cache // 根據(jù)緩存中保存的刷新數(shù)據(jù)更新遠(yuǎn)程狀態(tài) updateInstanceRemoteStatus(); // registry was fetched successfully, so return true return true; }
主要邏輯:
- 由上面的1.1可見狀態(tài)變更監(jiān)聽者還沒有初始化,從前面的文章也知道它的作用完成服務(wù)注冊,故這里從本地獲取應(yīng)用就為空。所以先打印下日志,調(diào)用全量拉取注冊表方法,下面分析。軌跡跟蹤對象非空,關(guān)閉。
- 在更新實例遠(yuǎn)程狀態(tài)之前通知緩存刷新
- 根據(jù)緩存中保存的刷新數(shù)據(jù)更新遠(yuǎn)程狀態(tài)
全量拉取注冊表
private void getAndStoreFullRegistry() throws Throwable { long currentUpdateGeneration = fetchRegistryGeneration.get(); // 從 eureka 服務(wù)器上獲取所有實例注冊信息 logger.info("Getting all instance registry info from the eureka server"); Applications apps = null; // RegistryRefreshSingleVipAddress默認(rèn)空 EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get()) : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get()); // 響應(yīng)成功獲取應(yīng)用 if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) { apps = httpResponse.getEntity(); } // 200 logger.info("The response status is {}", httpResponse.getStatusCode()); if (apps == null) { logger.error("The application is null for some reason. Not storing this information"); } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) { // 緩存到本地 localRegionApps.set(this.filterAndShuffle(apps)); // 如:UP_1_ logger.debug("Got full registry with apps hashcode {}", apps.getAppsHashCode()); } else { logger.warn("Not updating applications as another thread is updating it already"); } }
主要邏輯:
- RegistryRefreshSingleVipAddress默認(rèn)空,故調(diào)用eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())請求注冊中心
- 響應(yīng)成功獲取應(yīng)用
- 如果應(yīng)用apps空則打印下日志;一般CAS成功,在篩選僅具有 UP 狀態(tài)的實例的應(yīng)用程序并對它們進(jìn)行洗牌之后獲取應(yīng)用程序,緩存到本地localRegionApps(AtomicReference<Applications>類型);否則打印下日志
3、緩存刷新任務(wù)
class CacheRefreshThread implements Runnable { @Override public void run() { refreshRegistry(); } } @VisibleForTesting void refreshRegistry() { try { boolean isFetchingRemoteRegionRegistries = isFetchingRemoteRegionRegistries(); boolean remoteRegionsModified = false; // This makes sure that a dynamic change to remote regions to fetch is honored. // 這可以確保對要獲取的遠(yuǎn)程區(qū)域的動態(tài)更改得到遵守。 // 默認(rèn)null String latestRemoteRegions = clientConfig.fetchRegistryForRemoteRegions(); if (null != latestRemoteRegions) { String currentRemoteRegions = remoteRegionsToFetch.get(); if (!latestRemoteRegions.equals(currentRemoteRegions)) { // Both remoteRegionsToFetch and AzToRegionMapper.regionsToFetch need to be in sync // RemoteRegionsToFetch 和 AzToRegionMapper.regionsToFetch 都需要同步 synchronized (instanceRegionChecker.getAzToRegionMapper()) { // CAS if (remoteRegionsToFetch.compareAndSet(currentRemoteRegions, latestRemoteRegions)) { String[] remoteRegions = latestRemoteRegions.split(","); remoteRegionsRef.set(remoteRegions); instanceRegionChecker.getAzToRegionMapper().setRegionsToFetch(remoteRegions); remoteRegionsModified = true; } else { // 并發(fā)獲取修改的遠(yuǎn)程區(qū)域,忽略從{}到{}的更改 logger.info("Remote regions to fetch modified concurrently," + " ignoring change from {} to {}", currentRemoteRegions, latestRemoteRegions); } } } else { // Just refresh mapping to reflect any DNS/Property change // 只需刷新映射以反映任何 DNS/屬性更改 instanceRegionChecker.getAzToRegionMapper().refreshMapping(); } } // 刷新注冊表 boolean success = fetchRegistry(remoteRegionsModified); if (success) { registrySize = localRegionApps.get().size(); lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis(); } if (logger.isDebugEnabled()) { StringBuilder allAppsHashCodes = new StringBuilder(); allAppsHashCodes.append("Local region apps hashcode: "); allAppsHashCodes.append(localRegionApps.get().getAppsHashCode()); allAppsHashCodes.append(", is fetching remote regions? "); allAppsHashCodes.append(isFetchingRemoteRegionRegistries); for (Map.Entry<String, Applications> entry : remoteRegionVsApps.entrySet()) { allAppsHashCodes.append(", Remote region: "); allAppsHashCodes.append(entry.getKey()); allAppsHashCodes.append(" , apps hashcode: "); allAppsHashCodes.append(entry.getValue().getAppsHashCode()); } logger.debug("Completed cache refresh task for discovery. All Apps hash code is {} ", allAppsHashCodes); } } catch (Throwable e) { logger.error("Cannot fetch registry from server", e); } }
CacheRefreshThread 實現(xiàn)了Runnable接口,但是run()中任務(wù)邏輯封裝了出去,在refreshRegistry()中處理。在2中分析了初始化時已經(jīng)使用全量拉取注冊表并緩存應(yīng)用到本地localRegionApps,那么這里使用延遲任務(wù)處理的話就會執(zhí)行增量拉取邏輯了,在下面4分析
4、增量拉取注冊表
private void getAndUpdateDelta(Applications applications) throws Throwable { long currentUpdateGeneration = fetchRegistryGeneration.get(); Applications delta = null; // 增量查詢獲取 EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get()); // 響應(yīng)成功 if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) { delta = httpResponse.getEntity(); } if (delta == null) { // 服務(wù)器不允許應(yīng)用delta修訂,因為它不安全。因此得到了完整的登記表,即轉(zhuǎn)換為全量拉取 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)) { // CAS成功 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出于某種原因,數(shù)量有所不同 if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) { reconcileAndLogDifference(delta, reconcileHashCode); // this makes a remoteCall這個可以遠(yuǎn)程呼叫 } } 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()); } }
主要邏輯:
- 增量查詢獲取,響應(yīng)成功獲取數(shù)據(jù)
- CAS成功并且加鎖成功,將響應(yīng)結(jié)果更新到本地,然后釋放鎖
增量更新到本地緩存
private void updateDelta(Applications delta) { int deltaCount = 0; for (Application app : delta.getRegisteredApplications()) { for (InstanceInfo instance : app.getInstances()) { // 從本地獲取,以便更新 Applications applications = getApplications(); String instanceRegion = instanceRegionChecker.getInstanceRegion(instance); if (!instanceRegionChecker.isLocalRegion(instanceRegion)) { Applications remoteApps = remoteRegionVsApps.get(instanceRegion); if (null == remoteApps) { remoteApps = new Applications(); remoteRegionVsApps.put(instanceRegion, remoteApps); } applications = remoteApps; } ++deltaCount; if (ActionType.ADDED.equals(instance.getActionType())) { Application existingApp = applications.getRegisteredApplications(instance.getAppName()); if (existingApp == null) { applications.addApplication(app); } // 將實例{}添加到區(qū)域{}中的現(xiàn)有應(yīng)用程序 // ceam-config:8888,ceam-auth:8005,region null logger.debug("Added instance {} to the existing apps in region {}", instance.getId(), instanceRegion); // 即添加到Application的instancesMap applications.getRegisteredApplications(instance.getAppName()).addInstance(instance); } else if (ActionType.MODIFIED.equals(instance.getActionType())) { Application existingApp = applications.getRegisteredApplications(instance.getAppName()); if (existingApp == null) { applications.addApplication(app); } // 修改現(xiàn)有應(yīng)用程序的實例{} logger.debug("Modified instance {} to the existing apps ", instance.getId()); applications.getRegisteredApplications(instance.getAppName()).addInstance(instance); } else if (ActionType.DELETED.equals(instance.getActionType())) { Application existingApp = applications.getRegisteredApplications(instance.getAppName()); if (existingApp != null) { // 刪除現(xiàn)有應(yīng)用程序的實例{} logger.debug("Deleted instance {} to the existing apps ", instance.getId()); existingApp.removeInstance(instance); /* * We find all instance list from application(The status of instance status is not only the status is UP but also other status) * if instance list is empty, we remove the application. * 我們從應(yīng)用程序中找到所有的實例列表(實例狀態(tài)的狀態(tài)不僅是狀態(tài)是 UP, * 還有其他狀態(tài))如果實例列表為空,我們刪除應(yīng)用程序。 */ if (existingApp.getInstancesAsIsFromEureka().isEmpty()) { applications.removeApplication(existingApp); } } } } } logger.debug("The total number of instances fetched by the delta processor : {}", deltaCount); getApplications().setVersion(delta.getVersion()); // 對提供的實例進(jìn)行洗牌,以便它們不總是以相同的順序返回。 getApplications().shuffleInstances(clientConfig.shouldFilterOnlyUpInstances()); for (Applications applications : remoteRegionVsApps.values()) { applications.setVersion(delta.getVersion()); // 對提供的實例進(jìn)行洗牌,以便它們不總是以相同的順序返回。 applications.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances()); } }
主要邏輯:
- 遍歷增量的應(yīng)用數(shù)據(jù),遍歷應(yīng)用中的實例
- 從本地獲取應(yīng)用數(shù)據(jù),以便更新處理
- 如果是ADDED,則將實例添加到區(qū)域中的現(xiàn)有應(yīng)用程序;如果是MODIFIED,則修改現(xiàn)有應(yīng)用程序的實例;如果是DELETED,則刪除現(xiàn)有應(yīng)用程序的實例,并且從應(yīng)用程序中找到所有的實例列表(實例狀態(tài)的狀態(tài)不僅是狀態(tài)是 UP,還有其他狀態(tài))如果實例列表為空,刪除應(yīng)用程序。
- 對提供的實例進(jìn)行洗牌,以便它們不總是以相同的順序返回。
三、服務(wù)發(fā)現(xiàn)
1、客戶端獲取服務(wù)實例
@Override public List<ServiceInstance> getInstances(String serviceId) { // 委托eurekaClient處理 List<InstanceInfo> infos = this.eurekaClient.getInstancesByVipAddress(serviceId, false); List<ServiceInstance> instances = new ArrayList<>(); for (InstanceInfo info : infos) { instances.add(new EurekaServiceInstance(info)); } return instances; }
跟Nacos的入口是類似的,需要實現(xiàn)spring-cloud-commons的DiscoveryClient接口。這里EurekaDiscoveryClient會委托Eureka項目里面的EurekaClient處理,見下面2分析。然后將Instances列表轉(zhuǎn)換為spring-cloud-commons里面的ServiceInstance類型列表。
2、從本地列表獲取
@Override public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure) { return getInstancesByVipAddress(vipAddress, secure, instanceRegionChecker.getLocalRegion()); } @Override public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure, @Nullable String region) { if (vipAddress == null) { throw new IllegalArgumentException( "Supplied VIP Address cannot be null"); } Applications applications; // 如果eureka:region:沒有指定,則使用默認(rèn)值且非空,即默認(rèn)使用默認(rèn)localRegion if (instanceRegionChecker.isLocalRegion(region)) { // 本地獲取 applications = this.localRegionApps.get(); } else { applications = remoteRegionVsApps.get(region); if (null == applications) { logger.debug("No applications are defined for region {}, so returning an empty instance list for vip " + "address {}.", region, vipAddress); return Collections.emptyList(); } } // secure默認(rèn)false if (!secure) { return applications.getInstancesByVirtualHostName(vipAddress); } else { return applications.getInstancesBySecureVirtualHostName(vipAddress); } }
主要邏輯:
- 調(diào)用重載方法,vipAddress即serviceId,secure為false
- 如果eureka:region:沒有指定,則使用默認(rèn)值且非空,即默認(rèn)使用默認(rèn)localRegion。從本地應(yīng)用獲取列表。
- secure為false,獲取與虛擬主機名關(guān)聯(lián)的instance列表
到此這篇關(guān)于SpringCloud輪詢拉取注冊表與服務(wù)發(fā)現(xiàn)流程詳解的文章就介紹到這了,更多相關(guān)SpringCloud注冊表與服務(wù)發(fā)現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java System.currentTimeMillis()時間的單位轉(zhuǎn)換與計算方式案例詳解
這篇文章主要介紹了Java System.currentTimeMillis()時間的單位轉(zhuǎn)換與計算方式案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08SpringBoot+Quartz+數(shù)據(jù)庫存儲的完美集合
這篇文章主要介紹了SpringBoot+Quartz+數(shù)據(jù)庫存儲的示例代碼,本文通過實例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-02-02關(guān)于Spring MVC同名參數(shù)綁定問題的解決方法
Spring MVC中的參數(shù)綁定還是蠻重要的,最近在使用中遇到了同名參數(shù)綁定的問題,想著總結(jié)分享出來,下面這篇文章主要給大家介紹了關(guān)于Spring MVC同名參數(shù)綁定問題的解決方法,需要的朋友可以參考借鑒,下面來一起看看吧。2017-08-08RocketMQ消息存儲文件的加載與恢復(fù)機制源碼分析
這篇文章主要介紹了RocketMQ源碼分析之消息存儲文件的加載與恢復(fù)機制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05java實現(xiàn)文件和base64相互轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了java如何實現(xiàn)文件和base64相互轉(zhuǎn)換,文中的示例代碼講解詳細(xì),具有一定的參考價值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11