Eureka Server集群部署

部署环境:

  • IDEA
  • Windows10
  • Eureka-1.10.7
  • Gradle

1.依赖文件

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
plugins {
id 'org.springframework.boot' version '2.3.7.RELEASE'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
}

group = 'com.isspark.cloud'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
// mavenCentral()
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/repository/jcenter'}
}

ext {
set('springCloudVersion', "Hoxton.SR9")
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}

test {
useJUnitPlatform()
}

2.配置文件dev.yml

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
# 应用1 dev.yml
server:
servlet:
context-path: /cloud
port: 8761
spring:
profiles:
active: dev
application:
name: cloud1
# 应用2 dev.yml
server:
servlet:
context-path: /cloud
port: 8762
spring:
profiles:
active: dev
application:
name: cloud2
# 应用3 dev.yml
server:
servlet:
context-path: /cloud
port: 8763
spring:
profiles:
active: dev
application:
name: cloud3

3.配置Eureka在application-dev.yml文件中

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
## 应用1 application-dev.yml
eureka:
instance:
prefer-ip-address: false
hostname: "eureka-cluster1"
server:
enable-self-preservation: true
client:
# 是否将此实例注册到注册中心
register-with-eureka: true
# 此实例是否从注册中心取值
fetch-registry: true
service-url:
defaultZone: http://eureka-cluster2:8762/cloud/eureka
# spring cloud 引入了freemarker,需要加载该配置能看到Eureka管理页面
spring:
freemarker:
prefer-file-system-access: false

## 应用2 application-dev.yml
eureka:
instance:
prefer-ip-address: false
hostname: "eureka-cluster2"
server:
enable-self-preservation: true
client:
# 是否将此实例注册到注册中心
register-with-eureka: true
# 此实例是否从注册中心取值
fetch-registry: true
service-url:
defaultZone: http://eureka-cluster1:8761/cloud/eureka
# spring cloud 引入了freemarker,需要加载该配置能看到Eureka管理页面
spring:
freemarker:
prefer-file-system-access: false

## 应用1 application-dev.yml
eureka:
instance:
prefer-ip-address: false
hostname: "eureka-cluster3"
server:
enable-self-preservation: true
client:
# 是否将此实例注册到注册中心
register-with-eureka: true
# 此实例是否从注册中心取值
fetch-registry: true
service-url:
defaultZone: http://eureka-cluster1:8761/cloud/eureka
# spring cloud 引入了freemarker,需要加载该配置能看到Eureka管理页面
spring:
freemarker:
prefer-file-system-access: false

4.配置windows的host配置

1
2
3
127.0.0.1 eureka-cluster1
127.0.0.1 eureka-cluster2
127.0.0.1 eureka-cluster3

5.访问http://localhost:8761/cloud/,如下截图
Eureka架构

需要注意上面的配置中defaultZone的配置地址上下文一定要加上dev.yaml配置的context-path,不然会注册失败。

上面我们启动三个实例eureka-cluster1-3,启动后我们访问 http://localhost:8761/cloud/ 可以看到注册的实例。需要注意我们开启了Eureka自我保护模式,一旦下线超过85%就会提示开启保护模式,Eureka服务端将不会将实例信息从注册信息表中删除。

Eureka服务端启动过程

从@EnableEurekaServer开始

查看EnableEurekaServer注解源码:

1
2
3
4
5
6
7
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {

}

关注@Import注解,引入了配置类EurekaServerMarkerConfiguration,并把该配置类下的@Bean加入到容器中,源码如下:

1
2
3
4
5
6
7
8
9
@Configuration(proxyBeanMethods = false)
public class EurekaServerMarkerConfiguration {
@Bean
public Marker eurekaServerMarkerBean() {
return new Marker();
}
class Marker {
}
}

这里我们看到EurekaServerMarkerConfiguration 配置类把一个名为eurekaServerMarkerBean 的Mark类加入容器。该类的作用就是一个激活标记。后面有用到。

从EurekaServerAutoConfiguration开始

在spring-cloud-netflix-eureka-server-2.2.6.RELEASE.jar包的META—INFO目录下,我们看到spring.factories文件配置如下:

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

这表示EurekaServerAutoConfiguration会在Spring启动的时候会进行实例化。

spring factories是spring boot提供的一种扩展机制,和SPI类似。知识点参考:Creating Your Own Auto-configurationCreating Extensible Applications

下面我们再看下EurekaServerAutoConfiguration,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
//省略
@Bean
public HasFeatures eurekaServerFeature() {
return HasFeatures.namedFeature("Eureka Server",
EurekaServerAutoConfiguration.class);
}
}
  • @Configuration注解声明该类是个配置类。
  • @Import 注解引入了一个配置类EurekaServerInitializerConfiguration,Eureka初始化启动类;
  • @ConditionalOnBean 表明只有在SPRING容器中有EurekaServerMarkerConfiguration.Marker类的时候才加载EurekaServerAutoConfiguration。
  • @EnableConfigurationProperties,加载Eureka仪表盘配置和实例注册配置。
  • @PropertySource,加载server.properties配置

我们先看EurekaServerInitializerConfiguration,发现实现了三个类ServletContextAware, SmartLifecycle, Ordere。代码如下:

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
@Configuration(proxyBeanMethods = false)
public class EurekaServerInitializerConfiguration
implements ServletContextAware, SmartLifecycle, Ordered {

private static final Log log = LogFactory
.getLog(EurekaServerInitializerConfiguration.class);

@Autowired
private EurekaServerConfig eurekaServerConfig;

private ServletContext servletContext;

@Autowired
private ApplicationContext applicationContext;

@Autowired
private EurekaServerBootstrap eurekaServerBootstrap;

private boolean running;

private int order = 1;

@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}

@Override
public void start() {
new Thread(() -> {
try {
// TODO: is this class even needed now?
eurekaServerBootstrap.contextInitialized(
EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");

publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
EurekaServerInitializerConfiguration.this.running = true;
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}).start();
}
//省略
}

我们看到该接口实现了SmartLifecycle,表示在SPRING容器加载完所有bean之后,就会回调实现该接口的类的start()方法。start()方法中主要执行了eurekaServerBootstrap.contextInitialized()来初始化Eureka的上下问。以及发布了两个事件(我们可以通过监听该事件来实现一个自定义的操作)。

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
public void contextInitialized(ServletContext context) {
try {
//初始化环境
initEurekaEnvironment();
//初始化上下文
initEurekaServerContext();

context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
}
catch (Throwable e) {
log.error("Cannot bootstrap eureka server :", e);
throw new RuntimeException("Cannot bootstrap eureka server :", e);
}
}

protected void initEurekaServerContext() throws Exception {
// For backward compatibility
JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
XStream.PRIORITY_VERY_HIGH);
XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
XStream.PRIORITY_VERY_HIGH);

if (isAws(this.applicationInfoManager.getInfo())) {
this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
this.eurekaClientConfig, this.registry, this.applicationInfoManager);
this.awsBinder.start();
}

EurekaServerContextHolder.initialize(this.serverContext);

log.info("Initialized server context");

// 从其他节点复制注册信息
int registryCount = this.registry.syncUp();
// 将当前Server状态改成UP
this.registry.openForTraffic(this.applicationInfoManager, registryCount);

// 注册所有监控统计信息
EurekaMonitors.registerAllStats();
}

上面是contextInitialized方法中的内容,主要初始化环境和上下文。我们先看this.registry.syncUp()

参考资料

【1】SpringCloud的server端之EurekaServerAutoConfiguration介绍

【2】Creating Your Own Auto-configuration

【3】Creating Extensible Applications

【4】 Eureka Wiki