我们在将一个普通的Spring Boot应用注册到Eureka Server中,或是从Eureka Server中获取服务列表时,主要就做了两件事:
- 在应用主类he中配置了
@EnableDiscoveryClient
注解 - 在
application.properties
中用eureka.client.serviceUrl.defaultZone
参数指定了服务注册中心的位置
顺着上面的线索,我们先查看具体实现:
- @EnableDiscoveryClient的源码如下:java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* Annotation to enable a DiscoveryClient implementation.
* @author Spencer Gibb
*/
(ElementType.TYPE)
(RetentionPolicy.RUNTIME)
.class) (EnableDiscoveryClientImportSelector
public @interface EnableDiscoveryClient {
/**
* If true, the ServiceRegistry will automatically register the local server.
* @return - {@code true} if you want to automatically register.
*/
boolean autoRegister() default true;
}@EnableDiscoveryClient
注解的作用主要是用来引入EnableDiscoveryClientImportSelector
这个类
EnableDiscoveryClientImportSelector的源码:
java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50/**
* @author Spencer Gibb
*/
100) (Ordered.LOWEST_PRECEDENCE -
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector<EnableDiscoveryClient> {
public String[] selectImports(AnnotationMetadata metadata) {
String[] imports = super.selectImports(metadata);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
boolean autoRegister = attributes.getBoolean("autoRegister");
if (autoRegister) {
ListimportsList = new ArrayList<>(Arrays.asList(imports));
importsList.add(
"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = importsList.toArray(new String[0]);
}
else {
Environment env = getEnvironment();
if (ConfigurableEnvironment.class.isInstance(env)) {
ConfigurableEnvironment configEnv = (ConfigurableEnvironment) env;
LinkedHashMapmap = new LinkedHashMap<>();
map.put("spring.cloud.service-registry.auto-registration.enabled", false);
MapPropertySource propertySource = new MapPropertySource(
"springCloudDiscoveryClient", map);
configEnv.getPropertySources().addLast(propertySource);
}
}
return imports;
}
protected boolean isEnabled() {
return getEnvironment().getProperty("spring.cloud.discovery.enabled",
Boolean.class, Boolean.TRUE);
}
protected boolean hasDefaultFactory() {
return true;
}
}EnableDiscoveryClientImportSelector
继承了SpringFactoryImportSelector
并指定了泛型EnableDiscoveryClient
. 这里的泛型是重点.SpringFactoryImportSelector源码
java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59/**
* Selects configurations to load, defined by the generic type T. Loads implementations
* using {@link SpringFactoriesLoader}.
*
* @paramtype of annotation class
* @author Spencer Gibb
* @author Dave Syer
*/
public abstract class SpringFactoryImportSelector<T>
implements DeferredImportSelector, BeanClassLoaderAware, EnvironmentAware {
private final Log log = LogFactory.getLog(SpringFactoryImportSelector.class);
private ClassLoader beanClassLoader;
private ClassannotationClass;
private Environment environment;
"unchecked") (
protected SpringFactoryImportSelector() {
this.annotationClass = (Class) GenericTypeResolver
.resolveTypeArgument(this.getClass(), SpringFactoryImportSelector.class);
}
public String[] selectImports(AnnotationMetadata metadata) {
if (!isEnabled()) {
return new String[0];
}
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(this.annotationClass.getName(), true));
Assert.notNull(attributes, "No " + getSimpleName() + " attributes found. Is "
+ metadata.getClassName() + " annotated with @" + getSimpleName() + "?");
// Find all possible auto configuration classes, filtering duplicates
Listfactories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader
.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
if (factories.isEmpty() && !hasDefaultFactory()) {
throw new IllegalStateException("Annotation @" + getSimpleName()
+ " found, but there are no implementations. Did you forget to include a starter?");
}
if (factories.size() > 1) {
// there should only ever be one DiscoveryClient, but there might be more than
// one factory
this.log.warn("More than one implementation " + "of @" + getSimpleName()
+ " (now relying on @Conditionals to pick one): " + factories);
}
return factories.toArray(new String[factories.size()]);
}
protected abstract boolean isEnabled();
...省略
}这里只截取了部分变量和方法,
SpringFactoryImportSelector
是spring cloud common包中的一个抽象类, 主要作用是检查泛型T是否有指定的factory实现, 即spring.factories
中有对应类的配置并启动自动化配置(由SpringFactoriesLoader
加载并解析spring.factories
文件, 具体加载原理见springboot2.2自动注入文件spring.factories如何加载详解)spring.factories
在spring-cloud-netflix-eureka-client.jar!/META-INF/spring.factories
中EnableDiscoveryClient
的指定factory实现是
1 | \ = |
EnableAutoConfiguration
中包含了EurekaClientAutoConfiguration
, EurekaClientAutoConfiguration
会为```EurekaDiscoveryClientConfiguration`的实例依赖进行初始化。
下面对spring.factories
中的eureka自动化配置一个个分析源码:
- EurekaClientConfigServerAutoConfiguration 配置服务器自动化配置java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25false) (proxyBeanMethods =
.class, EurekaClient.class, ({ EurekaInstanceConfigBean
ConfigServerProperties.class })
public class EurekaClientConfigServerAutoConfiguration {
false) (required =
private EurekaInstanceConfig instance;
false) (required =
private ConfigServerProperties server;
public void init() {
if (this.instance == null || this.server == null) {
return;
}
String prefix = this.server.getPrefix();
if (StringUtils.hasText(prefix) && !StringUtils
.hasText(this.instance.getMetadataMap().get("configPath"))) {
this.instance.getMetadataMap().put("configPath", prefix);
}
}
}
从源码看到注入了EurekaInstanceConfig instance
配置,EurekaInstanceConfig
这个bean是在org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean()
中创建的, 另外 init()方法上有@PostConstruct
注解,说明在创建这个bean后执行了init()方法, 方法内部获取ConfigServerProperties
中的prefix, 如果eureka.instance.metadata.configPath
没有配置,则使用prefix的值。
- EurekaClientAutoConfiguration 客户端自动化配置
实例化application.yml中eureka.instance及eureka.client相关属性配置的bean以及创建EurekaClient
1 | false) (proxyBeanMethods = |
- EurekaInstanceConfigBean 实例配置信息
实例化eureka.instance
为前缀的配置信息EurekaInstanceConfigBean
(实现了EurekaInstanceConfig
)
用户承载eureka.instance配置信息的EurekaInstanceConfigBean
类的源码如下:
1 | import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceId; |
从EurekaInstanceConfigBean的构造方法中可以看到它接收1个参数InetUtils
, this.hostInfo = this.inetUtils.findFirstNonLoopbackHostInfo();
这行代码使用传入的InetUtils获取主机信息, 接下来分析InetUtils网络工具类源码。
- InetUtils网络工具类Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70public class InetUtils implements Closeable {
public HostInfo findFirstNonLoopbackHostInfo() {
InetAddress address = findFirstNonLoopbackAddress();
if (address != null) {
return convertAddress(address);
}
HostInfo hostInfo = new HostInfo();
hostInfo.setHostname(this.properties.getDefaultHostname());
hostInfo.setIpAddress(this.properties.getDefaultIpAddress());
return hostInfo;
}
public InetAddress findFirstNonLoopbackAddress() {
InetAddress result = null;
try {
int lowest = Integer.MAX_VALUE;
// 通过jdk的NetworkInterface获取所有网络接口
for (Enumerationnics = NetworkInterface
.getNetworkInterfaces(); nics.hasMoreElements();) {
NetworkInterface ifc = nics.nextElement();
// 当前遍历的网络接口是否已启用
if (ifc.isUp()) {
this.log.trace("Testing interface: " + ifc.getDisplayName());
// 找出索引最小的网络接口
if (ifc.getIndex() < lowest || result == null) {
lowest = ifc.getIndex();
}
else if (result != null) {
continue;
}
// 判断是否需要忽略当前网络接口
if (!ignoreInterface(ifc.getDisplayName())) {
// 不忽略当前网络接口,获取当前网络接口的所有网络地址
for (Enumerationaddrs = ifc
.getInetAddresses(); addrs.hasMoreElements();) {
InetAddress address = addrs.nextElement();
// 是ipv4地址,且不是本地回环地址(127.xxx.xxx.xx这种地址), 且是优先需要使用的地址
if (address instanceof Inet4Address
&& !address.isLoopbackAddress()
&& isPreferredAddress(address)) {
this.log.trace("Found non-loopback interface: "
+ ifc.getDisplayName());
result = address;
}
}
}
// @formatter:on
}
}
}
catch (IOException ex) {
this.log.error("Cannot get first non-loopback address", ex);
}
if (result != null) {
return result;
}
try {
return InetAddress.getLocalHost();
}
catch (UnknownHostException e) {
this.log.warn("Unable to retrieve localhost");
}
return null;
}
}
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean
方法中获取EurekaInstanceConfigBean
实例时设置了实例id(instance.setInstanceId(getDefaultInstanceId(env));
)中的getDefaultInstanceId()
方法是IdUtils
类中的方法,这里的getDefaultInstanceId()
方法是静态导入的,所以没有看到通过Class.methods()这种形式调用,IdUtils
的实现源码如下:
- IdUtils 实例id工具类java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47public final class IdUtils {
public static String getDefaultInstanceId(PropertyResolver resolver) {
return getDefaultInstanceId(resolver, true);
}
// 默认实例id(主机名:服务名:[实例id | 端口号])
// 具体格式为${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id}或${server.port}
public static String getDefaultInstanceId(PropertyResolver resolver,
boolean includeHostname) {
String vcapInstanceId = resolver.getProperty("vcap.application.instance_id");
if (StringUtils.hasText(vcapInstanceId)) {
return vcapInstanceId;
}
String hostname = null
// 实例id是否包含主机名,上面重载的getDefaultInstanceId方法内部传递的第2个参数为true,所以会包含主机名 ;
if (includeHostname) {
hostname = resolver.getProperty("spring.cloud.client.hostname");
}
// spring.application.name的属性值
String appName = resolver.getProperty("spring.application.name");
// 值为${spring.cloud.client.hostname}:${spring.application.name} , 把hostname和appName用冒号拼接起来
String namePart = combineParts(hostname, SEPARATOR, appName);
// 值为${spring.application.instance_id}或${server.port}
String indexPart = resolver.getProperty("spring.application.instance_id",
resolver.getProperty("server.port"));
// 把namePart和indexPart用冒号拼接起来
// ${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id}或${server.port}
return combineParts(namePart, SEPARATOR, indexPart);
}
public static String combineParts(String firstPart, String separator,
String secondPart) {
String combined = null;
if (firstPart != null && secondPart != null) {
combined = firstPart + separator + secondPart;
}
else if (firstPart != null) {
combined = firstPart;
}
else if (secondPart != null) {
combined = secondPart;
}
return combined;
}
} - EurekaClientConfigBean 客户端配置
实例化eureka.client为前缀的配置信息EurekaClientConfigBean(实现了EurekaClientConfig)
1 | (EurekaClientConfigBean.PREFIX) |
Eureka 中的 region 和 Zone
背景
像亚马逊这种大型的跨境电商平台,会有很多个机房。这时如果上线一个服务的话,我们希望一个机房内的服务优先调用同一个机房内的服务,当同一个机房的服务不可用的时候,再去调用其它机房的服务,以达到减少延时的作用。
于是亚马逊的 AWS 提供了 region 和 zone 两个概念
概念
- region:可以简单理解为地理上的分区。比如亚洲地区,或者华北地区,再或者北京地区等等,没有具体大小的限制,根据项目具体的情况,可以自行划分region。
- zone:可以简单理解为 region 内的具体机房,比如说 region 划分为华北地区,然后华北地区有两个机房,就可以在此 region 之下划分出 zone1、zone2 两个 zone
eureka 也借用了 region 和 zone 的概念
分区服务架构图
如图所示,有一个 region:华北地区,下面有两个机房,机房A 和机房B
每个机房内有一个 Eureka Server 集群 和两个服务提供者 ServiceA 和 ServerB
现在假设 serverA 需要调用 ServerB 服务,按照就近原则,serverA 会优先调用同一个 zone 内的 ServiceB,当 ServiceB 不可用时,才会去调用另一个 zone 内的 ServiceB
Eureka 中 Regin 和 Zone 的相关配置
- 服务注册:要保证服务注册到同一个zone内的注册中心,因为如果注册到别zone的注册中心的话,网络延时比较大,心跳检测很可能出问题。
- 服务调用:要保证优先调用同一个zone内的服务,只有在同一个zone内的服务不可用时,才去调用别zone的服务。当存在多个注册中心时,选择逻辑为:yml
1
2
3
4
5
6
7
8
9
10
11eureka:
client:
# 尽量向同一区域的 eureka 注册,默认为true
prefer-same-zone-eureka: true
#地区
region: huabei
availability-zones:
huabei: zone-1,zone-2
service-url:
zone-1: http://localhost:30000/eureka/
zone-2: http://localhost:30001/eureka/
- 如果 prefer-same-zone-eureka 为 false,按照 service-url 下的 list 取第一个注册中心来注册,并和其维持心跳检测,不再向list内的其它的注册中心注册和维持心跳。
只有在第一个注册失败的情况下,才会依次向其它的注册中心注册,总共重试3次,如果3个service-url都没有注册成功,则注册失败。
注册失败后每隔一个心跳时间,会再次尝试。
- 如果 prefer-same-zone-eureka 为true,先通过 region 取 availability-zones 内的第一个zone,然后通过这个zone取 service-url 下的list,并向list内的第一个注册中心进行注册和维持心跳,不再向list内的其它的注册中心注册和维持心跳。
只有在第一个注册失败的情况下,才会依次向其它的注册中心注册,总共重试3次,如果3个service-url都没有注册成功,则注册失败。
注册失败后每隔一个心跳时间,会再次尝试。
为了保证服务注册到同一个 zone 的注册中心,一定要注意 availability-zones 的顺序,必须把同一 zone 写在最前面
服务调用
1 | eureka: |
服务消费者和服务提供者分别属于哪个zone,均是通过 eureka.instance.metadata-map.zone 来判定的。
服务消费者会先通过 ribbon 去注册中心拉取一份服务提供者的列表,然后通过 eureka.instance.metadata-map.zone 指定的 zone 进行过滤,过滤之后如果同一个 zone 内的服务提供者有多个实例,则会轮流调用。
只有在同一个 zone 内的所有服务提供者都不可用时,才会调用其它zone内的服务提供者。
作者:我妻礼弥
链接:https://juejin.im/post/5d68b73af265da03b12061be
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- EurekaClient
以下图片来自Netflix官方,图中显示Eureka Client会向注册中心发起Get Registry请求来获取服务列表:
在org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration
中实例化了该bean, 源码如下:Code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class EurekaClientAutoConfiguration{
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingRefreshScope
protected static class EurekaClientConfiguration {
@Autowired
private ApplicationContext context;
@Autowired
private AbstractDiscoveryClientOptionalArgs optionalArgs;
/**
* 关键配置:实例化netflix的EurekaClient, 提供最终的服务注册发现功能, 该类具体源码后续分析。
*
* 在该类的构造方法中会做非常多的事情:
* 1、时候会根据eureka.client.serviceUrl的值依次遍历得到eureka server的地址向eureka server发送url路径
* 为/apps的rest请求来获取已注册的服务信息,得到一个Applications对象,最终存储到localRegionApps属性中,
* 如果获取失败则尝试使用当前zone的下一个url地址重新发送请求,直到成果(如果是集群,即eureka.client.serviceUrl的值是逗号分隔的多个地址), 但是最多重试3次
*
* 2、 然后启动一系列的定时任务(cluster resolvers, heartbeat, instanceInfo replicator, fetch),具体源码
* 后面会分析,这里只要知道什么时候第一次获取的服务注册信息、以后怎么更新的服务注册信息,存到哪就可以了。
*/
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config) {
return new CloudEurekaClient(manager, config, this.optionalArgs, this.context);
}
}
下面看EurekaClient
这个类的源码,注意重点是该类的构造方法创建了一系列的定时任务(心跳、更新服务注册信息、根据dns更新serviceUrl、注册实例信息到eurekaserver)以及注册了事件监听器StatusChangeListener用于监听实例自身状态变化,当发生变化时上报服务实例信息到eureka server
1 |
|
- InstanceInfoReplicator
- InstanceInfoReplicator是个任务类,负责将自身的信息周期性的上报到Eureka server;
- 有两个场景触发上报:周期性任务、服务状态变化(onDemandUpdate被调用),因此,在同一时刻有可能有两个上报的任务同时出现;
- 单线程执行上报的操作,如果有多个上报任务,也能确保是串行的;
- 有频率限制,通过burstSize参数来控制;
- 先创建的任务总是先执行,但是onDemandUpdate方法中创建的任务会将周期性任务给丢弃掉;java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129/**
* 用于更新本地instanceinfo并将其复制到远程服务器的任务。这个任务的属性是:
* 1. 使用单个更新线程进行配置,以确保对远程服务器进行连续更新
* 2. 可以通过onDemandUpdate()按需调度更新任务
* 3. 任务处理的速率受到burstSize的限制
* 4. 新的更新任务总是在较早的更新任务之后自动调度。但是,如果启动了随需应变任务,
* 则会丢弃调度的自动更新任务(并在* 新的随需应变更新之后调度新的自动更新任务)。
*/
class InstanceInfoReplicator implements Runnable {
private final DiscoveryClient discoveryClient;
private final InstanceInfo instanceInfo;
private final int replicationIntervalSeconds;
private final ScheduledExecutorService scheduler;
private final AtomicReferencescheduledPeriodicRef;
private final AtomicBoolean started;
private final RateLimiter rateLimiter;
private final int burstSize;
private final int allowedRatePerMinute;
InstanceInfoReplicator(DiscoveryClient discoveryClient,
InstanceInfo instanceInfo,
int replicationIntervalSeconds,
int burstSize) {
this.discoveryClient = discoveryClient;
this.instanceInfo = instanceInfo;
//线程池,core size为1,使用DelayedWorkQueue队列
this.scheduler = Executors.newScheduledThreadPool(1,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-InstanceInfoReplicator-%d")
.setDaemon(true)
.build());
this.scheduledPeriodicRef = new AtomicReference();
this.started = new AtomicBoolean(false);
this.rateLimiter = new RateLimiter(TimeUnit.MINUTES);
this.replicationIntervalSeconds = replicationIntervalSeconds;
this.burstSize = burstSize;
//通过周期间隔,和burstSize参数,计算每分钟允许的任务数
this.allowedRatePerMinute = 60 * this.burstSize / this.replicationIntervalSeconds;
}
/**
* 启动定时任务(通过scheduledPeriodicRef持有的引用可以获得启动的任务,并可以取消该定时任务)
*/
public void start(int initialDelayMs) {
if (started.compareAndSet(false, true)) { // cas更新设置为为已启用状态
/**
* setIsDirty()方法的作用是:设置脏标志,以便在下一次心跳时将实例信息传送到发现服务器,com.netflix.appinfo.InstanceInfo#setIsDirty()内部代码为:
*
* isInstanceInfoDirty = true;
* lastDirtyTimestamp = System.currentTimeMillis();
*/
// 这里是为了启动后立即将实例信息上报到eureka server
instanceInfo.setIsDirty();
// 启动定时任务, 执行run方法中的逻辑
Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
// 持有启动的任务,后续可以获得该任务然后调用其cancel方法取消执行
scheduledPeriodicRef.set(next);
}
}
/**
* com.netflix.discovery.DiscoveryClient#initScheduledTasks类中的statusChangeListener实例状态事件监听器中的notify放啊会调用该方法,当实例状态发生变化时立即同步(取消定时未完成的任务)实例信息到eureka server
*/
public boolean onDemandUpdate() {
// 没有达到频率限制才会执行
if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {
if (!scheduler.isShutdown()) {
//提交一个任务
scheduler.submit(new Runnable() {
public void run() {
logger.debug("Executing on-demand update of local InstanceInfo");
// 获取正在执行的定时任务
Future latestPeriodic = scheduledPeriodicRef.get();
// 如果当前定时任务启动了,但是还没有执行完成,则立即取消任务
if (latestPeriodic != null && !latestPeriodic.isDone()) {
logger.debug("Canceling the latest scheduled update, it will be rescheduled at the end of on demand update");
// 取消本次正在执行的定时任务(仅仅是取消本次任务,下一个任务周期到了仍然会继续执行)
latestPeriodic.cancel(false);
}
// 直接调用run方法将变更的实例信息注册到eureka server
InstanceInfoReplicator.this.run();
}
});
return true;
} else {
//如果超过了设置的频率限制,本次onDemandUpdate方法就提交任务了
logger.warn("Ignoring onDemand update due to stopped scheduler");
return false;
}
} else {
logger.warn("Ignoring onDemand update due to rate limiter");
return false;
}
}
/**
* 关键代码:将服务信息注册到eureka server的实现
*/
public void run() {
try {
// 刷新本地实例配置信息,如果本地配置信息发生了变化则调用com.netflix.appinfo.InstanceInfo#setIsDirty()将当前实例状态改为脏数据状态,以便下一步判断是否发生实例状态变化将实例信息注册到服务端
discoveryClient.refreshInstanceInfo();
// 当前任务启动时start()方法会将实例信息状态设置为脏数据状态
// 判断实例信息是否发生变化
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
// 关键代码:服务注册,将实例信息注册到eureka server
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);
}
}
}
- EurekaDiscoveryClientConfiguration (DiscoveryClient自动化配置)
spring cloud commons与eureka集成的自动化配置核心类
1 | false) (proxyBeanMethods = |
- EurekaRegistration 实例信息, Registration的实现
实现了spring cloud commons项目中的Registration
接口,代表要注册的实例信息
1 | public class EurekaRegistration implements Registration { |
总结spring-cloud-nextflix-eureka-client启动流程:
@EnableDiscoveryClient
引入EnableDiscoveryClientImportSelector
spring-cloud-netflix-eureka-client-2.2.0.RELEASE.jar!\META-INF\spring.factories
中会自动化配置EurekaClientConfigServerAutoConfiguration
、EurekaDiscoveryClientConfigServiceAutoConfiguration
、EurekaClientAutoConfiguration
、EurekaDiscoveryClientConfiguration
、RibbonEurekaAutoConfiguration
等几个类EurekaClientAutoConfiguration
会实例化如下几个类:
EurekaInstanceConfigBean
读取application.yml中eureka.instance为前缀的配置EurekaServiceRegistry
spring cloud commons项目ServiceRegisity接口的实现EurekaAutoServiceRegistration
spring cloud commons项目AutoServiceRegistration接口的实现EurekaClient
netfliex的服务发现、服务注册、心跳实现,构造方法中会发送第一次rest请求,全量获取所有服务注册信息,然后启动一系列定时任务(心跳、刷新服务发现信息、实例状态变化时注册注册实例信息)ApplicationInfoManager
持有实例信息EurekaRegistration
spring cloud commons项目Registration接口的实现