Spring Cloud Netflix-Eureka(一)、服务注册与发现

Spring Cloud Netflix之Eureka源码系列文章一共分为六个片段

Spring Cloud Netflix-Eureka(一)、服务注册与发现
Spring Cloud Netflix-Eureka(二)、信息存储原理
Spring Cloud Netflix-Eureka(三)、自我保护机制
Spring Cloud Netflix-Eureka(四)、心跳续约机制
Spring Cloud Netflix-Eureka(五)、多级缓存机制
Spring Cloud Netflix-Eureka(六)、集群数据同步

一、服务注册

1.1 服务注册机制

Spring Cloud 是一个生态,它提供了一套标准,这套标准可以通过不同的组件来实现,其中就包含服务注册/发现、熔断、负载均衡等,在 spring-cloud-common 这个包中,org.springframework.cloud.client.serviceregistry路径下,可以看到一个服务注册的接口定义 ServiceRegistry。它就是定义了 Spring Cloud 中服务注册的一个接口。

package org.springframework.cloud.client.serviceregistry;

public interface ServiceRegistry<R extends Registration> {
	// 服务上线
	void register(R registration);
	// 服务下线
	void deregister(R registration);
	
	void close();

	void setStatus(R registration, String status);

	<T> T getStatus(R registration);
}

我们看一下它的类关系图,这个接口有一个唯一的实现 EurekaServiceRegistry。表示采用的是 Eureka Server 作为服务注册中心。
在这里插入图片描述
通过调用链路我们会发现,EurekaServiceRegistry.register(R registration) 是在一个叫 EurekaAutoServiceRegistration 的类中被调用,EurekaAutoServiceRegistration 类实现了 SmartLifecycle 接口,会在 Spring 容器加载完成后,调用当前类中的 start() 方法,完成扩展组件的一些自定义逻辑。

1.2 EurekaAutoServiceRegistration

EurekaAutoServiceRegistration 通过 Spring 的 SPI 机制的 EurekaClientAutoConfiguration 类被装载到 Spring 容器中,并在容器初始化完成后,进行 start() 方法调用。

@Configuration(proxyBeanMethods = false)
public class EurekaClientAutoConfiguration {
	@Bean
	public EurekaServiceRegistry eurekaServiceRegistry() {
		return new EurekaServiceRegistry();
	}

	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	@ConditionalOnProperty(
			value = "spring.cloud.service-registry.auto-registration.enabled",
			matchIfMissing = true)
	public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(
			ApplicationContext context, EurekaServiceRegistry registry,
			EurekaRegistration registration) {
		return new EurekaAutoServiceRegistration(context, registry, registration);
	}
	
	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	@ConditionalOnProperty(
			value = "spring.cloud.service-registry.auto-registration.enabled",
			matchIfMissing = true)
	public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient,
			CloudEurekaInstanceConfig instanceConfig,
			ApplicationInfoManager applicationInfoManager, @Autowired(
					required = false) ObjectProvider<HealthCheckHandler> healthCheckHandler) {
		return EurekaRegistration.builder(instanceConfig).with(applicationInfoManager)
				.with(eurekaClient).with(healthCheckHandler).build();
	}
}

1.3 EurekaAutoServiceRegistration.start()

EurekaAutoServiceRegistration 接口实现了 Spring 的扩展接口 SmartLifecycle,会在 Spring 容器启动完成后,调用 start() 方法。

public class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered, SmartApplicationListener {
	@Override
	public void start() {
		// only set the port if the nonSecurePort or securePort is 0 and this.port != 0
		if (this.port.get() != 0) {
			if (this.registration.getNonSecurePort() == 0) {
				this.registration.setNonSecurePort(this.port.get());
			}

			if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
				this.registration.setSecurePort(this.port.get());
			}
		}

		// only initialize if nonSecurePort is greater than 0 and it isn't already running
		// because of containerPortInitializer below
		if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
			// 进行服务注册
			this.serviceRegistry.register(this.registration);
			// 发布一个事件
			this.context.publishEvent(new InstanceRegisteredEvent<>(this,
					this.registration.getInstanceConfig()));
			this.running.set(true);
		}
	}

	@Override
	public void stop() {
		this.serviceRegistry.deregister(this.registration);
		this.running.set(false);
	}
}

this.serviceRegistry.register(this.registration); 实际调用的是 EurekaServiceRegistry 这个对象中的 register() 方法,具体代码如下。

public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration> {
	@Override
	public void register(EurekaRegistration reg) {
		maybeInitializeClient(reg);

		if (log.isInfoEnabled()) {
			log.info("Registering application "
					+ reg.getApplicationInfoManager().getInfo().getAppName()
					+ " with eureka with status "
					+ reg.getInstanceConfig().getInitialStatus());
		}
		// 设置当前实例的状态,一旦这个实例的状态发生变化,只要状态不是DOWN,那么就会被监听器监听并且执行服务注册。
		reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
		// 设置健康检查的处理
		reg.getHealthCheckHandler().ifAvailable(healthCheckHandler -> reg.getEurekaClient()
				.registerHealthCheck(healthCheckHandler));
	}
}

从上述代码来看,注册方法中并没有真正调用 Eureka 的方法去执行注册,而是仅仅设置了一个状态以及设置健康检查处理器。下面看一下 reg.getApplicationInfoManager().setInstanceStatus(InstanceStatus status) 方法,setInstanceStatus() 方法通过监听器来发布一个状态变更事件,服务的注册核心逻辑在 StatusChangeListener 中的 notify() 方法中执行。

public synchronized void setInstanceStatus(InstanceStatus status) {
    InstanceStatus next = instanceStatusMapper.map(status);
    if (next == null) {
        return;
    }

    InstanceStatus prev = instanceInfo.setStatus(next);
    if (prev != null) {
        for (StatusChangeListener listener : listeners.values()) {
            try {
            	// 通过监听器来发布一个状态变更事件
                listener.notify(new StatusChangeEvent(prev, next));
            } catch (Exception e) {
                logger.warn("failed to notify listener: {}", listener.getId(), e);
            }
        }
    }
}

1.4 StatusChangeListener.notify()

StatusChangeListener 是一个静态内部接口。

public static interface StatusChangeListener {
    String getId();

    void notify(StatusChangeEvent statusChangeEvent);
}

所以在上面进行 StatusChangeListener 遍历调用时,我们需要找到 StatusChangeListener 的具体对象是那一个,下面看一下 listeners 是怎么被赋值的。

public class ApplicationInfoManager {
    protected final Map<String, StatusChangeListener> listeners;
    
	@Inject
    public ApplicationInfoManager(EurekaInstanceConfig config, InstanceInfo instanceInfo, OptionalArgs optionalArgs) {
        this.config = config;
        this.instanceInfo = instanceInfo;
        // 进行 listeners 赋值,初始为一个空的 ConcurrentHashMap。
        this.listeners = new ConcurrentHashMap<String, StatusChangeListener>();
        if (optionalArgs != null) {
            this.instanceStatusMapper = optionalArgs.getInstanceStatusMapper();
        } else {
            this.instanceStatusMapper = NO_OP_MAPPER;
        }

        // Hack to allow for getInstance() to use the DI'd ApplicationInfoManager
        instance = this;
    }
}

看到这里小伙伴是不是有点儿懵?不要慌,下面我们看看 Map<String, StatusChangeListener> listeners 是需要在哪里被 put 值的。

public class ApplicationInfoManager {
    protected final Map<String, StatusChangeListener> listeners;
    
    // 注册状态变化监听器
	public void registerStatusChangeListener(StatusChangeListener listener) {
    	listeners.put(listener.getId(), listener);
    }
}

通过调用链路你会发现,ApplicationInfoManager.registerStatusChangeListener(StatusChangeListener listener) 只有在一个叫 DiscoveryClient 的对象中被调用。

1.5 DiscoveryClient

上面我们跟踪到了 ApplicationInfoManager.registerStatusChangeListener(StatusChangeListener listener) 方法会在 DiscoveryClient 的构造函数中进行调用,辣么 DiscoveryClient 又是在哪里初始化的呢???经过寻找,我们发现 DiscoveryClient 并没有在那个地方进行创建,根据自己的开发经验总结,是不是 DiscoveryClient 的子类中进行了 super() 构造函数的调用呢???下面我们先看一下 DiscoveryClient 的类结构图。
在这里插入图片描述
会发现 DiscoveryClient 只有一个子类 CloudEurekaClient,通过查找,我们会在 EurekaClientAutoConfiguration 中发现 CloudEurekaClient 被注入的代码逻辑。

@Configuration(proxyBeanMethods = false)
public class EurekaClientAutoConfiguration {
@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingRefreshScope
	protected static class EurekaClientConfiguration {
		@Autowired
		private ApplicationContext context;

		@Autowired
		private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;

		@Bean(destroyMethod = "shutdown")
		@ConditionalOnMissingBean(value = EurekaClient.class,
				search = SearchStrategy.CURRENT)
		public EurekaClient eurekaClient(ApplicationInfoManager manager,
				EurekaClientConfig config) {
			return new CloudEurekaClient(manager, config, this.optionalArgs,
					this.context);
		}

		@Bean
		@ConditionalOnMissingBean(value = ApplicationInfoManager.class,
				search = SearchStrategy.CURRENT)
		public ApplicationInfoManager eurekaApplicationInfoManager(
				EurekaInstanceConfig config) {
			InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
			return new ApplicationInfoManager(config, instanceInfo);
		}

		@Bean
		@ConditionalOnBean(AutoServiceRegistrationProperties.class)
		@ConditionalOnProperty(
				value = "spring.cloud.service-registry.auto-registration.enabled",
				matchIfMissing = true)
		public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient,
				CloudEurekaInstanceConfig instanceConfig,
				ApplicationInfoManager applicationInfoManager, @Autowired(
						required = false) ObjectProvider<HealthCheckHandler> healthCheckHandler) {
			return EurekaRegistration.builder(instanceConfig).with(applicationInfoManager)
					.with(eurekaClient).with(healthCheckHandler).build();
		}

	}
}

CloudEurekaClient 的构造方法

public CloudEurekaClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, 
		AbstractDiscoveryClientOptionalArgs<?> args, ApplicationEventPublisher publisher) {
	super(applicationInfoManager, config, args);
	this.applicationInfoManager = applicationInfoManager;
	this.publisher = publisher;
	this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class,
			"eurekaTransport");
	ReflectionUtils.makeAccessible(this.eurekaTransportField);
}

果不其然,我们在 CloudEurekaClient 构造函数中发现了 super(applicationInfoManager, config, args); 的代码,也就是 DiscoveryClient 的构造函数调用。

DiscoveryClient 为 Eureka 启动的核心类,进行了一系列初始化,如初始化心跳的线程池、缓存刷新的线程池、心跳检测的线程、实例数据更新的线程等等。

下面我们看一下 DiscoveryClient 构造函数的具体代码。

@Singleton
public class DiscoveryClient implements EurekaClient {
    @Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
        if (args != null) {
            this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
            this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
            this.eventListeners.addAll(args.getEventListeners());
            this.preRegistrationHandler = args.preRegistrationHandler;
        } else {
            this.healthCheckCallbackProvider = null;
            this.healthCheckHandlerProvider = null;
            this.preRegistrationHandler = null;
        }
        
        this.applicationInfoManager = applicationInfoManager;
        InstanceInfo myInfo = applicationInfoManager.getInfo();

        clientConfig = config;
        staticClientConfig = clientConfig;
        transportConfig = config.getTransportConfig();
        instanceInfo = myInfo;
        if (myInfo != null) {
            appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
        } else {
            logger.warn("Setting instanceInfo to a passed in null value");
        }

        this.backupRegistryProvider = backupRegistryProvider;
        this.endpointRandomizer = endpointRandomizer;
        this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo);
        localRegionApps.set(new Applications());

        fetchRegistryGeneration = new AtomicLong(0);

        remoteRegionsToFetch = new AtomicReference<String>(clientConfig.fetchRegistryForRemoteRegions());
        remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(","));

		// 是否要从 Eureka Server 上获取服务地址信息
        if (config.shouldFetchRegistry()) {
            this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }

		// 当前服务启动后,是否要注册到 Eureka Server上
        if (config.shouldRegisterWithEureka()) {
            this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }

        logger.info("Initializing Eureka in region {}", clientConfig.getRegion());

		// 不需要注册 且 不需要获取服务地址
        if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
            logger.info("Client configured to neither register nor query for data.");
            scheduler = null;
            heartbeatExecutor = null;
            cacheRefreshExecutor = null;
            eurekaTransport = null;
            instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());

            // 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();
            initRegistrySize = this.getApplications().size();
            registrySize = initRegistrySize;
            logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                    initTimestampMs, initRegistrySize);

            return;  // no need to setup up an network tasks and we are done
        }

        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);
        }

		// 如果需要获取 Eureka Server 中的服务地址,则调用 fetchRegistry() 方法进行拉取
        if (clientConfig.shouldFetchRegistry()) {
            try {
            	// 从 Eureka Server 中拉取注册地址信息
            	// 注意:服务发现的核心流程
                boolean primaryFetchRegistryResult = fetchRegistry(false);
                if (!primaryFetchRegistryResult) {
                    logger.info("Initial registry fetch from primary servers failed");
                }
                // 从备用地址拉取服务注册信息
                boolean backupFetchRegistryResult = true;
                if (!primaryFetchRegistryResult && !fetchRegistryFromBackup()) {
                    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);
            }
        }

        // call and execute the pre registration handler before all background tasks (inc registration) is started
        // 判断是否存在预注册处理器,存在就执行
        if (this.preRegistrationHandler != null) {
            this.preRegistrationHandler.beforeRegistration();
        }

		// 如果需要注册到Eureka server并且是开启了初始化的时候强制注册,则调用 register()发起服务
		// 注册(默认情况下,shouldEnforceRegistrationAtInit为false)
        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();
        initRegistrySize = this.getApplications().size();
        registrySize = initRegistrySize;
        logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                initTimestampMs, initRegistrySize);
    }
}

1.6 DiscoveryClient.initScheduledTasks()

上面 DiscoveryClient 的构造函数中,会调用 initScheduledTasks() 方法,初始化一个定时任务,负责心跳、实例数据更新,具体代码如下。

@Singleton
public class DiscoveryClient implements EurekaClient {
	/**
	 * 初始化一个定时任务,负责心跳、实例数据更新
	 */
	private void initScheduledTasks() {
		// 如果需要获取 Eureka Server 中的服务地址,则会开启 cacheRefreshExecutor 这个定时任务,用于进行缓存数据的刷新
        if (clientConfig.shouldFetchRegistry()) {
            // registry cache refresh timer
            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);
        }

		// 开启了服务注册到 Eureka Server
        if (clientConfig.shouldRegisterWithEureka()) {
            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()
            );
            scheduler.schedule(
                    heartbeatTask,
                    renewalIntervalInSecs, TimeUnit.SECONDS);

            // InstanceInfo replicator
            // 创建一个 InstanceInfoReplicator 实例信息复制器
            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 (statusChangeEvent.getStatus() == InstanceStatus.DOWN) {
                        logger.error("Saw local status change event {}", statusChangeEvent);
                    } else {
                        logger.info("Saw local status change event {}", statusChangeEvent);
                    }
                    // 判断实例数据是否发生变化,如果有变化,更新服务注册中心的数据
                    instanceInfoReplicator.onDemandUpdate();
                }
            };

			// 注册实例状态变化的监听
            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }

			// 启动一个实例信息复制器,主要就是为了开启一个定时线程,每40秒判断实例信息是否变更,如果变更了则重新注册
            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }
    }
}

