2. 领导“叕”让我研究 Eureka 源码:注册过程
2. 领导“叕”让我研究 Eureka 源码:注册过程
转载自悟空聊架构,原文链接:领导“叕”让我研究 Eureka 源码:注册过程
这次我们来分析客户端是如何注册的。
Eureka Client 就是客户端,可以是 Eureka Server 自身,也可以是要注册的服务实例,比如订单服务、商品服务等。
后续讲到 @EnableEurekaClient
注解时,其实是将当前 Application 当作一个 eureka client,注册到 eureka 服务上。
那么 Eureka Client 是如何注册的呢?
我们可以通过 Eureka 源码提供的示例类 ExampleEurekaClient
来看下 Eureka Client 的构造和注册过程。
首先从 main
方法方法看起,但是只看 main
表面,看不出来注册的代码在哪,那我们就来研究下底层的源码。
public static void main(String[] args) throws UnknownHostException {
injectEurekaConfiguration();
ExampleEurekaClient sampleClient = new ExampleEurekaClient();
// create the client
ApplicationInfoManager applicationInfoManager = initializeApplicationInfoManager(new MyDataCenterInstanceConfig());
EurekaClient client = initializeEurekaClient(applicationInfoManager, new DefaultEurekaClientConfig());
// shutdown the client
eurekaClient.shutdown();
}
接着我们来一步一步分析 main
里面做了什么事情。
先放一张时序图,下文会逐步讲解。

一、初始化配置
1.1 初始化变量
injectEurekaConfiguration()
方法初始化了 Eureka 的一些变量,比如端口号、当前服务的访问路径、是否需要抓取注册表信息等等。
private static void injectEurekaConfiguration() throws UnknownHostException {
String myHostName = InetAddress.getLocalHost().getHostName();
String myServiceUrl = "http://" + myHostName + ":8080/v2/";
System.setProperty("eureka.name", "eureka");
System.setProperty("eureka.port", "8080");
...
}
1.2 获取配置文件配置
在这一行代码中,将配置文件 eureka-client.properties 中的配置读取后,放到了 EurekaInstanceConfig 中。这个 EurekaInstanceConfig 是用来初始化 applicationInfoManager 信息管理器的。
看下面代码,创建了一个 MyDataCenterInstanceConfig
,其实就是创建了 EurekaInstanceConfig
。
new MyDataCenterInstanceConfig()
那 MyDataCenterInstanceConfig
和 EurekaInstanceConfig
是什么关系呢?

从类图关系中可以看到 MyDataCenterInstanceConfig
继承 PropertiesInstanceConfig
类,实现了 EurekaInstanceConfig
接口。这种接口之前专门讲过,通过接口来获取配置信息,类似这种方法 getXX()
。
然后在 PropertiesInstanceConfig
类的构造函数调用了一个工具类,读取了配置文件 eureka-client.properties 中的值。这个隐藏的有点深啊!
Archaius1Utils.initConfig(CommonConstants.CONFIG_FILE_NAME);
1.3 初始化实例信息
主要就是构造出 instanceInfo 实例信息。这个里面的信息包含了第一步初始化变量中的配置信息。
InstanceInfo instanceInfo = new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get();

1.4 初始化实例信息管理器
就是将 instanceConfig 和 instanceInfo 交给实例信息管理器来管理。
applicationInfoManager = new ApplicationInfoManager(instanceConfig, instanceInfo);
二、构造 EurekaClient
2.1 构造流程
构造 eurekaClient 的代码
eurekaClient = new DiscoveryClient(applicationInfoManager, clientConfig);
DiscoveryClient
是 EurekaClient
的子类,构造 DiscoveryClient 做了以下几件事:

加载配置文件
初始化网络传输组件
将服务实例配置、配置文件配置、网络传输组件都赋值给了 DiscoveryClient。
初始化两个线程,一个用来心跳检测,一个用来刷新缓存。
初始化网络通信组件 EurekaTransport
尝试抓取注册表信息,如果没有抓取到,则从备用的注册表中获取。
初始化调度任务的方法中,启动了定时调度任务:心跳检测 heartbeat、缓存刷新 cacheRefresh。(这两个功能在后续篇章中都会讲到,请持续关注)
初始化调度任务的方法中,初始化了一个 InstanceInfoReplicator,用来向 eureka server 注册的。
初始化调度任务的方法中,初始化了一个状态变更的监听器 StatusChangeListener,这个里面也有注册的逻辑。
注意
在初始化调度任务的方法,会根据是否设置了抓取注册表信息和是否注册将 eureka-client 注册到 eureka-server 来执行上面的初始化操作。
如下代码所示:

三、Eureka Client 注册
3.1 注册流程
Eureka Client 向 Server 注册的代码隐藏的比较深,很难找到,不是直接调用注册的方法,而是通过一个后台线程去做的,而且调用注册方法的类的名字起得也有争议,叫做 InstanceInfoReplicator
,“Replicator” 是拷贝副本的意思,而注册其实不是拷贝副本,而是将新的注册信息发送到 eureka server 上去的,所以这个类的名字起得不太好,这也是容易造成找不到注册代码的一个原因。
下面来看下 eureka client 是怎么向 eureka server 注册的。
(1)注册是通过 InstanceInfoReplicator
类来注册的。它是在构造 DiscoveryClient 时创建出来的。
启动了一个延时 40 s 的线程,
instanceInfoReplicator.start(40); // 40 s后执行
(2)然后将一个标志位设置为 true,用来标记是否注册过了。
instanceInfo.setIsDirty();
(3)然后调用注册的方法
discoveryClient.register();
register() 里面的核心代码就是
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
返回的 httpResponse
大家可以想到这是一个 HTTP 请求,eureka client 注册时就是发送的 http 请求。
eurekaTransport:底层的传输组件,在初始化 DiscoveryClient 时初始化出来的。
registrationClient:它是一个抽象类,在初始化 DiscoveryClient 时,通过调用 scheduleServerEndpointTask()
初始化了专门用于注册的 registrationClient,这里就是 SessionedEurekaHttpClient。
instanceInfo:就是要发送给 eureka server 的当前实例信息,用来注册的信息。
(4)发送 post 注册请求
执行 register()
方法,发送注册请求的类是 AbstractJerseyEurekaHttpClient
,这个类在工程 eureka-client-jersey2 里面,用到的是 Jersey 框架,国内用这个框架的不多,就是一个支持 restful 的 Java 框架,不用深究,下篇还会讲到这一块。请求的 url 为

注册的方法里面发送了 post 请求。至此,Client 就注册到 Server 那边了。
response = resourceBuilder
.accept(MediaType.APPLICATION_JSON)
.acceptEncoding("gzip")
.post(Entity.json(info));
那么 Server 是如何将注册信息保存到自己注册表里面的呢? 下篇我们再来讲解。
四、总结
Eureka Client 向 Eureka Server 注册的过程:
(1)Eureka Client 初始化了一个 DiscoveryClient,抓取注册表,执行调度任务。
(2)InstanceInfoReplicator 对象启动了一个延迟 40 s 的后台线程,执行注册。
(3)然后使用 AbstractJersey2EurekaHttpClient 发送 post 请求,将 instanceInfo 实例信息发送给 Eureka Server。
时序图如下:

留个问题
我们使用 Eureka 时,Service 启动后,Eureka 很快就发现了 Service 的存在,如下图所示的控制台界面:

并不需要等待 40 s 才能注册到 Eureka,那这又是为什么呢?
好了,本篇接近尾声,下一篇,我们来看下 Eureka Server 是如何将 Eureka Client 发送过来的注册信息保存起来的。