Eureka核心源码解析系列(一)- 服务注册、续约篇

举报
码农参上 发表于 2022/04/26 09:10:11 2022/04/26
【摘要】 Eureka作为Spring Cloud的核心模块之一,担任着服务注册发现等重要作用。本文将从以下几个方面进行Eureka的源码分析,梳理实际工作流程:服务注册服务续约服务剔除服务下线服务发现集群信息同步上述各个方面,基于服务的运行场景不同,可能分别从Eureka的服务端(注册中心)与客户端(包含服务提供者与服务调用者)进行分析,为了简便下文中将Eureka服务端称为Eureka-serve...

Eureka作为Spring Cloud的核心模块之一,担任着服务注册发现等重要作用。本文将从以下几个方面进行Eureka的源码分析,梳理实际工作流程:

  • 服务注册
  • 服务续约
  • 服务剔除
  • 服务下线
  • 服务发现
  • 集群信息同步

上述各个方面,基于服务的运行场景不同,可能分别从Eureka的服务端(注册中心)与客户端(包含服务提供者与服务调用者)进行分析,为了简便下文中将Eureka服务端称为Eureka-server,客户端称为Eureka-client。

服务注册

Eureka-client

在Eureka-client中,DiscoveryClient这个类用来和Eureka-server互相协作,看一下它的注释,它可以完成服务注册,服务续约,服务下线,获取服务列表等工作,可以说它完成了client的大多数功能。首先,看一下用来向eureka-server发起注册请求的register方法:

调用 AbstractJerseyEurekaHttpClient 类的register方法:

Jersey是一个Restful请求服务的框架,与常用的springmvc类似,后面会讲到在Eureka-server拦截请求的时候也用到了Jersy。

在这里调用底层类:

com.sun.jersey.api.client.Client

通过HTTP客户端发送http请求,并构建响应结果。

Eureka-server

在Eureka-server,配置好yml文件中必需的参数后,只需要一个注解开启:

@EnableEurekaServer

查看该注解的实现方法,发现为空白注解,并使用了@Import

@Import(EurekaServerMarkerConfiguration.class)

查看EurekaServerMarkerConfiguration类的实现:

在这里只向spring容器中注入bean,没有任何意义。这里用到了Springboot的自动装配(这个不熟悉的可以参考springboot零配置启动):

发现Eureka server核心的自动配置类EurekaServerAutoConfiguration

我们看到,在这个类上有条件注入注解:

@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)

只有在Spring容器中存在Marker这个Bean时才会实例化这个类,所以@EnableEurekaServer就相当于一个开关,起到标识的作用。

在这个配置类中定义了拦截器,同样使用Jersy拦截请求:

ApplicationResource类的addInstance方法接收请求,在对实例的信息进行验证后,向服务注册中心添加实例:

进入InstanceRegistryregister方法:

在这里做了两个功能:

1、调用handleRegistration,在方法中使用publishEvent发布了监听事件 。Spring支持事件驱动,可以监听者模式进行事件的监听,这里广播给所有监听者,收到一个服务注册的请求。

至于监听器,可以由我们自己手写实现,参数中的事件类型spring会帮我们直接注入:

@Component
public class EurekaRegisterListener {
  @EventListener
  public void registe(EurekaInstanceRegisteredEvent event){
    System.out.println(event.getInstanceInfo().getAppName());
  }
}

2、调用父类PeerAwareInstanceRegistryImplregister方法:

进行了下面的操作:

① 拿到微服务的过期时间,并进行更新

② 将服务注册交给父类完成

③ 完成集群信息同步(这个会在后面说明)

调用父类AbstractInstanceRegistryregister方法,在这开始真正开始做服务注册。先说一下在这个类中定义的Eureka-server的服务注册列表的结构:

ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry;

ConcurrentHashMap中外层的String表示服务名称;

Map中的String表示服务节点的id (也就是实例的instanceid);

Lease是一个心跳续约的对象,InstanceInfo表示实例信息。

首先,注册表根据微服务的名称或取Map,如果不存在就新建,使用putIfAbsent

然后,从gMap(gMap就是该服务的实例列表)获取一次服务实例,判断这个微服务的节点是否存在,第一次注册的情况下一般是不存在的

当然,也有可能会发生注册信息冲突时,这时Eureka会根据最后活跃时间来判断到底覆盖哪一个:

这段代码中,Eureka拿到存在节点的最后活跃时间,和当前注册节点的发起注册时间,进行对比。当存在的节点的最后活跃时间大于当前注册节点的时间,就说明之前存在的节点更活跃,就替换当前节点。

这里有一个思想,就是如果Eureka缓存的老节点更活跃,就说明它能够使用,而新来的服务我并不知道是否能用,那么Eureka就保守的使用了可用的老节点,从这一点也保证了可用性

在拿到服务实例后对其进行封装:

Lease是一个心跳续约的包装类,里面存放了注册信息,最后操作时间,注册时间,过期时间,剔除时间等信息。在这里把注册实例及过期时间放到这个心跳续约对象中,再把心跳续约对象放到gmap注册表中去。之后进行改变服务状态,系统数据统计,至此一个服务注册的流程就完成了。

注册完成后,查看一下registry中的服务实例,发现我们启动的Eureka-client都已经放在里面了:

服务续约

Eureka-client

服务续约由Eureka-client端主动发起,由之前介绍过的DiscoveryClient类中的renew方法完成,主要内容仍然是发送http请求:

每隔30秒进行一次续约,调用AbstractJerseyEurekaHttpClientsendHeartBeat方法:

Eureka-server

在Eureka-server端,服务续约的调用链与服务注册基本相同:

InstanceRegistry # renew() ->
PeerAwareInstanceRegistry # renew()->
AbstractInstanceRegistry # renew()v

主要看一下AbstractInstanceRegistryrenew方法:

先从注册表获取该服务的实例列表gMap,再从gMap中通过实例的id 获取具体的 要续约的实例。之后根据服务实例的InstanceStatus判断是否处于宕机状态,以及是否和之前状态相同。如果一切状态正常,最终调用Lease中的renew方法:

可以看出,其实服务续约的操作非常简单,它的本质就是修改服务的最后的更新时间。将最后更新时间改为系统当前时间加上服务的过期时间。值得提一下的是,lastUpdateTimestamp这个变量是被volatile关键字修饰的。

之前的文章中我们讲过volitaile是用来保证可见性的。那么要被谁可见呢,提前说一下,这里要被服务剔除中执行的定时任务可见,后面会具体分析。

总结

本文作为Eureka源码分析的第一篇,简单讲解了服务是如何进行注册处以及续约的。下一篇,我们顺着流程,再来看一下服务剔除与服务下线。

最后

觉得对您有所帮助,小伙伴们可以点个赞啊,非常感谢~
公众号『码农参上』,一个热爱分享的公众号,有趣、深入、直接,与你聊聊技术。欢迎来加我好友 DrHydra9,围观朋友圈,做个点赞之交。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。