【微服务】SpringCloud轮询拉取注册表及服务发现源码解析

news2024/10/7 16:25:25

💖 Spring家族及微服务系列文章

✨【微服务】SpringCloud微服务剔除下线源码解析

✨【微服务】SpringCloud微服务续约源码解析

✨【微服务】SpringCloud微服务注册源码解析

✨【微服务】Nacos2.x服务发现?RPC调用?重试机制?

✨【微服务】Nacos通知客户端服务变更以及重试机制

✨【微服务】Nacos服务发现源码分析

✨【微服务】SpringBoot监听器机制以及在Nacos中的应用

✨【微服务】Nacos服务端完成微服务注册以及健康检查流程

✨【微服务】Nacos客户端微服务注册原理流程

✨【微服务】SpringCloud中使用Ribbon实现负载均衡的原理

✨【微服务】SpringBoot启动流程注册FeignClient

✨【微服务】SpringBoot启动流程初始化OpenFeign的入口

✨Spring Bean的生命周期

✨Spring事务原理

✨SpringBoot自动装配原理机制及过程

✨SpringBoot获取处理器流程

✨SpringBoot中处理器映射关系注册流程

✨Spring5.x中Bean初始化流程

✨Spring中Bean定义的注册流程

✨Spring的处理器映射器与适配器的架构设计

✨SpringMVC执行流程图解及源码

目录

​编辑

💖 Spring家族及微服务系列文章

一、前言

二、轮询拉取注册表

1、构造初始化

1.1、缓存刷新

2、刷新注册表

2.1、全量拉取注册表

3、缓存刷新任务

4、增量拉取注册表

4.1、增量更新到本地缓存

三、服务发现

1、客户端获取服务实例

2、从本地列表获取


一、前言

    上一篇我们讨论了关于周期性任务的一些应用等,本篇文章我们来探究一下这些内容:周期性刷新注册表?全量拉取注册表还是增量拉取注册表、更新本地缓存?服务发现的入口、获取本地服务列表?

二、轮询拉取注册表

1、构造初始化

同样是在Spring容器初始化的过程中初始化的,基于SpringBoot自动装配集成。上一节也讲了一部分,这里补充:

@Singleton
public class DiscoveryClient implements EurekaClient {
    ....省略n行代码......

    private final ScheduledExecutorService scheduler;
    // additional executors for supervised subtasks监督子任务的附加执行器
    private final ThreadPoolExecutor heartbeatExecutor;
    private final ThreadPoolExecutor cacheRefreshExecutor;

    private TimedSupervisorTask cacheRefreshTask;
    private TimedSupervisorTask heartbeatTask;
    
    ....省略n行代码......

    // Spring容器初始化时候调用
    public DiscoveryClient(ApplicationInfoManager applicationInfoManager,
                           final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) {
        // 调用下面重载方法
        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心跳和缓存刷新的默认大小分别为2-1
            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();

        ....省略n行代码......
            
        } catch (Throwable e) {
            throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
        }


        // 默认true,可更改配置不建议
        if (clientConfig.shouldFetchRegistry()) {
            try {// 初始化注册表
                boolean primaryFetchRegistryResult = fetchRegistry(false);
                // 下面主要打印失败日志,初始化时控制台可见是处理成功的
                if (!primaryFetchRegistryResult) {
                    // 从主服务器初始注册表提取失败
                    logger.info("Initial registry fetch from primary servers failed");
                }
                boolean backupFetchRegistryResult = true;
                if (!primaryFetchRegistryResult && !fetchRegistryFromBackup()) {
                    // 如果所有的eureka服务器网址都无法访问,从备份注册表中获取注册表信息也失败。
                    backupFetchRegistryResult = false;
                    // 从备份服务器初始注册表提取失败
                    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行代码......

        // 最后,初始化调度任务(例如,集群解析器、 heartbeat、 instanceInfo replicator、 fetch
        initScheduledTasks();

      ....省略n行代码......
    }

主要逻辑:

  1. 初始化缓存刷新执行器,用于周期性执行任务,下面继续分析
  2. 默认需要刷新注册表,默认不使用全量拉取,但是初始化时使用下面2分析:会调用注册中心完成注册表初始化,返回是否刷新成功;如果从主服务器初始注册表提取失败打印日志;如果所有的eureka服务器网址都无法访问,从备份注册表中获取注册表信息也失败打印日志;在启动时获取注册表错误,抛异常。
  3. 可见在2中没有特殊原因的话,一般是使用全量拉取注册表初始化成功了,否则的话抛异常

1.1、缓存刷新

大部分逻辑在前面的章节已经分析,TimedSupervisorTask跟发送心跳服务续约逻辑是一样的,这里补充刷新本地服务列表任务。

    private void initScheduledTasks() {
        // 默认true,可更改配置不建议
        if (clientConfig.shouldFetchRegistry()) {
            // registry cache refresh timer注册表缓存刷新计时器
            // 默认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;
             */
            // 默认30
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

            // Heartbeat timer心跳任务
            heartbeatTask = new TimedSupervisorTask(
                    "heartbeat",
                    scheduler,
                    heartbeatExecutor,
                    renewalIntervalInSecs,
                    TimeUnit.SECONDS,
                    expBackOffBound,
                    new HeartbeatThread()
            );
            // 默认的情况下会每隔30秒向注册中心 (eureka.instance.lease-renewal-interval-in-seconds)发送一次心跳来进行服务续约
            scheduler.schedule(
                    heartbeatTask,
                    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) {
                    // Saw local status change event StatusChangeEvent [timestamp=1668595102513, current=UP, previous=STARTING]
                    logger.info("Saw local status change event {}", statusChangeEvent);
                    instanceInfoReplicator.onDemandUpdate();
                }
            };
            // 初始化状态变更监听者
            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }

            // 3.2 定时刷新服务实例信息和检查应用状态的变化,在服务实例信息发生改变的情况下向server重新发起注册
            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }
    }

主要逻辑:

