3

Spring Cloud源码分析之Eureka第四章:服务注册是如何发起的

 1 year ago
source link: https://blog.51cto.com/zq2599/5450691
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码): https://github.com/zq2599/blog_demos

  • Spring Cloud环境下,服务提供者和消费者启动后都会将自身注册到Eureka,从本章开始我们来探寻整个注册过程的代码逻辑,以加深对Spring Cloud的服务注册发现机制的理解;
  • Eureka的服务注册发现功能涉及内容较多,因此分为多篇文章进行,大纲如下:
  1. 分析一个普通的SpringBoot应用,是如何开始执行服务注册发现相关的功能的,也就是本篇文章的内容;
  2. 分析服务列表的获取和更新逻辑;
  3. 分析注册到Eureka的逻辑;
  4. 分析向Eureka续租的逻辑;

将服务注册到Eureka

  • 一个SpringBoot应用如果要注册到Spring Cloud环境(Edgware.RELEASE版本),步骤很简单:
  1. pom.xml中添加启动器:spring-cloud-starter-netflix-eureka-client;
  2. 增加配置:eureka.client.serviceUrl.defaultZone:  http://localhost:8081/eureka/;
  3. 启动应用;
  • 如果注册中心正常,此时就能在注册中心发现这个应用了,如下图红框所示:
    Spring Cloud源码分析之Eureka第四章:服务注册是如何发起的_eureka

为什么不用注解@EnableDiscoveryClient

启动逻辑分析

  • 现在就来分析应用启动过程,看注册服务是如何启动的:
  • spring-cloud-netflix-eureka-client-1.4.0.RELEASE.jar是个重要的jar包,很多配置都在此jar内部的spring.factories文件中,首先要确定这个jar包是否会出现在应用的classpath中(如果不在classpath中,这些配置就不会生效),在pom.xml所在目录下执行命令mvn dependency:tree,打印依赖树,如下图,可确认spring-cloud-netflix-eureka-client被启动器spring-cloud-starter-netflix-eureka-client间接依赖,因此会出现在classpath中:
Spring Cloud源码分析之Eureka第四章:服务注册是如何发起的_eureka_02
  • spring-cloud-netflix-eureka-client-1.4.0.RELEASE.jar中的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.netflix.eureka.EurekaDiscoveryClientConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
  • 以上配置信息可见,如果springboot启用了自动配置,那么EurekaClientConfigServerAutoConfiguration、…、EurekaDiscoveryClientConfiguration等五个配置类都会生效;

  • 按照spring.factories中的配置,EurekaClientAutoConfiguration中的配置都会生效,包括下面这段代码返回的bean:

@Bean
public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) {
	return new EurekaDiscoveryClient(config, client);
}
  • spring容器初始化时会实例化所有单例bean,就会执行EurekaClientAutoConfiguration的discoveryClient方法获取这个bean实例,于是就构造了一个EurekaDiscoveryClient对象;
  • 注意EurekaDiscoveryClient的构造方法,第二个入参是com.netflix.discovery.EurekaClient类型,此对象同样来自EurekaClientAutoConfiguration类,如下方法:
@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);
}
  • CloudEurekaClient的父类com.netflix.discovery.DiscoveryClient来自netflix发布的eureka-client包中,所以可以这么理解:EurekaDiscoveryClient类是个代理身份,真正的服务注册发现是委托给netflix的开源包来完成的,我们可以专心的使用SpringCloud提供的服务注册发现功能,只需要知道EurekaDiscoveryClient即可,真正的服务是eureka-client来完成的;

  • 接下来需要关注com.netflix.discovery.DiscoveryClient的构造方法,因为这里面有服务注册的逻辑,整个构造方法内容太多,无需都细看,只看关键代码即可;

  • DiscoveryClient的构造方法中,最熟悉的应该是下图红框中这段日志输出的了:

Spring Cloud源码分析之Eureka第四章:服务注册是如何发起的_spring_03
  • 对应的应用启动日志中就有这段日志输出,如下图红框:

  • 红框中的"us-east-1",是默认的region,来自配置类EurekaClientConfigBean,这里面有各种eureka相关的配置信息,以及默认配置,如下图:

Spring Cloud源码分析之Eureka第四章:服务注册是如何发起的_spring cloud_04
  • 继续看DiscoveryClient的构造方法,服务注册相关的initScheduledTasks方法在此被调用,如下图:
Spring Cloud源码分析之Eureka第四章:服务注册是如何发起的_spring cloud_05
  • initScheduledTasks方法的内容如下,请注意中文注释:
private void initScheduledTasks() {
	//获取服务注册列表信息
        if (clientConfig.shouldFetchRegistry()) {
            //服务注册列表更新的周期时间
            int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
            int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            //定时更新服务注册列表
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "cacheRefresh",
                            scheduler,
                            cacheRefreshExecutor,
                            registryFetchIntervalSeconds,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new CacheRefreshThread() //该线程执行更新的具体逻辑
                    ),
                    registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }

        if (clientConfig.shouldRegisterWithEureka()) {
	        //服务续约的周期时间
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            //应用启动可见此日志,内容是:Starting heartbeat executor: renew interval is: 30
            logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
            // 定时续约
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread() //该线程执行续约的具体逻辑
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);

            //这个Runable中含有服务注册的逻辑
            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 (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.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }
    }
  • 上述代码中有几处需要注意,这些关键点在后面的章节将继续展开:
    a. 周期性更新服务列表;
    b. 周期性服务续约;
    c. 服务注册逻辑被放入Runnable实现类InstanceInfoReplicator之中,在新线程中执行;

关于Spring Cloud工程和Netflix工程

  • 在注册发现相关的内容中,Spring Cloud和Netflix的关系,可以用以下类图来辅助理解,该图来自大神程序猿DD的文章《Spring Cloud源码分析(一)Eureka》,向大神致敬!
Spring Cloud源码分析之Eureka第四章:服务注册是如何发起的_eureka_06
  • 至此,我们已经了解了SpringBoot应用启动服务注册功能的步骤,接下来的章节会继续深入如何更新服务列表;

欢迎关注51CTO博客:程序员欣宸

 学习路上,你不孤单,欣宸原创一路相伴…


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK