前言

上一讲已经讲解了EurekaClient的启动流程,到了这里已经有6篇Eureka源码分析的文章了,看了下之前的文章,感觉代码成分太多,会影响阅读,后面会只截取主要的代码,加上注释讲解。

这一讲看的是EurekaClient注册的流程,当然也是一块核心,标题为什么会写上眼花缭乱呢?关于EurekaClient注册的代码,真的不是这么容易被发现的。

如若转载 请标明来源:一枝花算不算浪漫

源码分析

如果是看过前面文章的同学,肯定会知道,Eureka Client启动流程最后是初始化DiscoveryClient这个类,那么我们就直接从这个类开始分析,后面代码都只截取重要代码,具体大家可以自行参照源码。

DiscoveryClient.java

@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider) {
// 省略部分代码... this.applicationInfoManager = applicationInfoManager;
// 创建一个配置实例,这里面会有eureka的各种信息,看InstanceInfo类的注释为:The class that holds information required for registration with Eureka Server
// and to be discovered by other components.
InstanceInfo myInfo = applicationInfoManager.getInfo(); // 省略部分代码... try {
// 支持底层的eureka client跟eureka server进行网络通信的组件
eurekaTransport = new EurekaTransport();
// 发送http请求,调用restful接口
scheduleServerEndpointTask(eurekaTransport, args);
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
} // 初始化调度任务
initScheduledTasks();
}

上面省略了很多代码,这段代码在之前的几篇文章也都有提及,说实话看到这里 仍然一脸闷逼,入册的入口在哪呢?不急,下面慢慢分析。

DiscoveryClient.java

private void initScheduledTasks() {
// 省略大部分代码,这段代码是初始化eureka client的一些调度任务 // InstanceInfo replicator
// 创建服务拷贝副本
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize // 执行线程 InitialInstanceInfoReplicationIntervalSeconds默认为40s
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}

上面仍然是DiscoveryClient中的源码,看方法名我们知道这里肯定是初始化EurekaClient启动时的相关定时任务的。

这里主要是截取了instanceInfoReplicator初始化和执行instanceInfoReplicator.start的任务,

接着我们就可以顺着这个线先看看InstatnceInfoReplicator是何方神圣?

/**
* A task for updating and replicating the local instanceinfo to the remote server. Properties of this task are:
* - configured with a single update thread to guarantee sequential update to the remote server
* - update tasks can be scheduled on-demand via onDemandUpdate()
* - task processing is rate limited by burstSize
* - a new update task is always scheduled automatically after an earlier update task. However if an on-demand task
* is started, the scheduled automatic update task is discarded (and a new one will be scheduled after the new
* on-demand update).
*
* 用于将本地instanceinfo更新和复制到远程服务器的任务。此任务的属性是:
* -配置有单个更新线程以保证对远程服务器的顺序更新
* -可以通过onDemandUpdate()按需调度更新任务
* -任务处理的速率受burstSize的限制
* -新更新总是在较早的更新任务之后自动计划任务。但是,如果启动了按需任务*,则计划的自动更新任务将被丢弃(并且将在新的按需更新之后安排新的任务)
*/
class InstanceInfoReplicator implements Runnable { }

这里有两个关键点:

  1. 此类实现了Runnable接口,说白了就是执行一个异步线程
  2. 该类作用是:用于将本地instanceinfo更新和复制到远程服务器的任务

看完这两点,我又不禁陷入思考,我找的是eurekaClient注册过程,咋还跑到这个里面来了?不甘心,于是继续往下看。

InstanceInfoReplicator.start()