  1. 默认需要刷新注册表,要达到服务高可用。1)clientConfig.getRegistryFetchIntervalSeconds()获取注册表刷新时间,默认30秒,可在配置文件更改;2)初始化cacheRefreshTask为TimedSupervisorTask类型,跟心跳任务一样处理逻辑,我们这节就只分析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 被禁用或者是第一次,那么获取所有的应用程序
            Applications applications = getApplications();

            // shouldDisableDelta默认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
        // 在更新实例远程状态之前通知缓存刷新
        onCacheRefreshed();

        // Update remote status based on refreshed data held in the cache
        // 根据缓存中保存的刷新数据更新远程状态
        updateInstanceRemoteStatus();

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

主要逻辑:

  1. 由上面的1.1可见状态变更监听者还没有初始化,从前面的文章也知道它的作用完成服务注册,故这里从本地获取应用就为空。所以先打印下日志,调用全量拉取注册表方法,下面分析。轨迹跟踪对象非空,关闭。
  2. 在更新实例远程状态之前通知缓存刷新
  3. 根据缓存中保存的刷新数据更新远程状态

2.1、全量拉取注册表

    private void getAndStoreFullRegistry() throws Throwable {
        long currentUpdateGeneration = fetchRegistryGeneration.get();
        // 从 eureka 服务器上获取所有实例注册信息
        logger.info("Getting all instance registry info from the eureka server");

        Applications apps = null;
        // RegistryRefreshSingleVipAddress默认空
        EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null
                ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())
                : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());
        // 响应成功获取应用
        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");
        }
    }

主要逻辑:

  1. RegistryRefreshSingleVipAddress默认空,故调用eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())请求注册中心
  2. 响应成功获取应用
  3. 如果应用apps空则打印下日志;一般CAS成功,在筛选仅具有 UP 状态的实例的应用程序并对它们进行洗牌之后获取应用程序,缓存到本地localRegionApps(AtomicReference<Applications>类型);否则打印下日志

3、缓存刷新任务

    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.
            // 这可以确保对要获取的远程区域的动态更改得到遵守。
            // 默认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 {
                            // 并发获取修改的远程区域,忽略从{}到{}的更改
                            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 实现了Runnable接口,但是run()中任务逻辑封装了出去,在refreshRegistry()中处理。在2中分析了初始化时已经使用全量拉取注册表并缓存应用到本地localRegionApps,那么这里使用延迟任务处理的话就会执行增量拉取逻辑了,在下面4分析

