0%

服务治理Spring Cloud Eureka

2018年7月,官方宣布Eureka 2.x停止开源计划了。

详细请看:
http://github.com/Netflix/eureka/wiki

尴尬了。

介绍

Eureka是Spring Cloud中的一个负责服务注册与发现的组件。遵循着CAP理论中的A(可用性)、P(分区容错性)。

一个Eureka中分为eureka server和eureka client。其中eureka server是作为服务的注册与发现中心。eureka client既可以作为服务的生产者,又可以作为服务的消费者。

如图:

简单例子

  • 工具:idea
  • JDK:1.8

服务注册中心

创建一个基础工程Spring Boot,命名:eureka-server,pom.xml内容如下:

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!-- springboot版本与springcloud须匹配 -->
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<!-- springboot版本与springcloud须匹配 -->
<spring.boot.version>2.1.4.RELEASE</spring.boot.version>
<spring.cloud.version>Greenwich.SR1</spring.cloud.version>
<spring.maven.version>2.4.1</spring.maven.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.maven.version}</version>
</plugin>
</plugins>
</build>
</project>

打开application.properties,内容设置为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#服务端口
server.port=8077
#服务名称
spring.application.name=eureka-server
#服务地址
eureka.instance.hostname=localhost

#不向注册中心注册自己
eureka.client.register-with-eureka=false
#取消检索服务
eureka.client.fetch-registry=false
#开启注册中心的保护机制,默认是开启
eureka.server.enable-self-preservation=true
#设置保护机制的阈值,默认是0.85。
eureka.server.renewal-percent-threshold=0.5
#注册中心路径,如果有多个eureka server,在这里需要配置其他eureka server的地址,用","进行区分,如"http://address:8888/eureka,http://address:8887/eureka"
eureka.client.service-url.default-zone=http://${eureka.instance.hostname}:${server.port}/eureka

在EurekaServerApplication.java类,加入:

1
@EnableEurekaServer

如:
EurekaServerApplication.java类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}

}

启动后,打开浏览器输入:http://127.0.0.1:8077/ ,可以看到Eureka面板,其中 Instances currently registered with Eureka 为空,那就证明注册中心没有注册任何服务。

如图:

服务提供者

创建一个基础工程Spring Boot,命名:Demo,pom.xml内容如下:

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!-- springboot版本与springcloud须匹配 -->
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.demo1</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<!-- springboot版本与springcloud须匹配 -->
<spring.boot.version>2.1.4.RELEASE</spring.boot.version>
<spring.cloud.version>Greenwich.SR1</spring.cloud.version>
<spring.maven.version>2.4.1</spring.maven.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>

