前言

上一讲已经讲解了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 和公众号:壹枝花算不算浪漫,如若转载请标明来源!

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

最新文章

  1. [工作中的设计模式]迭代子模式Iterator
  2. MySQL创建和操作数据库表demo
  3. c#String的不变特性,可读但不可写性
  4. POJ-2718 Smallest Difference
  5. python开发vim插件
  6. Innodb 锁系列2 事务锁
  7. Swift - 多线程实现方式(1) - NSThread
  8. Akka(12): 分布式运算:Cluster-Singleton-让运算在集群节点中自动转移
  9. 在Python3.5中使用 The del.icio.us API
  10. Dapper.Contrib——更加优雅地使用Dapper进行增删改查
  11. 听翁恺老师mooc笔记(16)--程序设计与C语言
  12. WebStorm配置node.js调试
  13. 备考2019年6月份PMP考试-分享一些(备考)考试心得
  14. 实战深度学习OpenCV(一):canny边缘检测
  15. Invoker-n颜色涂m个珠子的项链
  16. jQuery动态添加删除与添加表行代码
  17. 【iCore1S 双核心板_FPGA】例程一:GPIO输出实验——点亮LED
  18. idea中快捷键设置为eclipse中快捷键
  19. mysql 存入乱码问题
  20. Java微信公众平台开发(四)--回复消息的分类及实体的创建

热门文章

  1. Cmakelists.txt中配置glew
  2. Plupload的上传机制
  3. 网络编程--多线程 , socketserver
  4. TreeSet之用外部比较器实现自定义有序(重要)
  5. SVN过滤设置 标签: svn 2015-07-29 17:39 953人阅读 评论(35) 收藏
  6. 从零学React Native之06flexbox布局
  7. idea使用积累
  8. Libev源码分析07:Linux下的eventfd简介
  9. CSS的固定定位
  10. Python--day43--mysql自增列之起始值和步长