spring cloud服务发现和注册源码分析

概述

Spring Cloud中默认的服务发现采用的netflix的eureka,本篇文章就是阅读Spring cloud中通过eureka做服务发现时的笔记。

顺藤摸瓜

读取Spring各种框架的时候,很多时候不知道从什么地方开始,因为Spring中很多模块的开启就是通过一行注解,例如@EnableXXX。而Spring Cloud中的配置服务则是通过@EnableDiscoveryClient,(其实@EnableEurekaClient就是@EnableDiscoveryClient)

/**
 * Annotation to enable a DiscoveryClient implementation.
 * @author Spencer Gibb
 */
@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(EnableDiscoveryClientImportSelector.class),然后看一下EnableDiscoveryClientImportSelector这个类

public class EnableDiscoveryClientImportSelector
		extends SpringFactoryImportSelector<EnableDiscoveryClient> {

	@Override
	public String[] selectImports(AnnotationMetadata metadata) {
	...
		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;
	}


}

一看代码第一反应就是引入了org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration这个类,再看一下这个类

@Configuration
@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)
public class AutoServiceRegistrationConfiguration {
}

什么都没有,怎么回事。其实机关就是在SpringFactoryImportSelector<EnableDiscoveryClient>这个类。其实他会把类全命名作为自动配置类的key,了解Spring Boot自动加载过程的就应该知道在某个jar包下META-INFO/spring.factories。

org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration

这样,Spring Cloud启动的时候初始化Eureka就是在EurekaDiscoveryClientConfiguration这个类中。仔细一看,也不对啊,这个类只注册了一个Marker类,而且这个类是个空类。再全局搜索一下哪里引用了这个Marker类,终于发现真正初始化Eureka的类:EurekaClientAutoConfiguration

@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
		CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration")
public class EurekaClientAutoConfiguration

追根溯源

既然找到了EurekaClientAutoConfiguration这个配置类,那么肯定有初始化和NetFlix相关类,仔细一看,就是

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

image 终于发现和NetFlix的链接的地方,CloudEurekaClient继承的DiscoveryClient其实就是NetFlix的服务发现类,这样,就可以好好分析是怎么初始化的。

直击要害

CloudEurekaClient在初始化的时候主要是调用父类DiscoveryClient的构造函数,会做很多事,其中注册服务和发现服务是通过调度任务来完成,调度任务的初始化是在initScheduledTasks这个而方法中,其中服务发现的代码

ate void initScheduledTasks() {
    if (clientConfig.shouldFetchRegistry()) {
        // registry cache refresh timer
        int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
        int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
        scheduler.schedule(
            new TimedSupervisorTask(
                    "cacheRefresh",
                    scheduler,
                    cacheRefreshExecutor,
                    registryFetchIntervalSeconds,
                    TimeUnit.SECONDS,
                    expBackOffBound,
                    new CacheRefreshThread()
            ),
            registryFetchIntervalSeconds, TimeUnit.SECONDS);
    }

首先判断是否需要进行服务发现,然后通过一个定时任务去刷新缓存信息。TimedSupervisorTask是支持timeout的调度任务,刷新缓存逻辑实际是在CacheRefreshThread()中,往后看就会看到通过Http请求和Erureka服务器交互。服务注册的代码紧接着在服务发现后面

if (clientConfig.shouldRegisterWithEureka()) {
       ...
        // Heartbeat timer
        scheduler.schedule(
                new TimedSupervisorTask(
                        "heartbeat",
                        scheduler,
                        heartbeatExecutor,
                        renewalIntervalInSecs,
                        TimeUnit.SECONDS,
                        expBackOffBound,
                        new HeartbeatThread()
                ),
                renewalIntervalInSecs, TimeUnit.SECONDS);

        // InstanceInfo replicator
        instanceInfoReplicator = new InstanceInfoReplicator(
                this,
                instanceInfo,
                clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                2); // burstSize

       ...
        if (clientConfig.shouldOnDemandUpdateStatusChange()) {
            applicationInfoManager.registerStatusChangeListener(statusChangeListener);
        }

        instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
    } 

instanceInfoReplicator对当前服务信息( instanceInfo)进行注册。

public void run() {
    ...
    discoveryClient.refreshInstanceInfo();

    Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
    if (dirtyTimestamp != null) {
        discoveryClient.register();
        instanceInfo.unsetIsDirty(dirtyTimestamp);
    }
    ...
}

另外一个心跳检测的定时任务则是对服务进行续约

boolean renew() {
        EurekaHttpResponse<InstanceInfo> httpResponse;
        try {
            httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
            if (httpResponse.getStatusCode() == 404) {
                ...
                return register();
            }
            return httpResponse.getStatusCode() == 200;
        } catch (Throwable e) {
            logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
            return false;
        }
    }

可以看出如果续约失败则重新发起一次注册。那么服务注册的具体实现就在register()中

boolean register() throws Throwable {
    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;
}

实现十分简单,就是发起一次http调用,把当前instanceInfo传递过去。,那么InstanceInfo都有什么呢,其实就是一些appName ,url,port等等,通过这些信息我们就能发起一次基于http的rpc调用。

小结

可以看出,Spring cloud中的服务发现和注册其实就是和通过http方式和eureka进行通信,实现手段则是通过定时任务进行定时操作,包括定时查询,服务续约。

转载于:https://my.oschina.net/u/3039671/blog/1546168

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值