写点什么

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

作者:程序员欣宸
  • 2022 年 7 月 07 日
  • 本文字数:4222 字

    阅读完需:约 14 分钟

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

欢迎访问我的 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. 启动应用;


  • 如果注册中心正常,此时就能在注册中心发现这个应用了,如下图红框所示:

为什么不用注解 @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-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:


@Beanpublic 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@Lazypublic 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 的构造方法中,最熟悉的应该是下图红框中这段日志输出的了:



  • 对应的应用启动日志中就有这段日志输出,如下图红框:

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



  • 继续看 DiscoveryClient 的构造方法,服务注册相关的 initScheduledTasks 方法在此被调用,如下图:



  • 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》,向大神致敬!



  • 至此,我们已经了解了 SpringBoot 应用启动服务注册功能的步骤,接下来的章节会继续深入如何更新服务列表;

欢迎关注 ITpub:程序员欣宸

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

发布于: 21 小时前阅读数: 15
用户头像

搜索"程序员欣宸",一起畅游Java宇宙 2018.04.19 加入

前腾讯、前阿里员工,从事Java后台工作,对Docker和Kubernetes充满热爱,所有文章均为作者原创,个人Github:https://github.com/zq2599/blog_demos

评论

发布
暂无评论
Spring Cloud源码分析之Eureka篇第四章:服务注册是如何发起的_Java_程序员欣宸_InfoQ写作社区