在上述代码中,我们发现了一个很重要的逻辑:初始化一个StatusChangeListener,并保存到 ApplicationInfoManager 中的 listeners 集合中。

applicationInfoManager.registerStatusChangeListener(statusChangeListener);

还记得前面源码分析中的服务注册逻辑吗?当服务器启动或者停止时,会遍历 ApplicationInfoManager 中的 protected final Map<String, StatusChangeListener> listeners;,逐个调用 listener.notify(StatusChangeEvent statusChangeEvent) 方法。

1.7 instanceInfoReplicator.onDemandUpdate();

这个方法的主要作用是判断实例数据是否发生变化,如果有变化,更新服务注册中心的数据,下面看一下具体的实现。

/**
 * 判断实例数据是否发生变化,如果有变化,更新服务注册中心的数据
 */
public boolean onDemandUpdate() {
	// 限流判断
    if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {
        if (!scheduler.isShutdown()) {
        	// 提交任务
            scheduler.submit(new Runnable() {
                @Override
                public void run() {
                    logger.debug("Executing on-demand update of local InstanceInfo");

					// 取出之前已经提交的任务,也就是在 EurekaAutoServiceRegistration.start() 方法中提交的更新任务
					// 如果任务还没有执行完成,则取消之前的任务
                    Future latestPeriodic = scheduledPeriodicRef.get();
                    if (latestPeriodic != null && !latestPeriodic.isDone()) {
                        logger.debug("Canceling the latest scheduled update, it will be rescheduled at the end of on demand update");
                        latestPeriodic.cancel(false);
                    }
					// 调用run方法,令任务在延时后执行,相当于周期性任务中的一次
                    InstanceInfoReplicator.this.run();
                }
            });
            return true;
        } else {
            logger.warn("Ignoring onDemand update due to stopped scheduler");
            return false;
        }
    } else {
        logger.warn("Ignoring onDemand update due to rate limiter");
        return false;
    }
	
	/**
	 * run方法调用register方法进行服务注册,并且在finally中,每30s会定时执行一下当前的run 方法进行检查。
	 */
	public void run() {
        try {
        	// 刷新实例信息
            discoveryClient.refreshInstanceInfo();

			// 判断是否有状态更新,如果有,则获取更新的时间
            Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
            if (dirtyTimestamp != null) {// 有脏数据,重新注册
            	// 注册服务
                discoveryClient.register();
                instanceInfo.unsetIsDirty(dirtyTimestamp);
            }
        } catch (Throwable t) {
            logger.warn("There was a problem with the instance info replicator", t);
        } finally {
        	// 每隔30s,执行一次当前的 run() 方法
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }
}

1.9 DiscoveryClient.register()

历尽千辛万苦,我们最终找到了 Eureka 的服务注册方法 DiscoveryClient.register() 中的 eurekaTransport.registrationClient.register(instanceInfo)

@Singleton
public class DiscoveryClient implements EurekaClient {
	/**
	 * 服务注册
	 */
	boolean register() throws Throwable {
        logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
        EurekaHttpResponse<Void> httpResponse;
        try {
            httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
        } catch (Exception e) {
            logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
            throw e;
        }
        if (logger.isInfoEnabled()) {
            logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
        }
        return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
    }
}

通过断点的方式,我们发现最终会调用 AbstractJerseyEurekaHttpClient 中的 EurekaHttpResponse<Void> register(InstanceInfo info) 方法。

public abstract class AbstractJerseyEurekaHttpClient implements EurekaHttpClient {
	@Override
    public EurekaHttpResponse<Void> register(InstanceInfo info) {
        String urlPath = "apps/" + info.getAppName();
        ClientResponse response = null;
        try {
            Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
            addExtraHeaders(resourceBuilder);
            response = resourceBuilder
                    .header("Accept-Encoding", "gzip")
                    .type(MediaType.APPLICATION_JSON_TYPE)
                    .accept(MediaType.APPLICATION_JSON)
                    .post(ClientResponse.class, info);
            return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                        response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                response.close();
            }
        }
    }
}

