# SpringCloud
| 最简单spring-cloud教程 | 使用Spring Cloud Sleuth和Zipkin进行分布式链路跟踪 |
|---|---|
| alibaba-nacos | 微服务架构之spring boot admin |
# 服务治理
在简单介绍了Spring Cloud和微服务架构之后,下面回归本文的主旨内容,如何使用Spring Cloud来实现服务治理。
由于Spring Cloud为服务治理做了一层抽象接口,所以在Spring Cloud应用中可以支持多种不同的服务治理框架,比如:Netflix Eureka、Consul、Zookeeper。在Spring Cloud服务治理抽象层的作用下,我们可以无缝地切换服务治理实现,并且不影响任何其他的服务注册、服务发现、服务调用等逻辑。
所以,下面我们通过介绍两种服务治理的实现来体会Spring Cloud这一层抽象所带来的好处。
# 1、Spring-Cloud-ureka
首先,我们来尝试使用Spring Cloud Eureka来实现服务治理。
Spring Cloud Eureka是Spring Cloud Netflix项目下的服务治理模块。
而Spring Cloud Netflix项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。
通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端负载均衡(Ribbon)等。
下面,就来具体看看如何使用Spring Cloud Eureka实现服务治理。
# 创建“服务注册中心”
创建一个基础的Spring Boot工程,命名为register,并在pom.xml中引入需要的依赖内容:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.21.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- <version>1.18.8</version>-->
<scope>provided</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
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
兼容性
| Spring Cloud | Spring Boot |
|---|---|
| Finchley | 兼容Spring Boot 2.0.x,不兼容Spring Boot 1.5.x |
| Dalston和Edgware | 兼容Spring Boot 1.5.x,不兼容Spring Boot 2.0.x |
| Camden | 兼容Spring Boot 1.4.x,也兼容Spring Boot 1.5.x |
| Brixton | 兼容Spring Boot 1.3.x,也兼容Spring Boot 1.4.x |
| Angel | 兼容Spring Boot 1.2.x |
通过@EnableEurekaServer注解启动一个服务注册中心提供给其他应用进行对话。这一步非常的简单,只需要在一个普通的Spring Boot应用中添加这个注解就能开启此功能,比如下面的例子:
@EnableEurekaServer
@SpringBootApplication
public class RegisterApplication {
public static void main(String[] args) {
SpringApplication.run(RegisterApplication.class, args);
}
}
2
3
4
5
6
7
8
9
在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需要在application.properties配置文件中增加如下信息:
spring.application.name=eureka-server
server.port=5027
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
2
3
4
5
6
eureka.instance.ip-address和eureka.instance.prefer-ip-address = true同时设置,会用自动获取的ip还是手动设置的?
上文是讨论设置eureka.instance.prefer-ip-address = true ,但没有指定eureka.instance.ip-address 的情况。那么如果两者都被指定了,Spring会怎么处理呢?是使用eureka.instance.ip-address手动设置的IP,还是用上面自动获取的IP呢?
答案是听eureka.instance.ip-address的。
为了与后续要进行注册的服务区分,这里将服务注册中心的端口通过server.port属性设置为5027。启动工程后,访问:
http://localhost:5027
可以看到下面的页面,其中还没有发现任何服务。
# 创建“服务提供方”
下面我们创建提供服务的客户端,并向服务注册中心注册自己。本文我们主要介绍服务的注册与发现,所以我们不妨在服务提供方中尝试着提供一个接口来获取当前所有的服务信息。
首先,创建一个基本的Spring Boot应用。命名为client,在pom.xml中,加入如下配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</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>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- <version>1.18.8</version>-->
<scope>provided</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
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
其次,实现/dc请求处理接口,通过DiscoveryClient对象,在日志中打印出服务实例的相关内容。
package com.ht.client.pojo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.apache.http.HttpStatus;
import java.util.List;
@Getter
@Setter
@ToString
public class HtResult {
/**
*定义jackson对象
*/
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
- 响应业务状态
*/
private Integer status;
/**
- 响应消息
*/
private String msg;
/**
*响应中的数据
*/
private Object data;
public HtResult(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public HtResult(Object data) {
this.status = HttpStatus.SC_OK;
this.msg = "OK";
this.data = data;
}
public static HtResult ok(Object data) {
return new HtResult(data);
}
public static HtResult ok() {
return new HtResult(null);
}
public static HtResult build(Integer status, String msg, Object data) {
return new HtResult(status, msg, data);
}
public static HtResult build(Integer status, String msg) {
return new HtResult(status, msg, null);
}
/**
- 将json结果集转化为CustomResult对象
*
- @param jsonData json数据
- @param clazz CustomResult中的object类型
- @return
*/
public static HtResult formatToPojo(String jsonData, Class<?> clazz) {
try {
if (clazz == null) {
return MAPPER.readValue(jsonData, HtResult.class);
}
JsonNode jsonNode = MAPPER.readTree(jsonData);
JsonNode data = jsonNode.get("data");
Object obj = null;
if (clazz != null) {
if (data.isObject()) {
obj = MAPPER.readValue(data.traverse(), clazz);
} else if (data.isTextual()) {
obj = MAPPER.readValue(data.asText(), clazz);
}
}
return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
} catch (Exception e) {
return null;
}
}
/**
- 没有object对象的转化
*
- @param json
- @return
*/
public static HtResult format(String json) {
try {
return MAPPER.readValue(json, HtResult.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
- Object是集合转化
*
- @param jsonData json数据
- @param clazz 集合中的类型
- @return
*/
public static HtResult formatToList(String jsonData, Class<?> clazz) {
try {
JsonNode jsonNode = MAPPER.readTree(jsonData);
JsonNode data = jsonNode.get("data");
Object obj = null;
if (data.isArray() && data.size() > 0) {
obj = MAPPER.readValue(data.traverse(),
MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
}
return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
} catch (Exception e) {
return null;
}
}
}
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
package com.ht.client.Controller;
import com.ht.client.pojo.HtResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/cloud")
public class CloudClientController {
@Autowired
private DiscoveryClient discoveryClient;
/**
*
- @return
*/
@GetMapping("/getServices")
public HtResult getServices(){
String services = discoveryClient.getServices().toString();
return HtResult.build(HttpStatus.SC_OK,"请求成功",services);
}
}
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
最后在应用主类中通过加上@EnableDiscoveryClient注解,该注解能激活Eureka中的DiscoveryClient实现,这样才能实现Controller中对服务信息的输出。
@EnableDiscoveryClient
@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
2
3
4
5
6
7
8
9
我们在完成了服务内容的实现之后,再继续对application.properties做一些配置工作,具体如下:
spring.application.name=eureka-client
server.port=5028
eureka.client.serviceUrl.defaultZone=http://localhost:5027/eureka/
2
3
通过spring.application.name属性,我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问
eureka.client.serviceUrl.defaultZone属性对应服务注册中心的配置内容,指定服务注册中心的位置。为了在本机上测试区分服务提供方和服务注册中心,使用server.port属性设置不同的端口。
启动该工程后,再次访问:
http://localhost:5027/
可以如下图内容,我们定义的服务被成功注册了。
其中,方括号中的eureka-client就是通过Spring Cloud定义的DiscoveryClient接口在eureka的实现中获取到的所有服务清单。由于Spring Cloud在服务发现这一层做了非常好的抽象,
所以,对于上面的程序,我们可以无缝的从eureka的服务治理体系切换到consul的服务治理体系中区。
# Spring-Cloud-Consul
Spring Cloud Consul项目是针对Consul的服务治理实现。Consul是一个分布式高可用的系统,它包含多个组件,但是作为一个整体,在微服务架构中为我们的基础设施提供服务发现和服务配置的工具。它包含了下面几个特性:
- 服务发现
- 健康检查
- Key/Value存储
- 多数据中心
由于Spring Cloud Consul项目的实现,我们可以轻松的将基于Spring Boot的微服务应用注册到Consul上,并通过此实现微服务架构中的服务治理。
以之前实现的基于Eureka的示例(eureka-client)为基础,我们如何将之前实现的服务提供者注册到Consul上呢?
方法非常简单,我们只需要在pom.xml中将eureka的依赖修改为如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
2
3
4
5
到此为止,我们将eureka-client转换为基于consul服务治理的服务提供者就完成了。
前文我们已经有提到过服务发现的接口DiscoveryClient是Spring Cloud对服务治理做的一层抽象,所以可以屏蔽Eureka和Consul服务治理的实现细节,我们的程序不需要做任何改变,只需要引入不同的服务治理依赖,并配置相关的配置属性就能轻松的将微服务纳入Spring Cloud的各个服务治理框架中。
下面可以尝试让consul的服务提供者运行起来。这里可能读者会问,不需要创建类似eureka-server的服务端吗?
由于Consul自身提供了服务端,所以我们不需要像之前实现Eureka的时候创建服务注册中心,直接通过下载consul的服务端程序就可以使用。
我们可以用下面的命令启动consul的开发模式:
==> Starting Consul agent...
==> Consul agent running!
Version: 'v1.5.0'
Node ID: 'dd5c5367-b27c-b7bb-af5d-d33f51bbd181'
Node name: 'USER-2018SKIHIC'
Datacenter: 'dc1' (Segment: '<all>')
Server: true (Bootstrap: false)
Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)
Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false
==> Log data will now stream in as it occurs:
2019/05/22 10:11:51 [DEBUG] agent: Using random ID "dd5c5367-b27c-b7bb-af5d-
d33f51bbd181" as node ID
2019/05/22 10:11:51 [DEBUG] tlsutil: Update with version 1
2019/05/22 10:11:51 [DEBUG] tlsutil: OutgoingRPCWrapper with version 1
2019/05/22 10:11:51 [DEBUG] tlsutil: IncomingRPCConfig with version 1
2019/05/22 10:11:51 [DEBUG] tlsutil: OutgoingRPCWrapper with version 1
2019/05/22 10:11:51 [INFO] agent: Started gRPC server on 127.0.0.1:8502 (tcp
)
2019/05/22 10:11:51 [INFO] raft: Initial configuration (index=1): [{Suffrage
:Voter ID:dd5c5367-b27c-b7bb-af5d-d33f51bbd181 Address:127.0.0.1:8300}]
2019/05/22 10:11:51 [INFO] raft: Node at 127.0.0.1:8300 [Follower] entering
Follower state (Leader: "")
2019/05/22 10:11:51 [INFO] serf: EventMemberJoin: USER-2018SKIHIC.dc1 127.0.
0.1
2019/05/22 10:11:51 [INFO] serf: EventMemberJoin: USER-2018SKIHIC 127.0.0.1
2019/05/22 10:11:51 [INFO] consul: Adding LAN server USER-2018SKIHIC (Addr:
tcp/127.0.0.1:8300) (DC: dc1)
2019/05/22 10:11:51 [INFO] consul: Handled member-join event for server "USE
R-2018SKIHIC.dc1" in area "wan"
2019/05/22 10:11:51 [DEBUG] agent/proxy: managed Connect proxy manager start
ed
2019/05/22 10:11:51 [INFO] agent: Started DNS server 127.0.0.1:8600 (udp)
2019/05/22 10:11:51 [INFO] agent: Started DNS server 127.0.0.1:8600 (tcp)
2019/05/22 10:11:51 [INFO] agent: Started HTTP server on 127.0.0.1:8500 (tcp
)
2019/05/22 10:11:51 [INFO] agent: started state syncer
2019/05/22 10:11:51 [WARN] raft: Heartbeat timeout from "" reached, starting
election
2019/05/22 10:11:51 [INFO] raft: Node at 127.0.0.1:8300 [Candidate] entering
Candidate state in term 2
2019/05/22 10:11:51 [DEBUG] raft: Votes needed: 1
2019/05/22 10:11:51 [DEBUG] raft: Vote granted from dd5c5367-b27c-b7bb-af5d-
d33f51bbd181 in term 2. Tally: 1
2019/05/22 10:11:51 [INFO] raft: Election won. Tally: 1
2019/05/22 10:11:51 [INFO] raft: Node at 127.0.0.1:8300 [Leader] entering Le
ader state
2019/05/22 10:11:51 [INFO] consul: cluster leadership acquired
2019/05/22 10:11:51 [INFO] consul: New leader elected: USER-2018SKIHIC
2019/05/22 10:11:51 [INFO] connect: initialized primary datacenter CA with p
rovider "consul"
2019/05/22 10:11:51 [DEBUG] consul: Skipping self join check for "USER-2018S
KIHIC" since the cluster is too small
2019/05/22 10:11:51 [INFO] consul: member 'USER-2018SKIHIC' joined, marking
health alive
2019/05/22 10:11:51 [DEBUG] agent: Skipping remote check "serfHealth" since
it is managed automatically
2019/05/22 10:11:51 [INFO] agent: Synced node info
2019/05/22 10:11:51 [DEBUG] agent: Node info in sync
2019/05/22 10:11:53 [DEBUG] agent: Skipping remote check "serfHealth" since
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
我们已经成功地将服务提供者:eureka-client或consul-client注册到了Eureka服务注册中心或Consul服务端上了,同时我们也通过DiscoveryClient接口的getServices获取了当前客户端缓存的所有服务清单,那么接下来我们要学习的就是:如何去消费服务提供者的接口?
# 创建消费者
使用LoadBalancerClient
在Spring Cloud Commons中提供了大量的与服务治理相关的抽象接口,包括DiscoveryClient、这里我们即将介绍的LoadBalancerClient等。对于这些接口的定义我们在上一篇介绍服务注册与发现时已经说过,Spring Cloud做这一层抽象,很好的解耦了服务治理体系,使得我们可以轻易的替换不同的服务治理设施。
从LoadBalancerClient接口的命名中,我们就知道这是一个负载均衡客户端的抽象定义,下面我们就看看如何使用Spring Cloud提供的负载均衡器客户端接口来实现服务的消费。
下面的例子,我们将利用上一篇中构建的eureka-server作为服务注册中心、eureka-client作为服务提供者作为基础。
我们先来创建一个服务消费者工程,命名为:eureka-consumer。并在pom.xml中引入依赖(这里省略了parent和dependencyManagement的配置):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</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>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-actuator</artifactId>-->
<!-- <version>2.1.5.RELEASE</version>-->
<!-- </dependency>-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>1.5.21.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
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
配置application.properties,指定eureka注册中心的地址:
spring.application.name=eureka-consumer
server.port=5029
eureka.client.serviceUrl.defaultZone=http://localhost:5027/eureka/
2
3
4
创建应用主类。初始化RestTemplate,用来真正发起REST请求。@EnableDiscoveryClient注解用来将当前应用加入到服务治理体系中。
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
@Slf4j
@RestController
@RequestMapping("/consumer")
public class ConsumerEurekaController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getServices")
public HtResult getServices(){
ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client");
String url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/cloud/getServices";
log.info("消费的接口地址:{}",url);
log.info("结果:{}",restTemplate.getForObject(url,String.class));
return restTemplate.getForObject(url,String.class);
}
}
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
# Spring-Cloud-Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。它是一个基于HTTP和TCP的客户端负载均衡器。它可以通过在客户端中配置ribbonServerList来设置服务端列表去轮询访问以达到均衡负载的作用。
# 原生Ribbon简介
Ribbon五大组件:
ServerList:定义获取服务器列表
ServerListFilter:对ServerList服务器列表进行二次过滤
ServerListUpdater:定义服务更新策略
IPing:检查服务列表是否存活
IRule:根据算法中从服务列表中选取一个要访问的服务
Ribbon的主要接口:
ILoadBalancer:软件负载平衡器入口,整合以上所有的组件实现负载功能
当Ribbon与Eureka联合使用时,ribbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心中获取服务实例列表。同时它也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来确定服务端是否已经启动。
而当Ribbon与Consul联合使用时,ribbonServerList会被ConsulServerList来扩展成从Consul获取服务实例列表。同时由ConsulPing来作为IPing接口的实现。
我们在使用Spring Cloud Ribbon的时候,不论是与Eureka还是Consul结合,都会在引入Spring Cloud Eureka或Spring Cloud Consul依赖的时候通过自动化配置来加载上述所说的配置内容,所以我们可以快速在Spring Cloud中实现服务间调用的负载均衡。
下面我们通过具体的例子来看看如何使用Spring Cloud Ribbon来实现服务的调用以及客户端均衡负载。
动手试一试
下面的例子,我们将利用之前构建的eureka-server作为服务注册中心、eureka-client作为服务提供者作为基础。而基于Spring Cloud Ribbon实现的消费者,我们可以根据eureka-consumer实现的内容进行简单改在就能完成,具体步骤如下:
根据eureka-consumer复制一个服务消费者工程,命名为:eureka-consumer-ribbon。在pom.xml中增加下面的依赖:
# spring-cloud-zuul
当@EnableZuulProxy与Spring Boot Actuator配合使用时,Zuul会暴露一个路由管理端点/routes。
将所有端点都暴露出来,增加下面的配置:
management.endpoints.web.exposure.include=*
# GateWay
易于监控。可以在网关收集监控数据并将其推送到外部系统进行分析。
易于认证。可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
减少了客户端与各个微服务之间的交互次数。
Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代Netflix ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
# 一些问题
# 启动错误
11:02:22.997 [main] DEBUG org.springframework.boot.context.logging.ClasspathLoggingApplicationListener - Application failed to start with classpath: [file:/D:/ProgramFiles/jdk18/jre/lib/charsets.jar, file:/D:/ProgramFiles/jdk18/jre/lib/deploy.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/access-bridge-64.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/cldrdata.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/dnsns.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/jaccess.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/jfxrt.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/localedata.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/nashorn.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/sunec.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/sunjce_provider.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/sunmscapi.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/sunpkcs11.jar, file:/D:/ProgramFiles/jdk18/jre/lib/ext/zipfs.jar, file:/D:/ProgramFiles/jdk18/jre/lib/javaws.jar, file:/D:/ProgramFiles/jdk18/jre/lib/jce.jar, file:/D:/ProgramFiles/jdk18/jre/lib/jfr.jar, file:/D:/ProgramFiles/jdk18/jre/lib/jfxswt.jar, file:/D:/ProgramFiles/jdk18/jre/lib/jsse.jar, file:/D:/ProgramFiles/jdk18/jre/lib/management-agent.jar, file:/D:/ProgramFiles/jdk18/jre/lib/plugin.jar, file:/D:/ProgramFiles/jdk18/jre/lib/resources.jar, file:/D:/ProgramFiles/jdk18/jre/lib/rt.jar, file:/D:/JAVA_WORK/os/demo/target/classes/, file:/D:/Maven/repository/org/springframework/boot/spring-boot-starter-data-mongodb/2.1.5.RELEASE/spring-boot-starter-data-mongodb-2.1.5.RELEASE.jar,
11:02:23.168 [background-preinit] DEBUG org.hibernate.validator.internal.engine.ValidatorFactoryImpl - HV000234: Using org.hibernate.validator.internal.engine.scripting.DefaultScriptEvaluatorFactory as ValidatorFactory-scoped script evaluator factory.
11:02:23.452 [main] ERROR org.springframework.boot.SpringApplication - Application run failed
java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder.<init>([Ljava/lang/Object;)V
at org.springframework.cloud.bootstrap.BootstrapApplicationListener.bootstrapServiceContext(BootstrapApplicationListener.java:157)
2
3
4
5
6
原因:springcloud和springboot 的依赖版本不配套导致
解决办法:在引入springcloud基础包后将对应的springboot的包改成相对应匹配的包
# 万万没想到SpringBoot竟然这么耗内存
有Spring大靠山在,更新、稳定性、成熟度的问题根本不需要考虑
但是,你必然在服务器上付出:
- 至少一台“服务发现 ”的服务器;
- 可能有一个统一的网关Gateway;
- 可能需要一个用于“分布式配置管理”的配置中心;
- 可能进行“服务追踪”,知道我的请求从哪里来,到哪里去;
- 可能需要“集群监控”;
- 项目上线后发现,我们需要好多服务器,每次在集群中增加服务器时,都感觉心疼;
一个Spring Boot的简单应用,最少1G内存,一个业务点比较少的微服务编译后的JAR会大约50M,而Spring Cloud引入的组件会相对多一些,消耗的资源也会相对更多一些
此时你可能需要JAVA系响应式编程的工具包Vert.x
# 背靠Eclipse的Vert_x
背靠Eclipse的Eclipse Vert.x是一个用于在JVM上构建响应式应用程序的工具包。定位上与Spring Boot不冲突,甚至可以将Vert.x结合Spring Boot使用。
众多Vert.x模块提供了大量微服务的组件,在很多人眼里是一种微服务架构的选择。
华为微服务框架Apache ServiceComb就是以Vert.x为底层框架实现的,在"基准测试网站TechEmpower"中,Vert.x的表现也十分亮眼
# Vert_x总结
Vert.x单个服务打包完成后大约7M左右的JAR,不依赖Tomcat、Jetty之类的容器,直接在JVM上跑
# 其他微服务框架
- SparkJava
jar比较小,大约10M
占内存小,大约30~60MB;
性能还可以,与Spring Boot相仿; - Micronaut
Grails团队新宠;
可以用 Java、Groovy 和 Kotlin 编写的基于微服务的应用程序;
相比Spring Boot已经比较全面;
性能较优,编码方式与Spring Boot比较类似;
启动时间和内存消耗方面比其他框架更高效;
多语言;
依赖注入;
内置多种云本地功能;
很新,刚发布1.0.0 - Javalin
上手极为容易;
灵活,可以兼容同步和异步两种编程思路;
JAR小,4~5M;
多语言;
有KOA的影子;
只有大约2000行源代码,源代码足够简单,可以理解和修复;
符合当今趋势;
多语言;
嵌入式服务器Jetty; - Quarkus
启动快;
JAR小,大约10M;
文档很少;