小白也能读懂springcloud系列之Eureka Client 源码解析

本人小白一枚,第一次发文章,格式字体勿喷,我会进行改进的,我自己都看不下去了哈

一、Eureka体系架构

Eureka体系架构
从上图可以看出整个Eureka是分为client(客户端)、server(服务端),其中我要对图中的几个关键过程进行简单叙述;

  1. Register:客户端向服务端注册过程
  2. Renew:心跳(续约),客户端会定时向服务端发送自己的主机信息,并更新服务端列表
  3. Get:获取服务端的客户端注册信息列表
  4. Cancel:服务下架
  5. Replicate:服务端之间同步注册信息列表

二、源码解析

1、从何下手?

我们都知道SpringCloud是基于SpringBoot的,而SpringBoot最大的特点就是自动配置,所以我们可以从Eureka Client的自动配置开始。
在这里插入图片描述
首先准备一个简单SpringCloud项目,从上图看是一个SpringBoot的启动主类,其中有几个注解我先说一下;

  1. @SpringBootApplication
  2. @EnableDiscoveryClient:启用服务发现,可以连接任意注册中心
  3. @EnableEurekaClient:启用Eureka客户端,只能连接Eureka Server

接下来我们跟进@SpringBootApplication源码可以看到如下图:
在这里插入图片描述
我们可以发现@SpringBootApplication是一个组合注解,其核心注解有两个分别是:

  1. @EnableAutoConfiguration
  2. @ComponentScan

其中注意@ComponentScan注解仅仅是配置扫描指令,而不做具体扫描工作,而@EnableAutoConfiguration这个注解最终是要完成如下两个工作;

  1. 扫描自定义包(开发者自己定义的base-packages),将扫描到到类交由Spring容器管理
  2. 扫描默认自动配置相关包(META-INF/spring.factories),将扫描到到类交由Spring容器管理
    在这里插入图片描述
    在这里插入图片描述

这里我们最应该从EurekaClientAutoConfiguration这个类开始进行分析
在这里插入图片描述

  1. @ConditionalOnClass(EurekaClientConfig.class):该注解是一个条件注解,表明在类路径下需要有EurekaClientConfig这个接口的实现,其定义了eureka client向eureka server注册的必须配置信息,默认实现是DefaultEurekaClientConfig类。

  2. @Import(DiscoveryClientOptionalArgsConfiguration.class):该注解的作用是导入DiscoveryClientOptionalArgsConfiguration这个类。

  3. @ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class):该注解是一个条件注解,表明当前Spring容器中需要有Marker这个类的实例。

  4. @ConditionalOnProperty(value = “eureka.client.enabled”, matchIfMissing = true):该注解表示eureka.client.enabled这个属性如果没有配置默认值就是true。
    @ConditionalOnDiscoveryEnabled:如下图所示,表示spring.cloud.discovery.enabled这个属性如果没有配置默认值就是true。
    在这里插入图片描述

  5. @AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
    CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class }):表明在配置
    NoopDiscoveryClientAutoConfiguration、CommonsClientAutoConfiguration、ServiceRegistryAutoConfiguration类之前,先要完成当前类配置才可以(当前类配置在前)

  6. @AutoConfigureAfter(name = {
    “org.springframework.cloud.autoconfigure.RefreshAutoConfiguration”,
    “org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration”,
    “org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration” }):表示如果想让当前配置类起作用,需要先对RefreshAutoConfiguration、EurekaDiscoveryClientConfiguration、AutoServiceRegistrationAutoConfiguration这些配置类进行配置(当前类配置在后)
    注意:我们现在先关注一下EurekaDiscoveryClientConfiguration这个类
    在这里插入图片描述
    首先这是一个配置类(带有@Configuration注解的类),其次这里有一个@Bean刚好是创建了一个Marker类的实例,并将其纳入到Spring容器。这正好对应了之前看到的@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)

再回到EurekaClientAutoConfiguration类中,其还创建了一些重要的bean,比如:
在这里插入图片描述
在这里插入图片描述
这个类的作用是将配置文件中以eureka.client为前缀的配置信息进行读取后封装。
再有:
在这里插入图片描述
这个类的作用是将关于eureka实例的配置信息进行读取后封装。
接下来继续往下看,会发现有一个内部类
在这里插入图片描述
我们最终eureka的客户端是在这里创建的。所以说对于eureka client的源码应该从下图框框处开始跟
在这里插入图片描述
在这里插入图片描述
现在我们已经找到了入口,下面我会补充一些预备知识,为接下来阅读源码打基础。

2、预备知识