很显然,这里是发起了一次 Http 请求,访问 Eureka Server 的 apps/${APP_NAME} 接口,将当 前服务实例的信息发送到 Eureka Server 进行保存。 至此,我们基本上已经知道 Spring Cloud Eureka 是如何在启动的时候把服务信息注册到 Eureka Server上的了。

1.10 总结

服务注册的过程分几个个步骤:

  1. Spring Cloud 应用在启动时,基于 SmartLifeCycle 接口回调,触发 StatusChangeListener 事件通知。
  2. StatusChangeListener 对象会在 CloudEurekaClient 对象注入的时候,调用父类(DiscoveryClient)的构造方法进行创建
  3. 在 StatusChangeListener 的回调方法 notify() 中,通过调用 instanceInfoReplicator.onDemandUpdate() 方法,去更新客户端的地址信息,从而完成服务注册。

二、服务发现

上面分析了 Eureka 服务注册的流程,下面我们继续分析一下服务发现的流程。

客户端针对服务发现这个需求点,需要满足以下两个功能:

  1. 服务启动的时候获取 Eureka Server 中的服务地址列表;
  2. 在 Eureka Server 端地址发现变化后,需要动态更新本地服务列表;

基于以上两个需求,下面我们具体分析一下 Eureka 是怎么实现的。Eureka 在服务启动是,如果开启了eureka.client.fetch-registry=true(默认为true)配置,那么 DiscoveryClient 在启动时,就会去服务器上拉取一次地址信息。

2.1 服务启动的时候获取 Eureka Server 中的服务地址列表

2.1.1 DiscoveryClient 构造函数

在上面 DiscoveryClient 构造函数中有这么一段代码 boolean primaryFetchRegistryResult = fetchRegistry(false); 这就是服务发现的核心流程代码,具体如下。

@Singleton
public class DiscoveryClient implements EurekaClient {
    @Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
        // 省略其他代码...
        
		// 如果需要获取 Eureka Server 中的服务地址,则调用 fetchRegistry() 方法进行拉取
		// 注意:服务发现的核心流程
        if (clientConfig.shouldFetchRegistry()) {
            try {
            	// 从 Eureka Server 中拉取注册地址信息
                boolean primaryFetchRegistryResult = fetchRegistry(false);
                if (!primaryFetchRegistryResult) {
                    logger.info("Initial registry fetch from primary servers failed");
                }
                // 从备用地址拉取服务注册信息
                boolean backupFetchRegistryResult = true;
                if (!primaryFetchRegistryResult && !fetchRegistryFromBackup()) {
                    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);
            }
        }
		
		// 省略其他代码...
    }
}

2.1.2 DiscoveryClient.fetchRegistry(boolean forceFullRegistryFetch)

@Singleton
public class DiscoveryClient implements EurekaClient {
	
	/**
	 * 拉取服务地址
	 * @param forceFullRegistryFetch 是否需要全量获取
	 */
	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();