4、增量拉取注册表

    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) {
            // 服务器不允许应用delta修订,因为它不安全。因此得到了完整的登记表,即转换为全量拉取
            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出于某种原因,数量有所不同
            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. 增量查询获取,响应成功获取数据
  2. CAS成功并且加锁成功,将响应结果更新到本地,然后释放锁

4.1、增量更新到本地缓存

    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);
                    }
                    // 将实例{}添加到区域{}中的现有应用程序
                    // 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);
                    }
                    // 修改现有应用程序的实例{}
                    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) {
                        // 删除现有应用程序的实例{}
                        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.
                         * 我们从应用程序中找到所有的实例列表(实例状态的状态不仅是状态是 UP,
                         * 还有其他状态)如果实例列表为空,我们删除应用程序。
                         */
                        if (existingApp.getInstancesAsIsFromEureka().isEmpty()) {
                            applications.removeApplication(existingApp);
                        }
                    }
                }
            }
        }
        logger.debug("The total number of instances fetched by the delta processor : {}", deltaCount);

        getApplications().setVersion(delta.getVersion());
        // 对提供的实例进行洗牌,以便它们不总是以相同的顺序返回。
        getApplications().shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());

        for (Applications applications : remoteRegionVsApps.values()) {
            applications.setVersion(delta.getVersion());
            // 对提供的实例进行洗牌,以便它们不总是以相同的顺序返回。
            applications.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
        }
    }

主要逻辑:

  1. 遍历增量的应用数据,遍历应用中的实例
  2. 从本地获取应用数据,以便更新处理
  3. 如果是ADDED,则将实例添加到区域中的现有应用程序;如果是MODIFIED,则修改现有应用程序的实例;如果是DELETED,则删除现有应用程序的实例,并且从应用程序中找到所有的实例列表(实例状态的状态不仅是状态是 UP,还有其他状态)如果实例列表为空,删除应用程序。
  4. 对提供的实例进行洗牌,以便它们不总是以相同的顺序返回。

三、服务发现

1、客户端获取服务实例

	@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的入口是类似的,需要实现spring-cloud-commons的DiscoveryClient接口。这里EurekaDiscoveryClient会委托Eureka项目里面的EurekaClient处理,见下面2分析。然后将Instances列表转换为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:没有指定,则使用默认值且非空,即默认使用默认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默认false
        if (!secure) {
            return applications.getInstancesByVirtualHostName(vipAddress);
        } else {
            return applications.getInstancesBySecureVirtualHostName(vipAddress);

        }

    }

主要逻辑:

  1. 调用重载方法,vipAddress即serviceId,secure为false
  2. 如果eureka:region:没有指定,则使用默认值且非空,即默认使用默认localRegion。从本地应用获取列表。
  3. secure为false,获取与虚拟主机名关联的instance列表

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/19870.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习:支持向量机SVM的SVC和SVR

