概述
从服务发现注解
@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-clientsrcmain esourcesMETA-INFspring.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对象