1. InstanceInfo类
在这里插入图片描述
该类的作用是,持有了client向server注册必须的信息和被其他组件发现,也就是说这个InstanceInfo代表当前主机的信息,注册实际上是注册InstanceInfo,而服务发现,也就是发现InstanceInfo。
2. Application
在这里插入图片描述
在这个Application中维护一个列表,如下图所示
在这里插入图片描述
也就是说Application中维护着某一个微服务名称下面所有的提供者信息的列表。我们可以把Application简单当作一个Set集合。
3. Applications
在这里插入图片描述
在这里插入图片描述
Applications就是Eureka Client中存放的
客户端注册表
注意这里说的是客户端注册表,而非服务端注册表(服务端注册表是一个Map<微服务名称,Application>)。
在这里插入图片描述
4. Jersey框架
该框架的功能与 SpringMVC 的相同,都是通过让用户提交 URI,然后处理器进行路由找到相应的后台业务。这个处理器不叫 Controller,而叫 Resource。Eureka Client 与Eureka Server 间,及 Eureka Server 间的通信是通过 Jersey 来完成的。也就是说在文章开始我们说的一些重要过程中Register应该提交的是post请求,Cancel提交delete请求,Renew提交put请求,Get提交get请求。

3、客户端注册表的获取

在这里插入图片描述
首先跟进super()
在这里插入图片描述
通过this()调用了构造方法,传进了4个参数,我们直接看第四个参数,其是以匿名内部类的方式传递的。
在这里插入图片描述
类中有一个get()方法,其获取的是BackupRegistry(备用的注册中心),那什么是备用的注册中心呢?由于Eureka是基于AP模型的,client每30秒会从server下载注册表信息,当client连接不上集群中任意一台server时,将直接采用本地注册列表来保证整体服务可用。这也就是本地注册中心或者说是备用注册中心。下面继续
在这里插入图片描述
跟进this()
在这里插入图片描述
注意这个方法非常长,主要大部分代码其实在做一些准备工作,我们主要关心如下代码
在这里插入图片描述
首先看clientConfig.shouldFetchRegistry() 这里面clientConfig指的是客户端配置信息,shouldFetchRegistry()这个方法对应的配置项是fetch-registry此配置项默认为true
在这里插入图片描述
fetchRegistry(false)这个方法的作用就是获取注册表,返回值是boolean(表示获取注册表成功或失败),方法参数表示是否强制获取所有注册表,也就是说如果if条件满足,则需要获取本地注册表。
继续跟进fetchRegistry(false)
在这里插入图片描述
Applications是啥?前面说过了
在这里插入图片描述
在这里插入图片描述
注意:由于client每30秒去server端下载客户端注册表,当前是服务第一次启动所以获取到的applications肯定是null。
在这里插入图片描述此时if条件是满足的,故肯定会执行if语句里面的getAndStoreFullRegistry(),获取和存储所有注册信息。
在这里插入图片描述
跟进getAndStoreFullRegistry()
在这里插入图片描述
注意这个方法:clientConfig.getRegistryRefreshSingleVipAddress(),clientConfig为客户端配置信息,其在配置文件中对应的是:
在这里插入图片描述
这个配置项的作用是:由于defaultZone这个是可以配置多台server的(集群),如果配置了registry-refresh-single-vip-address,则认定某一台为主节点,这时client连接时会只连接这个主节点,而不配置registry-refresh-single-vip-address,则说明client连接时会将所有的server进行shuffle(打乱)后随机连接一台。
这里由于没有配置该配置项,故跟进
在这里插入图片描述
注意这里要进入AbstractJerseyEurekaHttpClient
在这里插入图片描述
在这里插入图片描述
继续跟进
在这里插入图片描述
在这里插入图片描述
通过response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);这行代码可以清楚看到,提交的是get请求,而serviceUrl则是server地址。
在这里插入图片描述

4、客户端注册

继续往下,找到注册
在这里插入图片描述
跟进register()方法,由于之前分析register()应该发送一个post请求,而post请求发送的是InstanceInfo对象,也就是将主机信息注册到server端
在这里插入图片描述
继续跟进
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最终根据响应的状态码来决定注册是否成功
在这里插入图片描述

5、定时任务

继续往下
在这里插入图片描述
initScheduledTasks():启动一堆定时任务

(1) 第一个定时任务

第一个定时任务就是定时更新客户端注册表在这里插入图片描述
clientConfig.getRegistryFetchIntervalSeconds()对应配置文件中的配置,指的是每30秒从server端下载注册表信息(默认30秒)
在这里插入图片描述
从下图可以看出定时任务的时间以及具体的任务
在这里插入图片描述
跟进new CacheRefreshThread(),可以看到该类是一个线程类且运行run()方法
在这里插入图片描述
跟进refreshRegistry()方法
在这里插入图片描述
这一段代码指的是从远程地区获取注册中心,其对应的配置如下
在这里插入图片描述
这一块代码不做详细描述,继续往下
在这里插入图片描述
remoteRegionsModified代表前一段代码中的远程region是否被修改了,我们这里没有配置则说明是false,跟进fetchRegistry()方法
在这里插入图片描述
注意,由于此时的applications不为null了,if条件不成立,则会走else分支,跟进
getAndUpdateDelta(applications),也就是获取并修改注册表信息
在这里插入图片描述
delta在这里面的意思被翻译成最新的,由于这个定时任务是每30秒获取注册表信息,所以跟进getDelta()方法,这里面依然是发送get请求去获取。
在这里插入图片描述
在这里插入图片描述
如果delta为空,意味着没有获取到最新的,没有获取到最新的我就必须再全部获取一次,如果delta不为空,则需要更新,跟进updateDelta()方法
在这里插入图片描述
这里面代码也比较长,主要用作更新操作(把delta中applications下的instanceinfo取出来与本地的做比较),详细逻辑这里不做分析

(2) 第二个定时任务

第二个定时任务就是定时心跳
在这里插入图片描述
getLeaseInfo():指的是获取续约信息(Lease:续约)
getRenewalIntervalInSecs():在配置文件中的配置是
在这里插入图片描述
跟进new HeartbeatThread()
在这里插入图片描述
renew()方法返回值是boolean,心跳成功则记录最后成功方法心跳的时间戳,继续跟进renew()方法在这里插入图片描述
跟进sendHeartBeat()方法
在这里插入图片描述
注意这里提交的是put请求,是更新操作,但是put请求具体携带的数据我们在这里面没有看到,等下次将服务端源码解析时会看到
在这里插入图片描述
注意这里的Status.NOT_FOUND 指的是,发送心跳实际上是将instanceinfo发送到服务端,如果服务端的注册表信息中没有我的这个instanceinfo,就会发生NOT_FOUND
在这里插入图片描述
如果状态是NOT_FOUND ,则重新进行注册操作。

(3) 第三个定时任务

第三个定时任务就是定时检查更新续约信息
在这里插入图片描述
其中跟进new InstanceInfoReplicator(),发现其是一个线程类
在这里插入图片描述
该类的作用是:更新和复制本地的instanceinfo到远程server,这里要说明一点,client的配置文件是可以动态修改的,这里面会通过定时任务去检查配置文件的修改并同步到server端
该线程在这里(还是在定时任务的方法里面)启动的在这里插入图片描述
启动后将执行run()方法
在这里插入图片描述
首先跟进refreshInstanceInfo()方法
在这里插入图片描述
这里主要关心refreshLeaseInfoIfRequired()方法,更新续约信息,跟进
在这里插入图片描述
其中instanceInfo.getLeaseInfo()是获取当前的续约信息
在这里插入图片描述
这些是获取配置文件中的续约相关信息,其中config.getLeaseExpirationDurationInSeconds()在配置文件中是
在这里插入图片描述
而后进行比较(当前与配置文件中)
在这里插入图片描述
在这里插入图片描述
这一块代表instanceinfo配置已经被修改了,并记录最新修改的时间戳
在这里插入图片描述
接下来继续返回到run()方法里
在这里插入图片描述
跟进isDirtyWithTime()方法
在这里插入图片描述
这里面的isInstanceInfoDirty指的是上述是否修改配置文件信息的操作,如果修改了返回最新修改时间戳否则返回null,继续向下
在这里插入图片描述
如果dirtyTimestamp不为null说明配置文件修改了,则重新执行注册操作。也就是说如果你把续约信息改掉了,则说明这个instanceinfo对server来说是一个全新的,需要注册。

6、服务下架

在这里插入图片描述
这一段代码在最一开始创建EurekaClient代码中有一个destroyMethod = “shutdown”,跟进这个方法在这里插入图片描述
首先执行cancelScheduledTasks() 取消所有定时任务
在这里插入图片描述
依次停止了定时检测更新配置文件、定时心跳、定时更新注册表以及其他定时任务。
在这里插入图片描述
在这里插入图片描述
修改instanceinfo状态为DOWN以及关闭监视器
在这里插入图片描述
跟进unregister()方法,进行服务下架
在这里插入图片描述
跟进cancel()方法
在这里插入图片描述
最终是发送delete请求完成服务下架。

三、总结

整体跟源码结构大致如下图
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值