			/**
			 * 判断多个条件,确定是否触发全量更新,如下任一个满足都会全量更新:
			 * 1. 是否禁用增量更新 clientConfig.shouldDisableDelta()
			 * 2. 是否对某个region特别关注 (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
			 * 3. 外部调用时是否通过入参指定全量更新 forceFullRegistryFetch
			 * 4. 本地还未缓存有效的服务列表信息 (applications == null) || (applications.getRegisteredApplications().size() == 0) || (applications.getVersion() == -1)
			 */
            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.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
        /**
         * 根据缓存中保存的数据更新判断是否需要更新 lastRemoteInstanceStatus
         * 1.检查刚刚更新的缓存中,有来自Eureka server的服务列表,其中包含了当前应用的状态
         * 2.当前实例的成员变量lastRemoteInstanceStatus,记录的是最后一次更新的当前应用状态
         * 上述两种状态在updateInstanceRemoteStatus方法中作比较 ,如果不一致,就更新lastRemoteInstanceStatus,并且广播对应的事件
         */
        updateInstanceRemoteStatus();

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

2.1.3 DiscoveryClient.getAndStoreFullRegistry() 全量更新

全量更新具体代码如下。

/**
 * 全量更新
 */
private void getAndStoreFullRegistry() throws Throwable {
      long currentUpdateGeneration = fetchRegistryGeneration.get();

      logger.info("Getting all instance registry info from the eureka server");

      Applications apps = null;
      /**
       * 判断是否 registryRefreshSingleVipAddress 是否为空,
       * 如果为空,调用 eurekaTransport.queryClient.getApplications(remoteRegionsRef.get()) 获取全部服务地址
       * 如果不为空,则只获取 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();
      }
      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));
          logger.debug("Got full registry with apps hashcode {}", apps.getAppsHashCode());
      } else {
          logger.warn("Not updating applications as another thread is updating it already");
      }
  }

eurekaTransport.queryClient.getApplications(remoteRegionsRef.get()) 获取全部服务地址。

public abstract class AbstractJerseyEurekaHttpClient implements EurekaHttpClient {
	@Override
    public EurekaHttpResponse<Applications> getApplications(String... regions) {
    	// 调用 /apps 接口获取全部服务地址
        return getApplicationsInternal("apps/", regions);
    }
    
	private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
        ClientResponse response = null;
        String regionsParamValue = null;
        try {
            WebResource webResource = jerseyClient.resource(serviceUrl).path(urlPath);
            if (regions != null && regions.length > 0) {
                regionsParamValue = StringUtil.join(regions);
                webResource = webResource.queryParam("regions", regionsParamValue);
            }
            Builder requestBuilder = webResource.getRequestBuilder();
            addExtraHeaders(requestBuilder);
            response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);

            Applications applications = null;
            if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
                applications = response.getEntity(Applications.class);
            }
            return anEurekaHttpResponse(response.getStatus(), Applications.class)
                    .headers(headersOf(response))
                    .entity(applications)
                    .build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP GET {}/{}?{}; statusCode={}",
                        serviceUrl, urlPath,
                        regionsParamValue == null ? "" : "regions=" + regionsParamValue,
                        response == null ? "N/A" : response.getStatus()
                );
            }
            if (response != null) {
                response.close();
            }
        }
    }
}

获取 registryRefreshSingleVipAddress 地址

public abstract class AbstractJerseyEurekaHttpClient implements EurekaHttpClient {
	@Override
    public EurekaHttpResponse<Applications> getVip(String vipAddress, String... regions) {
    	// 调用 /vips 接口获取 registryRefreshSingleVipAddress 地址
        return getApplicationsInternal("vips/" + vipAddress, regions);
    }
    
	private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
        ClientResponse response = null;
        String regionsParamValue = null;
        try {
            WebResource webResource = jerseyClient.resource(serviceUrl).path(urlPath);
            if (regions != null && regions.length > 0) {
                regionsParamValue = StringUtil.join(regions);
                webResource = webResource.queryParam("regions", regionsParamValue);
            }
            Builder requestBuilder = webResource.getRequestBuilder();
            addExtraHeaders(requestBuilder);
            response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);

            Applications applications = null;
            if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
                applications = response.getEntity(Applications.class);
            }
            return anEurekaHttpResponse(response.getStatus(), Applications.class)
                    .headers(headersOf(response))
                    .entity(applications)
                    .build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP GET {}/{}?{}; statusCode={}",
                        serviceUrl, urlPath,
                        regionsParamValue == null ? "" : "regions=" + regionsParamValue,
                        response == null ? "N/A" : response.getStatus()
                );
            }
            if (response != null) {
                response.close();
            }
        }
    }
}

2.1.4 DiscoveryClient.getAndUpdateDelta(Applications applications) 增量更新

增量更新具体代码如下。

/**
 * 增量更新
 */
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();
        }

		// 如果 delta 为空,则调用全量更新
        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());
        }
    }

2.2 在 Eureka Server 端地址发现变化后,需要动态更新本地服务列表

在分析 Eureka Server 服务注册流程中,DiscoveryClient 的构造函数中,会调用 DiscoveryClient.initScheduledTasks() 方法,初始化一个定时任务,负责心跳、实例数据更新,定时任务更新缓存则在这个定时任务中,具体代码如下。

