概述
從服務發現注解
@EnableDiscoveryClient入手,剖析整個服務發現與注冊過程
一,spring-cloud-common包
針對服務發現,本jar包定義了
DiscoveryClient 接口
public interface DiscoveryClient { /** * A human readable description of the implementation, used in HealthIndicator * @return the description */ String description(); /** * @deprecated use the {@link org.springframework.cloud.client.serviceregistry.Registration} bean instead * * @return ServiceInstance with information used to register the local service */ @Deprecated ServiceInstance getLocalServiceInstance(); /** * Get all ServiceInstances associated with a particular serviceId * @param serviceId the serviceId to query * @return a List of ServiceInstance */ List<ServiceInstance> getInstances(String serviceId); /** * @return all known service ids */ List<String> getServices(); }
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; }
@Import注解:支持導入普通的java類,並將其聲明成一個bean
現在看EnableDiscoveryClientImportSelector類實現
@Order(Ordered.LOWEST_PRECEDENCE - 100) //指定實例化bean的順序 public class EnableDiscoveryClientImportSelector extends SpringFactoryImportSelector<EnableDiscoveryClient> { @Override public String[] selectImports(AnnotationMetadata metadata) { String[] imports = super.selectImports(metadata); AnnotationAttributes attributes = AnnotationAttributes.fromMap( metadata.getAnnotationAttributes(getAnnotationClass().getName(), true)); boolean autoRegister = attributes.getBoolean("autoRegister"); if (autoRegister) { List<String> importsList = new ArrayList<>(Arrays.asList(imports)); importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration"); imports = importsList.toArray(new String[0]); } return imports; }
EnableDiscoveryClientImportSelector類繼承SpringFactoryImportSelector類,該類是重點,如下:
public abstract class SpringFactoryImportSelector<T> implements DeferredImportSelector, BeanClassLoaderAware, EnvironmentAware { private ClassLoader beanClassLoader; private Class<T> annotationClass; private Environment environment; private final Log log = LogFactory.getLog(SpringFactoryImportSelector.class); @SuppressWarnings("unchecked") protected SpringFactoryImportSelector() { this.annotationClass = (Class<T>) GenericTypeResolver .resolveTypeArgument(this.getClass(), SpringFactoryImportSelector.class); } @Override public String[] selectImports(AnnotationMetadata metadata) { if (!isEnabled()) { return new String[0]; } AnnotationAttributes attributes = AnnotationAttributes.fromMap( metadata.getAnnotationAttributes(this.annotationClass.getName(), true)); Assert.notNull(attributes, "No " + getSimpleName() + " attributes found. Is " + metadata.getClassName() + " annotated with @" + getSimpleName() + "?"); // Find all possible auto configuration classes, filtering duplicates 重點在這個地方 List<String> factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader .loadFactoryNames(this.annotationClass, this.beanClassLoader))); if (factories.isEmpty() && !hasDefaultFactory()) { throw new IllegalStateException("Annotation @" + getSimpleName() + " found, but there are no implementations. Did you forget to include a starter?"); } if (factories.size() > 1) { // there should only ever be one DiscoveryClient, but there might be more than // one factory log.warn("More than one implementation " + "of @" + getSimpleName() + " (now relying on @Conditionals to pick one): " + factories); } return factories.toArray(new String[factories.size()]); }
SpringFactoriesLoader調用loadFactoryNames其實加載META-INF/spring.factories下的class。
spring-cloud-netflix-eureka-client\src\main\resources\META-INF\spring.factories中配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\ org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\ org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\ org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration org.springframework.cloud.client.discovery.EnableDiscoveryClient=\ org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
該類調用了DeferredImportSelector接口,即ImportSelector接口
,繼承了selectImport方法,關於ImportSelector的具體作用,參考下面鏈接
對於selectImport的調用,是在spring context 包中的ConfigurationClassParser進行解析
先流程走到EurekaClientAutoConfiguration類與EurekaDiscoveryClientConfiguration類
EurekaClientAutoConfiguration詳解
源碼如下
@Configuration @EnableConfigurationProperties @ConditionalOnClass(EurekaClientConfig.class) // 該注解的參數對應的類必須存在,否則不解析該注解修飾的配置類;
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true) //屬性必須存在,才解析該類
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class }) @AutoConfigureAfter(name = "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration") public class EurekaClientAutoConfiguration { @Value("${server.port:${SERVER_PORT:${PORT:8080}}}") int nonSecurePort; @Value("${management.port:${MANAGEMENT_PORT:${server.port:${SERVER_PORT:${PORT:8080}}}}}") int managementPort; @Value("${eureka.instance.hostname:${EUREKA_INSTANCE_HOSTNAME:}}") String hostname; @Autowired ConfigurableEnvironment env; //環境上下文,即配置文件相關的內容 @Bean public HasFeatures eurekaFeature() { return HasFeatures.namedFeature("Eureka Client", EurekaClient.class); } // EurekaClientConfigBean: 服務注冊類配置,如指定注冊中心,以及定義了各種超時時間,比如下線超時時間,注冊超時時間等
// 這些注冊類配置,以eureka.client為前綴 @Bean @ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT) public EurekaClientConfigBean eurekaClientConfigBean() { EurekaClientConfigBean client = new EurekaClientConfigBean(); if ("bootstrap".equals(this.env.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: 服務實例類配置 instance 實例配置 ,包括appname,instanceId,主機名等
// 1:元數據:實例元數據的配置,比如服務名稱,實例名稱,實例ip,端口,安全通信端口,非安全通信端口,心跳間隔等 此類信息會包裝成InstanceInfo 然后傳遞到服務中心
// 以eureka.instance配置為前綴
@Bean @ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT) public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils) { EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils); instance.setNonSecurePort(this.nonSecurePort); instance.setInstanceId(getDefaultInstanceId(this.env)); if (this.managementPort != this.nonSecurePort && this.managementPort != 0) { if (StringUtils.hasText(this.hostname)) { instance.setHostname(this.hostname); } RelaxedPropertyResolver relaxedPropertyResolver = new RelaxedPropertyResolver(env, "eureka.instance."); String statusPageUrlPath = relaxedPropertyResolver.getProperty("statusPageUrlPath"); String healthCheckUrlPath = relaxedPropertyResolver.getProperty("healthCheckUrlPath"); if (StringUtils.hasText(statusPageUrlPath)) { instance.setStatusPageUrlPath(statusPageUrlPath); } if (StringUtils.hasText(healthCheckUrlPath)) { instance.setHealthCheckUrlPath(healthCheckUrlPath); } String scheme = instance.getSecurePortEnabled() ? "https" : "http"; instance.setStatusPageUrl(scheme + "://" + instance.getHostname() + ":" + this.managementPort + instance.getStatusPageUrlPath()); instance.setHealthCheckUrl(scheme + "://" + instance.getHostname() + ":" + this.managementPort + instance.getHealthCheckUrlPath()); } return instance; } @Bean public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) { return new EurekaDiscoveryClient(config, client); //注冊服務發現客戶端類成bean } @Bean @ConditionalOnMissingBean(value = DiscoveryClientOptionalArgs.class, search = SearchStrategy.CURRENT) public MutableDiscoveryClientOptionalArgs discoveryClientOptionalArgs() { return new MutableDiscoveryClientOptionalArgs(); }
spring-cloud-netflix-eureka-client的EurekaDiscoveryClilent類只是對我們注解用到的DiscoveryClient接口的實現, 該類中的EurekaClient接口變量才是真正對服務發現實現,即Eureka-client中的EurekaClient接口實現類DiscoveryClient才是真正對發現服務進行了實現
DiscoveryClient類的實現內容:
1:向Eureka server 注冊服務實例
2:向Eureka server 服務租約
3:當服務關閉期間,向Eureka server 取消租約
4:查詢Eureka server 中的服務實例列表
Eureka client 還需要配置一個Eureka server 的url列表
DiscoveryClient類服務注冊關鍵實現:
private void initScheduledTasks() { int renewalIntervalInSecs; int expBackOffBound; if(this.clientConfig.shouldFetchRegistry()) { renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds(); expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS); } if(this.clientConfig.shouldRegisterWithEureka()) { renewalIntervalInSecs = this.instanceInfo.getLeaseInfo().getRenewalIntervalInSecs(); expBackOffBound = this.clientConfig.getHeartbeatExecutorExponentialBackOffBound(); logger.info("Starting heartbeat executor: renew interval is: " + renewalIntervalInSecs);
this.scheduler.schedule(new TimedSupervisorTask("heartbeat", this.scheduler, this.heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread(null)), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
/* 下面這行啟動一個定時任務,這個定時任務的執行在后面,作用是向服務端注冊 */
this.instanceInfoReplicator = new InstanceInfoReplicator(this, this.instanceInfo, this.clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2); this.statusChangeListener = new StatusChangeListener() { public String getId() { return "statusChangeListener"; } public void notify(StatusChangeEvent statusChangeEvent) { if(InstanceStatus.DOWN != statusChangeEvent.getStatus() && InstanceStatus.DOWN != statusChangeEvent.getPreviousStatus()) { DiscoveryClient.logger.info("Saw local status change event {}", statusChangeEvent); } else { DiscoveryClient.logger.warn("Saw local status change event {}", statusChangeEvent); } DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate(); } }; if(this.clientConfig.shouldOnDemandUpdateStatusChange()) { this.applicationInfoManager.registerStatusChangeListener(this.statusChangeListener); } this.instanceInfoReplicator.start(this.clientConfig.getInitialInstanceInfoReplicationIntervalSeconds()); } else { logger.info("Not registering with Eureka server per configuration"); } }
查看InstanceInfoReplicator類的實現,這個類是個線程類,查看里面的run方法
this.discoveryClient.refreshInstanceInfo(); Long next = this.instanceInfo.isDirtyWithTime(); if(next != null) { this.discoveryClient.register(); this.instanceInfo.unsetIsDirty(next.longValue()); var6 = false; } else { var6 = false; }
這個discoveryclient.register調用了http請求,實現了注冊,傳入參數是com.netflix.appinfo.instanceInfo對象