</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<!-- jar包的名字,默认为project/name节点 -->
<!--
<finalName>demo</finalName>
-->
<plugins>
<!--加入插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.maven.version}</version>
<!-- 打jar包注意事项 -->
<configuration>
<!-- 入口类的全限包名 -->
<mainClass>com.demo1.demo</mainClass>
<layout>JAR</layout>
</configuration>
</plugin>
</plugins>
<resources>
<!-- 打包时将jsp文件拷贝到META-INF目录下-->
<resource>
<!-- 指定resources插件处理哪个目录下的资源文件 -->
<directory>src/main/webapp</directory>
<!--注意此次必须要放在此目录下才能被访问到-->
<targetPath>META-INF/resources</targetPath>
<includes>
<include>**/**</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/**</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
</build>
</project>

打开application.properties,内容设置为:

1
2
3
4
5
6
7
#服务端口
server.port=9091

#服务名称
spring.application.name=hello-service
#eureka注册中心地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8077/eureka/

在DemoApplication.java类,加入:

1
@EnableDiscoveryClient

如:
DemoApplication.java类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.demo1.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}

}

创建HelloController.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
package com.demo1.demo.controller;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class HelloController {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private DiscoveryClient client;//服务发现客户端


@RequestMapping("/hello")
public String hello() {
System.out.println("Hello Spring Boot");
return "Hello Spring Boot";
}
}

启动后,日志输出:

1
2
2020-12-12 22:49:40.115  INFO 4408 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_HELLO-SERVICE/PC-20190831IKWQ:hello-service:9091: registering service...
2020-12-12 22:49:40.514 INFO 4408 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_HELLO-SERVICE/PC-20190831IKWQ:hello-service:9091 - registration status: 204

转到Eureka面板,在Instances currently registered with Eureka看到服务的注册信息,

如图:

服务发现与消费

服务发现的任务由Eureka的客户端完成。

服务消费的任务由Ribbon完成。

Ribbon是一个基于HTTP和TCP的客户端负载均衡器,当将Ribbon和Eureka一起使用时,Ribbon会到Eureka注册中心去获取服务端列表,然后进行轮询访问以到达负载均衡的作用,客户端负载均衡也需要心跳机制去维护服务端清单的有效性,当然这个过程需要配合服务注册中心一起完成。

为了实验Ribbon的客户端负载均衡功能,以命令来启动两个不同端口的hello-servicer(服务提供方),

1
2
3
4
cd D:\Workspaces\demo\target\

java -jar demo-0.0.1-SNAPSHOT.jar --server.port=9001
java -jar demo-0.0.1-SNAPSHOT.jar --server.port=9002

转到Eureka面板,在Instances currently registered with Eureka看到服务的注册信息,

如图:

创建一个基础工程Spring Boot,命名: demo-consumer,pom.xml内容如下:

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!-- springboot版本与springcloud须匹配 -->
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-consumer</name>
<description>Demo project for Spring Boot</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<!-- springboot版本与springcloud须匹配 -->
<spring.boot.version>2.1.4.RELEASE</spring.boot.version>
<spring.cloud.version>Greenwich.SR1</spring.cloud.version>
<spring.maven.version>2.4.1</spring.maven.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.maven.version}</version>
</plugin>
</plugins>
</build>
</project>

打开application.properties,内容设置为:

1
2
3
4
5
6
#服务端口
server.port=9101
#服务名称
spring.application.name=ribbon-consumer
#eureka注册中心地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8077/eureka/

在DemoConsumerApplication.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
package com.example.democonsumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@SpringBootApplication
public class DemoConsumerApplication {

@Bean
// 让RestTemplate在请求时拥有客户端负载均衡的能力
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}

public static void main(String[] args) {
SpringApplication.run(DemoConsumerApplication.class, args);
}
}

创建ConsumerController.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
package com.example.democonsumer.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@RestController
public class ConsumerController {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private RestTemplate restTemplate;

@RequestMapping(value = "/ribbon-consumer", method = RequestMethod.GET)
public String helloConsumer() {
//服务提供方服务名称
String memberUrl = "http://HELLO-SERVICE/hello";
String result = restTemplate.getForObject(memberUrl, String.class);
System.out.println("访问结果" + result);
return result;
}
}

启动后,日志输出:

1
2
2020-12-13 13:04:59.166  INFO 6620 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_RIBBON-CONSUMER/PC-20190831IKWQ:ribbon-consumer:9101: registering service...
2020-12-13 13:04:59.213 INFO 6620 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_RIBBON-CONSUMER/PC-20190831IKWQ:ribbon-consumer:9101 - registration status: 204

转到Eureka面板,在Instances currently registered with Eureka看到服务的注册信息,

如图:

打开网址,输入:http://127.0.0.1:9101/ribbon-consumer , 日志输出:

1
2
3
4
5
6
2020-12-13 13:07:07.652  INFO 6620 --- [io-9101-exec-10] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client HELLO-SERVICE initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=HELLO-SERVICE,current list of Servers=[PC-20190831IKWQ:9001, PC-20190831IKWQ:9002],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;	Instance count:2;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
},Server stats: [[Server:PC-20190831IKWQ:9001; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
, [Server:PC-20190831IKWQ:9002; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@6d194ff3

访问结果Hello Spring Boot

从日志可以看到,Ribbon输出了当前客户端维护的服务列表情况:

1
NFLoadBalancer:name=HELLO-SERVICE,current list of Servers=[PC-20190831IKWQ:9001, PC-20190831IKWQ:9002])

Ribbon按此服务列表信息进行轮询访问,以实现基于客户端的负载均衡。在服务消费方,多发送几次请求:http://127.0.0.1:9101/ribbon-consumer ,可以观察判断客户端的负载均衡是否有效。

layicr 微信

微信

layicr 支付宝

支付宝