@Singleton
public class DiscoveryClient implements EurekaClient {
	/**
	 * 初始化一个定时任务,负责心跳、实例数据更新
	 */
	private void initScheduledTasks() {
		// 如果需要获取 Eureka Server 中的服务地址,则会开启 cacheRefreshExecutor 这个定时任务,用于进行缓存数据的刷新
        if (clientConfig.shouldFetchRegistry()) {
            // registry cache refresh timer
            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);
        }

		// 省略其他代码...
    }
}

2.2.1 TimedSupervisorTask

TimedSupervisorTask 是固定间隔的周期性任务(默认30s),一旦遇到超时就会将下一个周期的间隔时间调大,如果连续超时,那么每次间隔时间都会增大一倍,一直到达外部参数设定的上限为止,一旦新任务不再超时,间隔时间又会自动恢复为初始值。这种设计思想采用了衰减重试的思想,非常值得借鉴学习。

@Override
public TimedSupervisorTask(String name, ScheduledExecutorService scheduler, ThreadPoolExecutor executor,
                               int timeout, TimeUnit timeUnit, int expBackOffBound, Runnable task) {
    public void run() {
        Future<?> future = null;
        try {
        	// 使用Future,可以设定子线程的超时时间
            future = executor.submit(task);
            threadPoolLevelGauge.set((long) executor.getActiveCount());
            // 指定等待子线程的最长时间
            future.get(timeoutMillis, TimeUnit.MILLISECONDS);  // block until done or timeout
            // 每次执行任务成功都重置delay
            delay.set(timeoutMillis);
            threadPoolLevelGauge.set((long) executor.getActiveCount());
            successCounter.increment();
        } catch (TimeoutException e) {
            logger.warn("task supervisor timed out", e);
            timeoutCounter.increment();

            long currentDelay = delay.get();
            // future任务线程超时,delay变量翻倍,但不会超过外部调用时设定的最大延时时间
            long newDelay = Math.min(maxDelay, currentDelay * 2);
            // 使用了CAS设置为最新的值
            delay.compareAndSet(currentDelay, newDelay);
        } catch (RejectedExecutionException e) {
        	// 一旦线程池的阻塞队列中放满了待处理任务,触发了拒绝策略,就会将调度器停掉
            if (executor.isShutdown() || scheduler.isShutdown()) {
                logger.warn("task supervisor shutting down, reject the task", e);
            } else {
                logger.warn("task supervisor rejected the task", e);
            }

            rejectedCounter.increment();
        } catch (Throwable e) {
        	// 一旦出现未知的异常,就停掉调度器
            if (executor.isShutdown() || scheduler.isShutdown()) {
                logger.warn("task supervisor shutting down, can't accept the task");
            } else {
                logger.warn("task supervisor threw an exception", e);
            }

            throwableCounter.increment();
        } finally {
        	// 调用cancel方法来清理任务
            if (future != null) {
                future.cancel(true);
            }

			// 只要调度器没有停止,就再指定等待时间之后在执行一次同样的任务
            if (!scheduler.isShutdown()) {
            	// 周期性任务:只要没有停止调度器,就再创建一次性任务,在dealy后执行
                scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
            }
        }
    }
}

2.2.2 CacheRefreshThread

@Singleton
public class DiscoveryClient implements EurekaClient {
	class CacheRefreshThread implements Runnable {
	    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.
            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
                    synchronized (instanceRegionChecker.getAzToRegionMapper()) {
                        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
                    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);
        }
    }
}

2.2.3 总结

服务发现的过程分几个个步骤:

  1. Spring Cloud 应用在启动时,判断是否需要获取远程服务地址,如果需要获取,则调用 /apps 拉取。
  2. 如果需要获取远程服务地址,则开启一个定时延时任务,定时刷新本地缓存。
  3. 定时延时任务通过循环调用实现,如果执行错误,则延迟时间加倍,直到外部调用时设定的最大延时时间。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值