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 支付寶

支付寶