目录java
本文使用Spring Cloud Eureka分析spring
Spring Cloud版本: Dalston.SR5json
spring-cloud-starter-eureka版本: 1.3.6.RELEASEbootstrap
netflix eureka版本: 1.6.2缓存
首先从使用Eureka Client必须引入的@EnableDiscoveryClient
注解提及服务器
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(EnableDiscoveryClientImportSelector.class) public @interface EnableDiscoveryClient { /** * If true, the ServiceRegistry will automatically register the local server. */ boolean autoRegister() default true; }
@EnableDiscoveryClient
注解的做用:session
autoRegister默认值为true,即服务发现客户端默认会自动注册到服务端app
Import导入EnableDiscoveryClientImportSelector.class
,其做用是框架
导入了 spring-cloud-eureka-client.jar!\META-INF\spring.factories 中的 EurekaDiscoveryClientConfiguration
dom
org.springframework.cloud.client.discovery.EnableDiscoveryClient=\ org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
因为autoRegister默认为true,故还会导入AutoServiceRegistrationConfiguration
,即启用自动服务注册的配置,等同于在配置文件中spring.cloud.service-registry.auto-registration.enabled = true
EurekaClientAutoConfiguration
配置类知足启用条件RefreshScopeRefreshedEvent
事件的监听器,知足在使用RefreshScope刷新时能够重建EurekaClient(不是本文重点)eureka.client.healthcheck.enabled=true
的前提下,向Spring容器注册EurekaHealthCheckHandler
用于健康检查(不是本文重点)因此,EurekaDiscoveryClientConfiguration的主要做用是向Spring容器注册EurekaDiscoveryClientConfiguration.Marker.class,使得EurekaClientAutoConfiguration
配置类知足启用条件
EurekaClientAutoConfiguration
配置类中涉及的内容比较多,主要内容:
EurekaClientConfigBean
,这是个对netflix的EurekaClientConfig
客户端配置接口的实现EurekaInstanceConfigBean
,这是个对netflix的EurekaInstanceConfig
实例信息配置接口的实现EurekaRegistration
: Eureka实例的服务注册信息(在开启客户端自动注册时才会注册)EurekaServiceRegistry
: Eureka服务注册器EurekaAutoServiceRegistration
: Eureka服务自动注册器,实现了SmartLifecycle,会在Spring容器的refresh的最后阶段被调用,经过EurekaServiceRegistry
注册器注册EurekaRegistration
信息EurekaClient
和ApplicationInfoManager
,注册时分为两种状况,便是否知足RefreshScope,若是知足,注入的Bean是带有 @Lazy + @RefreshScope 注解的
ApplicationInfoManager
: 管理并初始化当前Instance实例的注册信息,并提供了实例状态监听机制EurekaClient
: netflix的接口类,用于和Eureka Server交互的客户端,而netflix的默认实现是DiscoveryClient
,也是本文分析的重点EurekaHealthIndicator
,为/health端点提供Eureka相关信息,主要有Status当前实例状态和applications服务列表,在从Eureka Server获取服务列表正常的状况下,Status使用Eureka Server上的InstanceRemoteStatus,不正常状况下,代码中有一些判断逻辑public class EurekaClientAutoConfiguration { ...省略 /** * 一、注册EurekaClientConfigBean */ @Bean @ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT) public EurekaClientConfigBean eurekaClientConfigBean() { EurekaClientConfigBean client = new EurekaClientConfigBean(); if ("bootstrap".equals(propertyResolver.getProperty("spring.config.name"))) { // We don't register during bootstrap by default, but there will be another // chance later. client.setRegisterWithEureka(false); } return client; } /** * 二、注册EurekaInstanceConfigBean */ @Bean @ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT) public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils) throws MalformedURLException { PropertyResolver eurekaPropertyResolver = new RelaxedPropertyResolver(this.env, "eureka.instance."); String hostname = eurekaPropertyResolver.getProperty("hostname"); boolean preferIpAddress = Boolean.parseBoolean(eurekaPropertyResolver.getProperty("preferIpAddress")); int nonSecurePort = Integer.valueOf(propertyResolver.getProperty("server.port", propertyResolver.getProperty("port", "8080"))); int managementPort = Integer.valueOf(propertyResolver.getProperty("management.port", String.valueOf(nonSecurePort))); String managementContextPath = propertyResolver.getProperty("management.contextPath", propertyResolver.getProperty("server.contextPath", "/")); EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils); instance.setNonSecurePort(nonSecurePort); instance.setInstanceId(getDefaultInstanceId(propertyResolver)); instance.setPreferIpAddress(preferIpAddress); if (managementPort != nonSecurePort && managementPort != 0) { if (StringUtils.hasText(hostname)) { instance.setHostname(hostname); } String statusPageUrlPath = eurekaPropertyResolver.getProperty("statusPageUrlPath"); String healthCheckUrlPath = eurekaPropertyResolver.getProperty("healthCheckUrlPath"); if (!managementContextPath.endsWith("/")) { managementContextPath = managementContextPath + "/"; } if (StringUtils.hasText(statusPageUrlPath)) { instance.setStatusPageUrlPath(statusPageUrlPath); } if (StringUtils.hasText(healthCheckUrlPath)) { instance.setHealthCheckUrlPath(healthCheckUrlPath); } String scheme = instance.getSecurePortEnabled() ? "https" : "http"; URL base = new URL(scheme, instance.getHostname(), managementPort, managementContextPath); instance.setStatusPageUrl(new URL(base, StringUtils.trimLeadingCharacter(instance.getStatusPageUrlPath(), '/')).toString()); instance.setHealthCheckUrl(new URL(base, StringUtils.trimLeadingCharacter(instance.getHealthCheckUrlPath(), '/')).toString()); } return instance; } /** * 三、注册客户端自动注册相关组件 * EurekaRegistration: Eureka实例的服务注册信息(在开启客户端自动注册时才会注册) * EurekaServiceRegistry: Eureka服务注册器 * EurekaAutoServiceRegistration: Eureka服务自动注册器, * 经过EurekaServiceRegistry注册器注册EurekaRegistration信息 */ @Bean public EurekaServiceRegistry eurekaServiceRegistry() { return new EurekaServiceRegistry(); } @Bean @ConditionalOnBean(AutoServiceRegistrationProperties.class) @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient, CloudEurekaInstanceConfig instanceConfig, ApplicationInfoManager applicationInfoManager) { return EurekaRegistration.builder(instanceConfig) .with(applicationInfoManager) .with(eurekaClient) .with(healthCheckHandler) .build(); } @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); } /** * 四、注册netflix的 EurekaClient 和 ApplicationInfoManager */ // 若是禁用客户端自动注册,在此方法debug打断点会触发服务注册,状态为STARTING @Bean public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) { return new EurekaDiscoveryClient(config, client); } // 普通的EurekaClient配置(不可刷新) @Configuration @ConditionalOnMissingRefreshScope protected static class EurekaClientConfiguration { @Autowired private ApplicationContext context; @Autowired(required = false) private DiscoveryClientOptionalArgs 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); } } // 可刷新的EurekaClient配置类 @Configuration @ConditionalOnRefreshScope //知足@ConditionalOnClass(RefreshScope.class) // @ConditionalOnBean(RefreshAutoConfiguration.class) protected static class RefreshableEurekaClientConfiguration { @Autowired private ApplicationContext context; @Autowired(required = false) private DiscoveryClientOptionalArgs optionalArgs; // 注册CloudEurekaClient,是com.netflix.discovery.EurekaClient接口的实现类 @Bean(destroyMethod = "shutdown") @ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT) @org.springframework.cloud.context.config.annotation.RefreshScope @Lazy public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config, EurekaInstanceConfig instance) { manager.getInfo(); // force initialization return new CloudEurekaClient(manager, config, this.optionalArgs, this.context); } // 注册ApplicationInfoManager @Bean @ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT) @org.springframework.cloud.context.config.annotation.RefreshScope @Lazy public ApplicationInfoManager eurekaApplicationInfoManager(EurekaInstanceConfig config) { InstanceInfo instanceInfo = new InstanceInfoFactory().create(config); return new ApplicationInfoManager(config, instanceInfo); } } /** * 五、注册 EurekaHealthIndicator */ @Configuration @ConditionalOnClass(Endpoint.class) protected static class EurekaHealthIndicatorConfiguration { @Bean @ConditionalOnMissingBean public EurekaHealthIndicator eurekaHealthIndicator(EurekaClient eurekaClient, EurekaInstanceConfig instanceConfig, EurekaClientConfig clientConfig) { return new EurekaHealthIndicator(eurekaClient, instanceConfig, clientConfig); } } }
如上,在知足一系列Conditional条件后,会向Spring容器中注册CloudEurekaClient
,它是com.netflix.discovery.EurekaClient接口的实现类,具体继承实现关系以下
如上图所示,刚刚建立的CloudEurekaClient
是 com.netflix.discovery.DiscoveryClient
的子类,它们都实现了com.netflix.discovery.EurekaClient
接口
EurekaClient
是Netflix对服务发现客户端抽象的接口,包含不少方法,而DiscoveryClient
是其默认实现,也是本文分析的重点,CloudEurekaClient
是spring cloud的实现,根据类上注释,其主要重写了onCacheRefreshed()方法
,这个方法主要是从Eureka Server fetchRegistry()
获取服务列表以后用于以广播方式通知缓存刷新事件的,其实DiscoveryClient
也有onCacheRefreshed()方法
的实现,但因为DiscoveryClient
是Netflix的类,只发送了com.netflix.discovery.EurekaEvent,而CloudEurekaClient
使用Spring的ApplicationEventPublisher
,发送了HeartbeatEvent
注意:
上面说的都是netflix的DiscoveryClient
还有另外一个DiscoveryClient,是
org.springframework.cloud.client.discovery.DiscoveryClient
是Spring对服务发现客户端的抽象
@Inject DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider) { /** * AbstractDiscoveryClientOptionalArgs 是DiscoveryClient的可选参数,可理解为扩展点 * 包含healthCheckHandlerProvider、healthCheckCallbackProvider、eventListeners等 * spring cloud默认实现为MutableDiscoveryClientOptionalArgs,但此处相关成员变量赋值后认为空 */ if (args != null) { this.healthCheckHandlerProvider = args.healthCheckHandlerProvider; this.healthCheckCallbackProvider = args.healthCheckCallbackProvider; this.eventListeners.addAll(args.getEventListeners()); } else { this.healthCheckCallbackProvider = null; this.healthCheckHandlerProvider = 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.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(",")); // 若是 shouldFetchRegistry=true,注册netflix servo监控 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; } // 若是 shouldRegisterWithEureka=true,注册netflix servo监控 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()); // 若是既不要向eureka server注册,又不要获取服务列表,就什么都不用初始化 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(); logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}", initTimestampMs, this.getApplications().size()); return; // no need to setup up an network tasks and we are done } // 【重点】建立各类Executor 和 eurekaTransport、instanceRegionChecker try { // 执行定时任务的定时器,定时线程名为 DiscoveryClient-%d // 在定时器中用于定时执行TimedSupervisorTask监督任务,监督任务会强制超时 和 记录监控数据 scheduler = Executors.newScheduledThreadPool(3, new ThreadFactoryBuilder() .setNameFormat("DiscoveryClient-%d") .setDaemon(true) .build()); // 执行heartbeat心跳任务的执行器,默认最大线程数=2,线程名为:DiscoveryClient-HeartbeatExecutor-%d heartbeatExecutor = new ThreadPoolExecutor( 1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactoryBuilder() .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d") .setDaemon(true) .build() ); // use direct handoff // 执行服务列表缓存刷新的执行器,默认最大线程数=2,线程名为:DiscoveryClient-CacheRefreshExecutor-%d 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(); // 初始化eurekaTransport在服务注册,获取服务列表时的client 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(false)失败,调用BackupRegistry if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) { fetchRegistryFromBackup(); } // 【重点】初始化全部定时任务 initScheduledTasks(); // 添加servo监控 try { Monitors.registerObject(this); } catch (Throwable e) { logger.warn("Cannot register timers", e); } // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance() // to work with DI'd DiscoveryClient DiscoveryManager.getInstance().setDiscoveryClient(this); DiscoveryManager.getInstance().setEurekaClientConfig(config); initTimestampMs = System.currentTimeMillis(); logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}", initTimestampMs, this.getApplications().size()); }
上面的DiscoveryClient构造方法代码比较多,但多数都是一些赋值,本次分析的重点在注释中已经标出,建立了各类Executor 和 eurekaTransport、instanceRegionChecker,以后又调用initScheduledTasks()方法
初始化全部这些定时任务
/** * Initializes all scheduled tasks. */ private void initScheduledTasks() { // 一、若是要从Eureka Server获取服务列表 if (clientConfig.shouldFetchRegistry()) { // registry cache refresh timer // 从eureka服务器获取注册表信息的频率(默认30s) // 同时也是单次获取服务列表的超时时间 int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds(); // 若是缓存刷新超时,下一次执行的delay最大是registryFetchIntervalSeconds的几倍(默认10),默认每次执行是上一次的2倍 int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); /** * 【#### 执行CacheRefreshThread,服务列表缓存刷新任务 ####】 * 执行TimedSupervisorTask监督任务的定时器,具体执行器为cacheRefreshExecutor,任务为CacheRefreshThread */ scheduler.schedule( new TimedSupervisorTask( "cacheRefresh", //监控名 scheduler, cacheRefreshExecutor, registryFetchIntervalSeconds, //指定具体任务的超时时间 TimeUnit.SECONDS, expBackOffBound, new CacheRefreshThread() ), registryFetchIntervalSeconds, TimeUnit.SECONDS); } // 二、若是要注册到Eureka Server if (clientConfig.shouldRegisterWithEureka()) { // 续租的时间间隔(默认30s) int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs(); // 若是心跳任务超时,下一次执行的delay最大是renewalIntervalInSecs的几倍(默认10),默认每次执行是上一次的2倍 int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound(); logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs); // Heartbeat timer /** * 【#### 执行HeartbeatThread,发送心跳数据 ####】 * 执行TimedSupervisorTask监督任务的定时器,具体执行器为heartbeatExecutor,任务为HeartbeatThread */ scheduler.schedule( new TimedSupervisorTask( "heartbeat", scheduler, heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread() ), renewalIntervalInSecs, TimeUnit.SECONDS); // InstanceInfo replicator /** * 【#### InstanceInfo复制器 ####】 * 启动后台定时任务scheduler,线程名为 DiscoveryClient-InstanceInfoReplicator-%d * 默认每30s执行一次定时任务,查看Instance信息(DataCenterInfo、LeaseInfo、InstanceStatus)是否有变化 * 若是有变化,执行 discoveryClient.register() */ instanceInfoReplicator = new InstanceInfoReplicator( this, //当前DiscoveryClient instanceInfo, //当前实例信息 clientConfig.getInstanceInfoReplicationIntervalSeconds(),//InstanceInfo的复制间隔(默认30s) 2); // burstSize /** * 【StatusChangeListener 状态改变监听器】 */ statusChangeListener = new ApplicationInfoManager.StatusChangeListener() { @Override public String getId() { return "statusChangeListener"; } @Override public void notify(StatusChangeEvent statusChangeEvent) { if (InstanceStatus.DOWN == statusChangeEvent.getStatus() || InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) { // log at warn level if DOWN was involved logger.warn("Saw local status change event {}", statusChangeEvent); } else { logger.info("Saw local status change event {}", statusChangeEvent); } //使用InstanceInfo复制器 scheduler.submit()一个Runnable任务 //后台立刻执行 discoveryClient.register() instanceInfoReplicator.onDemandUpdate(); } }; /** * 是否关注Instance状态变化,使用后台线程将状态同步到eureka server(默认true) * 调用 ApplicationInfoManager#setInstanceStatus(status) 会触发 * 将 StatusChangeListener 注册到 ApplicationInfoManager */ if (clientConfig.shouldOnDemandUpdateStatusChange()) { applicationInfoManager.registerStatusChangeListener(statusChangeListener); } // 启动InstanceInfo复制器 instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds()); } // 当前服务实例不注册到Eureka Server else { logger.info("Not registering with Eureka server per configuration"); } }
总的来讲initScheduledTasks()
作了如下几件事:
CacheRefreshThread
,即从Eureka Server获取服务列表,也刷新客户端缓存HeartbeatThread
,即客户端向Eureka Server发送心跳discoveryClient.register()
,将实例信息同步到Server端由建立DiscoveryClient的过程可知,建立了不少定时执行线程,如定时从Server端刷新服务列表的CacheRefreshThread,定时报心跳续约的HeartbeatThread,还有用于更新并复制本地实例状态到Server端的InstanceInfo复制器定时线程,而正是InstanceInfoReplicator#run()
中的discoveryClient.register()
发起了注册
那么怎么能够触发注册行为呢?
// InstanceInfoReplicator#run() public void run() { try { /** * 刷新 InstanceInfo * 一、刷新 DataCenterInfo * 二、刷新 LeaseInfo 租约信息 * 三、根据HealthCheckHandler获取InstanceStatus,并更新,若是状态发生变化会触发全部StatusChangeListener */ discoveryClient.refreshInstanceInfo(); // 若是isInstanceInfoDirty=true,返回dirtyTimestamp,不然是null Long dirtyTimestamp = instanceInfo.isDirtyWithTime(); if (dirtyTimestamp != null) { discoveryClient.register(); //发起注册 instanceInfo.unsetIsDirty(dirtyTimestamp); //isInstanceInfoDirty置为false } } catch (Throwable t) { logger.warn("There was a problem with the instance info replicator", t); } finally { // 继续下次任务 Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS); scheduledPeriodicRef.set(next); } }
如上,先刷新InstanceInfo,刷新后若是发现有脏数据,即实例发生了变动,还未同步给Server的数据,就发起注册
那么在Eureka Client启动的这种场景下,怎样会触发有脏数据下的注册?
onDemandUpdate()
按需更新方法,一旦调用,立刻会submit()任务,其中会cancel自动更新任务,立刻执行InstanceInfoReplicator#run()
InstanceInfoReplicator复制器在启动建立DiscoveryClient时被建立并start()启动
// InstanceInfoReplicator#start() public void start(int initialDelayMs) { // 默认40s if (started.compareAndSet(false, true)) { instanceInfo.setIsDirty(); // for initial register 初始化时会将instanceInfo设置为dirty Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS); scheduledPeriodicRef.set(next); } }
因此当自动更新启动时会设置InstanceInfo为脏数据,由于要触发第一次向Server同步,那么在40s后会调用InstanceInfoReplicator#run()
,假设InstanceInfo并无其它变动,那么也会发起discoveryClient.register()
注意:
正常状况下是不会由延迟40s的第一次执行定时任务发起注册,而是下面的onDemandUpdate() 主动按需更新发起注册
若是设置@EnableDiscoveryClient(autoRegister = false) 或者 spring.cloud.service-registry.auto-registration.enabled=false,即放弃自动注册,并在EurekaClientAutoConfiguration的以下方法打断点
@Bean public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) { return new EurekaDiscoveryClient(config, client); }会在断点生效时触发EurekaClient的实例化,而此EurekaClient就是一个DiscoveryClient,会启动InstanceInfoReplicator自动定时更新线程,但因为new InstanceInfoFactory().create(config)时本地实例状态为STARTING,因此注册到Server端的状态也是STARTING
目前只有在ApplicationInfoManager#setInstanceStatus()更新实例状态,且实例状态真的发生变动,触发StatusChangeListener状态变动监听器时,会调用onDemandUpdate
立刻submit任务执行InstanceInfoReplicator#run()
,再发起注册
因为Spring Cloud默认是启用服务自动注册AutoServiceRegistration的,因此在EurekaClientAutoConfiguration
自动配置时会注册服务自动注册相关组件(EurekaRegistration、EurekaServiceRegistry、EurekaAutoServiceRegistration),其中EurekaAutoServiceRegistration
实现了Spring的SmartLifecycle接口,会在Spring容器refresh要完毕时触发生命周期方法start(),其中会使用EurekaServiceRegistry
服务注册器注册EurekaRegistration
这个本地实例信息
// EurekaServiceRegistry#register() public void register(EurekaRegistration reg) { maybeInitializeClient(reg); if (log.isInfoEnabled()) { log.info("Registering application " + reg.getInstanceConfig().getAppname() + " with eureka with status " + reg.getInstanceConfig().getInitialStatus()); } // 设置初始化状态 reg.getApplicationInfoManager() .setInstanceStatus(reg.getInstanceConfig().getInitialStatus()); if (reg.getHealthCheckHandler() != null) { reg.getEurekaClient().registerHealthCheck(reg.getHealthCheckHandler()); } }
主要是设置初始化状态步骤,而EurekaInstanceConfigBean
本地实例信息的initialStatus初始化状态为 InstanceStatus.UP,因此状态与new InstanceInfo()时的STARTING不一样,发生了状态变动,触发在建立DiscoveryClient时设置的StatusChangeListener
...省略 statusChangeListener = new ApplicationInfoManager.StatusChangeListener() { @Override public String getId() { return "statusChangeListener"; } @Override public void notify(StatusChangeEvent statusChangeEvent) { if (InstanceStatus.DOWN == statusChangeEvent.getStatus() || InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) { // log at warn level if DOWN was involved logger.warn("Saw local status change event {}", statusChangeEvent); } else { logger.info("Saw local status change event {}", statusChangeEvent); } instanceInfoReplicator.onDemandUpdate(); } }; if (clientConfig.shouldOnDemandUpdateStatusChange()) { applicationInfoManager.registerStatusChangeListener(statusChangeListener); } ...省略
其中会调用 InstanceInfoReplicator.onDemandUpdate() 实例信息复制器作按需更新,立刻将UP状态更新/注册到Server端
因此,以我判断,Eureka Client启动时的自动注册大多数应该是Spring Cloud的服务自动注册机制,在Spring容器基本启动完毕时,触发服务自动注册操做,其中会使用ApplicationInfoManager更新实例状态为初始状态UP,一旦实例状态变动会被立刻监听到,执行复制器的InstanceInfoReplicator.onDemandUpdate()按需更新,立刻执行一次discoveryClient.register()操做
因此,下面就是分析 discoveryClient.register() 是怎么注册服务的
// DiscoveryClient#register() boolean register() throws Throwable { logger.info(PREFIX + appPathIdentifier + ": registering service..."); EurekaHttpResponse<Void> httpResponse; try { httpResponse = eurekaTransport.registrationClient.register(instanceInfo); } catch (Exception e) { logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e); throw e; } if (logger.isInfoEnabled()) { logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode()); } return httpResponse.getStatusCode() == 204; }
如上,注册方法使用eurekaTransport
的注册客户端registrationClient
调用了register(instanceinfo)方法
EurekaTransport
是DiscoveryClient
的内部类,其中包含
EurekaHttpClient
和 EurekaHttpClientFactory
的实现类EurekaHttpClient
和 EurekaHttpClientFactory
的实现类那么EurekaTransport
的相关组件,尤为是registrationClient
注册客户端是如何初始化的呢?
初始化是在DiscoveryClient
的构造方法中
eurekaTransport = new EurekaTransport(); scheduleServerEndpointTask(eurekaTransport, args);
主要是scheduleServerEndpointTask()
方法
private void scheduleServerEndpointTask(EurekaTransport eurekaTransport, AbstractDiscoveryClientOptionalArgs args) { Collection<?> additionalFilters = args == null ? Collections.emptyList() : args.additionalFilters; EurekaJerseyClient providedJerseyClient = args == null ? null : args.eurekaJerseyClient; TransportClientFactories argsTransportClientFactories = null; if (args != null && args.getTransportClientFactories() != null) { argsTransportClientFactories = args.getTransportClientFactories(); } // Ignore the raw types warnings since the client filter interface changed between jersey 1/2 @SuppressWarnings("rawtypes") TransportClientFactories transportClientFactories = argsTransportClientFactories == null ? new Jersey1TransportClientFactories() : argsTransportClientFactories; // If the transport factory was not supplied with args, assume they are using jersey 1 for passivity // 一、参数中是否提供了transportClientFactory的实现,没有就使用Jersey1TransportClientFactories eurekaTransport.transportClientFactory = providedJerseyClient == null ? transportClientFactories.newTransportClientFactory(clientConfig, additionalFilters, applicationInfoManager.getInfo()) : transportClientFactories.newTransportClientFactory(additionalFilters, providedJerseyClient); ApplicationsResolver.ApplicationsSource applicationsSource = new ApplicationsResolver.ApplicationsSource() { @Override public Applications getApplications(int stalenessThreshold, TimeUnit timeUnit) { long thresholdInMs = TimeUnit.MILLISECONDS.convert(stalenessThreshold, timeUnit); long delay = getLastSuccessfulRegistryFetchTimePeriod(); if (delay > thresholdInMs) { logger.info("Local registry is too stale for local lookup. Threshold:{}, actual:{}", thresholdInMs, delay); return null; } else { return localRegionApps.get(); } } }; eurekaTransport.bootstrapResolver = EurekaHttpClients.newBootstrapResolver( clientConfig, transportConfig, eurekaTransport.transportClientFactory, applicationInfoManager.getInfo(), applicationsSource ); /** * 是否要想Eureka Server注册 * 二、建立RegistrationClient用于注册的客户端及工厂,并设置到eurekaTransport */ if (clientConfig.shouldRegisterWithEureka()) { EurekaHttpClientFactory newRegistrationClientFactory = null; EurekaHttpClient newRegistrationClient = null; try { newRegistrationClientFactory = EurekaHttpClients.registrationClientFactory( eurekaTransport.bootstrapResolver, eurekaTransport.transportClientFactory, transportConfig ); newRegistrationClient = newRegistrationClientFactory.newClient(); } catch (Exception e) { logger.warn("Transport initialization failure", e); } eurekaTransport.registrationClientFactory = newRegistrationClientFactory; eurekaTransport.registrationClient = newRegistrationClient; } // /** * 是否要从Server端获取服务列表 * 三、建立QueryClient用于查询服务列表的客户端及工厂,并设置到eurekaTransport */ // new method (resolve from primary servers for read) // Configure new transport layer (candidate for injecting in the future) if (clientConfig.shouldFetchRegistry()) { EurekaHttpClientFactory newQueryClientFactory = null; EurekaHttpClient newQueryClient = null; try { newQueryClientFactory = EurekaHttpClients.queryClientFactory( eurekaTransport.bootstrapResolver, eurekaTransport.transportClientFactory, clientConfig, transportConfig, applicationInfoManager.getInfo(), applicationsSource ); newQueryClient = newQueryClientFactory.newClient(); } catch (Exception e) { logger.warn("Transport initialization failure", e); } eurekaTransport.queryClientFactory = newQueryClientFactory; eurekaTransport.queryClient = newQueryClient; } }
因此,下面就是逐层深刻分析RegistrationClient用于注册的客户端及工厂是如何建立的?
因为RegistrationClient实际上是一种EurekaHttpClient
,而EurekaHttpClient
是接口,其实现类不少
查看源码发现,Netflix采用的是 Factory工厂 + 代理 的模式,从最外层建立的EurekaHttpClient工厂包含一个成员变量是另外一个代理的EurekaHttpClient工厂,每一个工厂生成的EurekaHttpClient功能不同,在从外层执行一个操做时,最外层的工厂执行其相关功能后,使用代理的工厂新建EurekaHttpClient实例,再调用其相同的方法,也实现这个EurekaHttpClient的相关功能,就这样逐层深刻,各司其职后,最后使用Jersey发送POST请求到Eureka Server发起注册,而这些EurekaHttpClient都是在com.netflix.discovery.shared.transport.decorator
EurekaHttpClient的包装类的包下的,由外到内大体是:
// SessionedEurekaHttpClient#execute() @Override protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) { long now = System.currentTimeMillis(); long delay = now - lastReconnectTimeStamp; // 若是上次重连时间到如今已经超过了currentSessionDurationMs,关闭当前EurekaHttpClient if (delay >= currentSessionDurationMs) { logger.debug("Ending a session and starting anew"); lastReconnectTimeStamp = now; currentSessionDurationMs = randomizeSessionDuration(sessionDurationMs); TransportUtils.shutdown(eurekaHttpClientRef.getAndSet(null)); } // 若是EurekaHttpClient为空,clientFactory.newClient()重建 EurekaHttpClient eurekaHttpClient = eurekaHttpClientRef.get(); if (eurekaHttpClient == null) { eurekaHttpClient = TransportUtils.getOrSetAnotherClient(eurekaHttpClientRef, clientFactory.newClient()); } // 继续执行后续 return requestExecutor.execute(eurekaHttpClient); }
// RetryableEurekaHttpClient#execute() @Override protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) { List<EurekaEndpoint> candidateHosts = null; int endpointIdx = 0; // 最多重试numberOfRetries(默认:3) for (int retry = 0; retry < numberOfRetries; retry++) { EurekaHttpClient currentHttpClient = delegate.get();//从AtomicReference<EurekaHttpClient>获取当前EurekaHttpClient EurekaEndpoint currentEndpoint = null; if (currentHttpClient == null) { if (candidateHosts == null) { candidateHosts = getHostCandidates(); //返回候选集合 排除 已经失败隔离的Host集合 if (candidateHosts.isEmpty()) { throw new TransportException("There is no known eureka server; cluster server list is empty"); } } if (endpointIdx >= candidateHosts.size()) { throw new TransportException("Cannot execute request on any known server"); } // 根据当前的下标获取Endpoint,并新建 JerseyClient currentEndpoint = candidateHosts.get(endpointIdx++); currentHttpClient = clientFactory.newClient(currentEndpoint); } try { // 继续后续执行 EurekaHttpResponse<R> response = requestExecutor.execute(currentHttpClient); // 若是根据当前操做类型 和 返回状态码,知足状态计算器,记录currentHttpClient可用,下次继续使用 // 返回状态码是:200、300、302,或者Register、SendHeartBeat状况下是404 if (serverStatusEvaluator.accept(response.getStatusCode(), requestExecutor.getRequestType())) { delegate.set(currentHttpClient); if (retry > 0) { logger.info("Request execution succeeded on retry #{}", retry); } return response; } logger.warn("Request execution failure with status code {}; retrying on another server if available", response.getStatusCode()); } catch (Exception e) { logger.warn("Request execution failed with message: {}", e.getMessage()); // just log message as the underlying client should log the stacktrace } // Connection error or 5xx from the server that must be retried on another server // 请求失败 或 报5xx错误,将delegate清空,重试另外一个Server,并将当前Endpoint放到隔离集合 delegate.compareAndSet(currentHttpClient, null); if (currentEndpoint != null) { quarantineSet.add(currentEndpoint); } } // 屡次重试后仍没法成功返回结果,上抛异常 throw new TransportException("Retry limit reached; giving up on completing the request"); } //########## RetryableEurekaHttpClient#getHostCandidates() // 返回 全部候选的Host节点数据 与 隔离集合 的数据差集 private List<EurekaEndpoint> getHostCandidates() { List<EurekaEndpoint> candidateHosts = clusterResolver.getClusterEndpoints(); //全部候选节点数据 quarantineSet.retainAll(candidateHosts); //确保quarantineSet隔离集合中的数据都在candidateHosts中 //当candidateHosts发生变化时也能及时清理quarantineSet隔离集合 // If enough hosts are bad, we have no choice but start over again // 默认:0.66百分比 int threshold = (int) (candidateHosts.size() * transportConfig.getRetryableClientQuarantineRefreshPercentage()); // 隔离集合为空 if (quarantineSet.isEmpty()) { // no-op } // 隔离数据已经大于阀值,不得已要从新开始,清空隔离集合 else if (quarantineSet.size() >= threshold) { logger.debug("Clearing quarantined list of size {}", quarantineSet.size()); quarantineSet.clear(); } // 隔离集合不为空,也不大于阀值,排除隔离集合中的Endpoint后返回 else { List<EurekaEndpoint> remainingHosts = new ArrayList<>(candidateHosts.size()); for (EurekaEndpoint endpoint : candidateHosts) { if (!quarantineSet.contains(endpoint)) { remainingHosts.add(endpoint); } } candidateHosts = remainingHosts; } return candidateHosts; } //########## ServerStatusEvaluators#LEGACY_EVALUATOR // Eureka Server返回状态的计算器,计算不一样场景下的不一样状态码是否表明成功 private static final ServerStatusEvaluator LEGACY_EVALUATOR = new ServerStatusEvaluator() { @Override public boolean accept(int statusCode, RequestType requestType) { if (statusCode >= 200 && statusCode < 300 || statusCode == 302) { return true; } else if (requestType == RequestType.Register && statusCode == 404) { return true; } else if (requestType == RequestType.SendHeartBeat && statusCode == 404) { return true; } else if (requestType == RequestType.Cancel) { // cancel is best effort return true; } else if (requestType == RequestType.GetDelta && (statusCode == 403 || statusCode == 404)) { return true; } return false; } };
//########## RedirectingEurekaHttpClient#executeOnNewServer // Server端返回302重定向时,客户端shutdown原EurekaHttpClient,根据response header中的Location新建EurekaHttpClient private <R> EurekaHttpResponse<R> executeOnNewServer(RequestExecutor<R> requestExecutor, AtomicReference<EurekaHttpClient> currentHttpClientRef) { URI targetUrl = null; // 最多重定向默认10次 for (int followRedirectCount = 0; followRedirectCount < MAX_FOLLOWED_REDIRECTS; followRedirectCount++) { EurekaHttpResponse<R> httpResponse = requestExecutor.execute(currentHttpClientRef.get()); // 若是返回的不是302重定向,返回response if (httpResponse.getStatusCode() != 302) { if (followRedirectCount == 0) { logger.debug("Pinning to endpoint {}", targetUrl); } else { logger.info("Pinning to endpoint {}, after {} redirect(s)", targetUrl, followRedirectCount); } return httpResponse; } // 从response中获取Location,用于重建EurekaHttpClient targetUrl = getRedirectBaseUri(httpResponse.getLocation()); if (targetUrl == null) { throw new TransportException("Invalid redirect URL " + httpResponse.getLocation()); } currentHttpClientRef.getAndSet(null).shutdown(); currentHttpClientRef.set(factory.newClient(new DefaultEndpoint(targetUrl.toString()))); } String message = "Follow redirect limit crossed for URI " + serviceEndpoint.getServiceUrl(); logger.warn(message); throw new TransportException(message); }
// MetricsCollectingEurekaHttpClient#execute() protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) { EurekaHttpClientRequestMetrics requestMetrics = metricsByRequestType.get(requestExecutor.getRequestType()); Stopwatch stopwatch = requestMetrics.latencyTimer.start(); //统计执行延时 try { EurekaHttpResponse<R> httpResponse = requestExecutor.execute(delegate); requestMetrics.countersByStatus.get(mappedStatus(httpResponse)).increment(); //按状态统计 return httpResponse; } catch (Exception e) { requestMetrics.connectionErrors.increment(); //统计错误 exceptionsMetric.count(e); //按异常名统计 throw e; } finally { stopwatch.stop(); } }
public abstract class AbstractJerseyEurekaHttpClient implements EurekaHttpClient { protected final Client jerseyClient; //真正处理请求的Jersery客户端 protected final String serviceUrl; //链接的Server端地址 protected AbstractJerseyEurekaHttpClient(Client jerseyClient, String serviceUrl) { this.jerseyClient = jerseyClient; this.serviceUrl = serviceUrl; logger.debug("Created client for url: {}", serviceUrl); } /** * 注册方法 */ @Override public EurekaHttpResponse<Void> register(InstanceInfo info) { String urlPath = "apps/" + info.getAppName(); //请求Eureka Server的【/apps/应用名】接口地址 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); //实例InstanceInfo数据,经过Post请求body发过去 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(); } } } ...省略
通过上面的步骤,客户端已经能够经过Jersery发送Http请求给Eureka Server端注册,具体请求以下:
POST /eureka/apps/应用名 HTTP/1.1
Accept-Encoding: gzip
Content-Type: application/json
Accept: application/json
DiscoveryIdentity-Name: DefaultClient
DiscoveryIdentity-Version: 1.4
DiscoveryIdentity-Id: 192.168.70.132
Transfer-Encoding: chunked
Host: localhost:8001
Connection: Keep-Alive
User-Agent: Java-EurekaClient/v1.6.2
1a0
{"instance":{
"instanceId":"192.168.70.132:应用名:10001",
"hostName":"192.168.70.132",
"app":"应用名",
"ipAddr":"192.168.70.132",
"status":"UP",
"overriddenstatus":"UNKNOWN",
"port": { "\(":10001, "@enabled" : "true" }, "securePort": { "\)":443, "@enabled" : "false"},
"countryId":1,
"dataCenterInfo":{"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name":"MyOwn"
}
大致来讲,Eureka Client的注册是由Spring Cloud的
AutoServiceRegistration自动注册
发起,在设置应用实例Instance初始状态为UP时,触发了InstanceInfoReplicator#onDemandUpdate()按需更新
方法,将实例Instance信息经过DiscoveryClient
注册到Eureka Server,期间通过了一些EurekaHttpClient的装饰类,实现了诸如按期重连、失败重试、注册重定向、统计收集Metrics信息等功能,最后由JerseryClient
发送POST请求调用Eureka Server的【/eureka/apps/应用名】端点,请求体携带InstanceInfo实例信息,完成注册
EurekaAutoServiceRegistration#start(): 实现Spring的SmartLifecycle,在Spring容器refresh()最后一步finishRefresh()会调用生命周期的start()方法
EurekaServiceRegistry#register(EurekaRegistration): 使用服务注册器注册服务信息
ApplicationInfoManager#setInstanceStatus(初始状态): 应用实例信息管理器更新初始状态为 UP
StatusChangeListener: 触发实例状态监听(此Listener是在DiscoveryClient#initScheduledTasks()方法中设置的)
InstanceInfoReplicator.onDemandUpdate(): 实例状态复制器执行按需状态更新
DiscoveryClient#register(): DiscoveryClient发起注册实例信息
EurekaHttpClientDecorator#execute(): 执行EurekaHttpClient的装饰类,实现其各自功能
SessionedEurekaHttpClient: 定时重连
RetryableEurekaHttpClient: 候选范围内失败重试
RedirectingEurekaHttpClient: 按Eureka Server端要求重定向到新Server
MetricsCollectingEurekaHttpClient: 统计收集执行状况