支持向量机SVMSVM的工作原理及分类支持向量机的原理线性可分的SVM非线性可分的支持向量机支持向量机分类SVC支持向量机回归SVRSVR原理SVR模型时间序列曲线预测SVM的工作原理及分类 支持向量机的原理 支持向量机(Support Vector Machine&#xff0c;SVM)是一种二类分类器&…

积极融入信创生态 | 思腾合力软件产品完成多个信创产品适配

从2019年我国提出发展信创产业&#xff0c;2020年迈入信创发展元年&#xff0c;到2022信创开始向行业深水区迈进&#xff0c;逐渐延伸到金融、电信等重点行业、核心业务中&#xff0c;开启了“行业信创元年”。一个真正的“大信创”时代已开启&#xff0c;一个数万亿规模的市场…

MybatisPlus---从入门到深化

目录 MyBatisPlus入门 MyBatisPlus介绍 ​编辑Spring集成MyBatisPlus SpringBoot集成MyBatisPlus MyBatisPlus_CRUD 添加 CRUD_相关注解 修改 删除 查询 条件构造器 全局配置 ActiveRecord_概念 ActiveRecord_增删改查 MyBatisPlus插件_插件概述 MyBatisPlus插件_…

超神之路 数据结构 3 —— Stack栈实现及应用

栈也是一种线性表结构&#xff0c;相较于数组&#xff0c;栈对应的操作是数组的子集&#xff0c;我们只要实现从一端添加元素&#xff0c;并从这个一端取出元素&#xff0c;这一端我们称呼它为栈顶&#xff0c;正是由于这种结构&#xff0c;它具有“后入先出”&#xff08;LIFO…

PTA题目 计算工资

某公司员工的工资计算方法如下&#xff1a;一周内工作时间不超过40小时&#xff0c;按正常工作时间计酬&#xff1b;超出40小时的工作时间部分&#xff0c;按正常工作时间报酬的1.5倍计酬。员工按进公司时间分为新职工和老职工&#xff0c;进公司不少于5年的员工为老职工&#…

基于jsp+mysql+ssm健身信息交流网站-计算机毕业设计

项目介绍 随着全民健身运动的兴起&#xff0c;越来越多的人走进了健身房&#xff0c;而传统的管理模式已不能适应现代健身机构的发展趋势&#xff0c;如何增强健身房会员卡的管理和完善客户服务&#xff0c;成了健身房发展的当务之急。健身信息管理系统的研究与开发&#xff0…

文本摘要实战:基于句子相似度矩阵构建图结构实现文本摘要 代码+数据

任务描述: 自动文本摘要(Text Summarization)是指给出一段文本,我们从中提取出要点,然后再形成一个短的概括性的文本。自动的文本摘要是非常具有挑战性的,当我们作为人类总结一篇文章时,我们通常会完整地阅读它以发展我们的理解,然后写一个摘要突出其要点。由于计算机缺乏…

计算机毕业设计springboot+vue基本微信小程序的码高教育课后在线小程序

项目介绍 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,码高教育课后在线小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行码高教育课后在线小程序的设计与开…

HTML期末大学生网页设计作业----锤子手机 1页

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 在线商城购物 | 水果商城 | 商城系统建设 | 多平台移动商城 | H5微商城购物商城项目 | HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&a…

java计算机毕业设计ssm基于JAVA的网上购物系统-商城购物网站

项目介绍 本网上购物网站是针对目前商城的实际需求,从实际工作出发,对过去的网上购物存在的问题进行分析,完善用户的使用体会。采用计算机系统来管理信息,取代人工管理模式,查询便利,信息准确率高,节省了开支,提高了工作的效率。 本系统结合计算机系统的结构、概念、模型、原理…

【机器学习】红酒数据集和加利福尼亚的房价数据的随机森林算法详解

一.随机森林 1.1随机森林的构建 bootstrap参数代表的是bootstrap sample&#xff0c;也就是“有放回抽样”的意思&#xff0c;指每次从样本空间中可以重复抽取同一个样本&#xff08;因为样本在第一次被抽取之后又被放回去了&#xff09; 假设&#xff0c;原始样本是”苹果&…

【POJ No. 3253】 围栏修复 Fence Repair

【POJ No. 3253】 围栏修复 Fence Repair 北大OJ 题目地址 这道题其实我们 之前就做过了 https://blog.csdn.net/weixin_44226181/article/details/127064923 当时我们 是在学习哈夫曼树 【题意】 约翰想修牧场周围的篱笆&#xff0c;需要N 块&#xff08;1≤N ≤20000&…

CMake中if的使用

CMake中的if命令用于有条件地执行一组命令&#xff0c;其格式如下&#xff1a; if(<condition>)<commands> elseif(<condition>) # optional block, can be repeated<commands> else() # optional block<commands> endif() 根据Co…

HTML5期末大作业:旅游网页设计与实现——旅游风景区网站HTML+CSS (1)

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 游景点介绍 | 旅游风景区 | 家乡介绍 | 等网站的设计与制作 | HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&#xff1a;结构 CSS&…

【项目实战:核酸检测平台】第一章 逆向工程

引言 准备做个项目实战系列&#xff0c;选一些比较有代表性&#xff0c;难度适中、规模合适的项目&#xff0c;从不同的角度&#xff0c;不同的深度去解析。很多初学者都抱怨没有实战项目&#xff0c;而学校/机构里大量用烂的学生管理、图书管理、电子商城等项目实在没有什么新…

LeetCode--230. 二叉搜索树中第K小的元素(C++描述)

// Source :https://leetcode.cn/problems/kth-smallest-element-in-a-bst/ // Date : 2022-11-19 /************************************************************************************** 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计…

Nginx 快速入门

一、简述 1.1 介绍 传统的 Web 服务器&#xff0c;每个客户端连接作为一个单独的进程或线程处理&#xff0c;需在切换任务时将 CPU 切换到新的任务并创建一个新的运行时上下文&#xff0c;消耗额外的内存和 CPU 时间&#xff0c;当并发请求增加时&#xff0c;服务器响应变慢&…

修改xml文件

一、前言 本节是修改xml;同样方法也可修改arxml文件&#xff1b;主要使用LET.Element创建节点&#xff1b; 二、知识点 1、专门用于格式化处理xml文件使其具有层级分明的缩进函数 #专门用于格式化处理xml文件使其具有层级分明的缩进 def indent( elem, level0): i "\n&…

基于51单片机的全自动智能洗衣机控制系统Proteus仿真

资料编号&#xff1a;108 下面是相关功能视频演示&#xff1a; 108-基于51单片机的全自动智能洗衣机控制系统Proteus仿真&#xff08;仿真源码全套资料&#xff09;设计功能&#xff1a; 1、对水温实时显示 2、显示滚筒转速 3、显示运行倒计时 4、能通过按钮分别设置洗涤时间…

一文带你深入理解【Java基础】· 常用类(上)字符串相关类

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…