public void start(int initialDelayMs) {
if (started.compareAndSet(false, true)) {
instanceInfo.setIsDirty(); // for initial register
Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}

这个scheduler是一个调度任务线程池,会将this线程放入到线程池中,然后再指定时间后执行该线程的run方法。

InstanceInfoReplicator.run()

public void run() {
try {
// 刷新一下服务实例信息
discoveryClient.refreshInstanceInfo(); Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
logger.warn("There was a problem with the instance info replicator", t);
} finally {
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}

看到这里是不是有种豁然开朗的感觉?看到了register就感觉到希望来了,这里使用的是DiscoveryClient.register方法,其实这里我们也可以先找DiscoveryClient中的register方法,然后再反查调用方,这也是一种好的思路呀。

DiscoveryClient.register

boolean register() throws Throwable {
logger.info(PREFIX + appPathIdentifier + ": registering service...");
EurekaHttpResponse<Void> httpResponse;
try {
// 回看eurekaTransport创建及初始化过程
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;
}

这里是使用eurekaTransport.registrationClient去进行注册,我们在最开始DiscoveryClient构造方法中已经截取了eurekaTransport创建及初始化代码,这里再贴一下:

// 支持底层的eureka client跟eureka server进行网络通信的组件
eurekaTransport = new EurekaTransport();
// 发送http请求,调用restful接口
scheduleServerEndpointTask(eurekaTransport, args); private void scheduleServerEndpointTask(EurekaTransport eurekaTransport,
AbstractDiscoveryClientOptionalArgs args) { // 省略大量代码 // 如果需要抓取注册表,读取其他server的注册信息
if (clientConfig.shouldRegisterWithEureka()) {
EurekaHttpClientFactory newRegistrationClientFactory = null;
EurekaHttpClient newRegistrationClient = null;
try {
newRegistrationClientFactory = EurekaHttpClients.registrationClientFactory(
eurekaTransport.bootstrapResolver,
eurekaTransport.transportClientFactory,
transportConfig
);
newRegistrationClient = newRegistrationClientFactory.newClient();
} catch (Exception e) {
logger.warn("Transport initialization failure", e);
}
// 将newRegistrationClient放入到eurekaTransport中
eurekaTransport.registrationClientFactory = newRegistrationClientFactory;
eurekaTransport.registrationClient = newRegistrationClient;
}
}

到了这里,可以看到eurekaTransport.registrationClient实际就是EurekaHttpClient,不知道是我没找对地方还是什么原因,我并没有找到具体执行的实现类。

最后网上查了下,具体执行的实现类是:AbstractJersey2EurekaHttpClient

AbstractJersey2EurekaHttpClient.register()

public EurekaHttpResponse<Void> register(InstanceInfo info) {
String urlPath = "apps/" + info.getAppName();
Response response = null;
try {
// 发送请求,类似于:http://localhost:8080/v2/apps/ServiceA
// 发送的是post请求,服务实例的对象被打成了一个json发送,包括自己的主机、ip、端口号
// eureka server 就知道了这个ServiceA这个服务,有一个服务实例,比如是在192.168.31.109、host-01、8761端口
Builder resourceBuilder = jerseyClient.target(serviceUrl).path(urlPath).request();
addExtraProperties(resourceBuilder);
addExtraHeaders(resourceBuilder);
response = resourceBuilder
.accept(MediaType.APPLICATION_JSON)
.acceptEncoding("gzip")
.post(Entity.json(info));
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey2 HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}

到了这里就已经真相大白了,可是 读了一通发现这个代码实在是不好理解,最后再总结一波才行。。。

总结

(1)DiscoveryClient构造函数会初始化EurekaClient相关的定时任务,定时任务里面会启动instanceInfo 互相复制的任务,就是InstanceInfoReplicator中的start()

(2)InstanceInfoReplicator的start()方法里面,将自己作为一个线程放到一个调度线程池中去了,默认是延迟40s去执行这个线程,还将isDirty设置为了ture

(3)如果执行线程的时候,是执行run()方法,线程

(3)先是找EurekaClient.refreshInstanceInfo()这个方法,里面其实是调用ApplicationInfoManager的一些方法刷新了一下服务实例的配置,看看配置有没有改变,如果改变了,就刷新一下;用健康检查器,检查了一下状态,将状态设置到了ApplicationInfoManager中去,更新服务实例的状态

(4)因为之前设置过isDirty,所以这里会执行进行服务注册

(5)服务注册的时候,是基于EurekaClient的reigster()方法去注册的,调用的是底层的TransportClient的RegistrationClient,执行了register()方法,将InstanceInfo服务实例的信息,通过http请求,调用eureka server对外暴露的一个restful接口,将InstanceInfo给发送了过去。这里找的是EurekaTransport,在构造的时候,调用了scheduleServerEndpointTask()方法,这个方法里就初始化了专门用于注册的RegistrationClient。

(6)找SessionedEurekaHttpClient调用register()方法,去进行注册,底层最终使用的AbstractJersey2EurekaHttpClient的register方法实现的

(7)eureka大量的基于jersey框架,在eureka server上提供restful接口,在eureka client如果要发送请求到eureka server的话,一定是基于jersey框架,去发送的http restful接口调用的请求

(8)真正执行注册请求的,就是eureka-client-jersey2工程里的AbstractJersey2EurekaHttpClient,请求http://localhost:8080/v2/apps/ServiceA,将服务实例的信息发送过去

eureka client这一块,在服务注册的这块代码,很多槽点:

(1)服务注册,不应该放在InstanceInfoReplicator里面,语义不明朗

(2)负责发送请求的HttpClient,类体系过于复杂,导致人根本就找不到对应的Client,最后是根据他是使用jersey框架来进行restful接口暴露和调用,才能连蒙带猜,找到真正发送服务注册请求的地方

申明

本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!

感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫

【一起学源码-微服务】Nexflix Eureka 源码六:在眼花缭乱的代码中,EurekaClient是如何注册的?的更多相关文章

  1. 【一起学源码-微服务】Eureka+Ribbon+Feign阶段性总结

    前言 想说的话 这里已经梳理完Eureka.Ribbon.Feign三大组件的基本原理了,今天做一个总结,里面会有一个比较详细的调用关系流程图. 说明 原创不易,如若转载 请标明来源! 博客地址:一枝 ...

  2. 【一起学源码-微服务】Ribbon源码五:Ribbon源码解读汇总篇~

    前言 想说的话 [一起学源码-微服务-Ribbon]专栏到这里就已经全部结束了,共更新四篇文章. Ribbon比较小巧,这里是直接 读的spring cloud 内嵌封装的版本,里面的各种config ...

  3. 【一起学源码-微服务】Feign 源码一:源码初探,通过Demo Debug Feign源码

    前言 前情回顾 上一讲深入的讲解了Ribbon的初始化过程及Ribbon与Eureka的整合代码,与Eureka整合的类就是DiscoveryEnableNIWSServerList,同时在Dynam ...

  4. 【一起学源码-微服务】Hystrix 源码一:Hystrix基础原理与Demo搭建

    说明 原创不易,如若转载 请标明来源! 欢迎关注本人微信公众号:壹枝花算不算浪漫 更多内容也可查看本人博客:一枝花算不算浪漫 前言 前情回顾 上一个系列文章讲解了Feign的源码,主要是Feign动态 ...

  5. 【一起学源码-微服务】Hystrix 源码三:Hystrix核心流程:Hystix降级、熔断等原理剖析

    说明 原创不易,如若转载 请标明来源! 欢迎关注本人微信公众号:壹枝花算不算浪漫 更多内容也可查看本人博客:一枝花算不算浪漫 前言 前情回顾 上一讲我们讲解了Hystrix在配合feign的过程中,一 ...

  6. 【一起学源码-微服务】Ribbon 源码三:Ribbon与Eureka整合原理分析

    前言 前情回顾 上一篇讲了Ribbon的初始化过程,从LoadBalancerAutoConfiguration 到RibbonAutoConfiguration 再到RibbonClientConf ...

  7. 【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试

    前言 前情回顾 前面文章已经梳理清楚了Eureka相关的概念及源码,接下来开始研究下Ribbon的实现原理. 我们都知道Ribbon在spring cloud中担当负载均衡的角色, 当两个Eureka ...

  8. 【一起学源码-微服务】Ribbon 源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析

    前言 前情回顾 上一讲讲了Ribbon的基础知识,通过一个简单的demo看了下Ribbon的负载均衡,我们在RestTemplate上加了@LoadBalanced注解后,就能够自动的负载均衡了. 本 ...

  9. 【一起学源码-微服务】Ribbon 源码四:进一步探究Ribbon的IRule和IPing

    前言 前情回顾 上一讲深入的讲解了Ribbon的初始化过程及Ribbon与Eureka的整合代码,与Eureka整合的类就是DiscoveryEnableNIWSServerList,同时在Dynam ...

  10. 【一起学源码-微服务】Feign 源码三:Feign结合Ribbon实现负载均衡的原理分析

    前言 前情回顾 上一讲我们已经知道了Feign的工作原理其实是在项目启动的时候,通过JDK动态代理为每个FeignClinent生成一个动态代理. 动态代理的数据结构是:ReflectiveFeign ...

随机推荐

  1. 2019-10-26-dotnet-core-发布只有一个-exe-的方法

    title author date CreateTime categories dotnet core 发布只有一个 exe 的方法 lindexi 2019-10-26 8:42:7 +0800 2 ...

  2. 【UTR #1】ydc的大树

    [UTR #1]ydc的大树 全网唯一一篇题解我看不懂 所以说一下我的O(nlogn)做法: 以1号点为根节点 一个黑点如果有多个相邻的节点出去都能找到最远的黑点,那么这个黑点就是无敌的 所以考虑每个 ...

  3. 【datagrid】动态加载列 2016-01-03 16:32 2013人阅读 评论(19) 收藏

    之前我们的项目在前台显示只需要把数据从数据库读出来进行显示就可以,datagrid的表头字段都是写死的,把数据往表里一扔,就基本没什么事儿了,结果客户前几天要求,其中一个字段不能是死的,应该是有多少项 ...

  4. Python基础:03序列:字符串、列表和元组

    一:序列 1:连接操作符(+) 这个操作符允许把一个序列和另一个相同类型的序列做连接,生成新的序列.语法如下:sequence1 + sequence2 该表达式的结果是一个包含sequence1和s ...

  5. jQuery 链

    通过 jQuery,可以把动作/方法链接在一起. Chaining 允许我们在一条语句中运行多个 jQuery 方法(在相同的元素上). jQuery 方法链接 直到现在,我们都是一次写一条 jQue ...

  6. 2019-2-11-WPF-获取应用的所有窗口

    title author date CreateTime categories WPF 获取应用的所有窗口 lindexi 2019-02-11 08:55:31 +0800 2019-02-11 0 ...

  7. js切割字符串

    var time_str= '2019-9-10 13:18:20'; var t = time_str.substr(2,8);   console.log(t);   输出  19-9-10

  8. python新知识

    # 强制字符串转化 repr(1.1 + 2.2) # 字符串换行 a = "hello, world. " \ "it's a nice day. " \ & ...

  9. tomcat access日志

    每次看access log都会记不住pattern里的各个标识代表的什么意思,记录下,备忘! tomcat的access log是由实现了org.apache.catalina.AccessLog接口 ...

  10. PHP IF判断 简写

    第一种:IF 条件语句 第二种:三元运算 第三种:&& .|| 组成的条件语句 第一种: IF 基础,相信绝大多数人都会: 第二种:  c=a>b ? true:false  / ...