SpringCloud 详解(三)

Eureka

在传统的 RPC 远程调用框架中,管理每个服务之间的依赖关系比较复杂,管理也就比较复杂,所以需要使用服务治理,管理服务之间的依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前服务器的信息,比如服务地址和通讯地址以别名方式注册到注册中心上。消费者以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地 RPC 调用。

Eureka 采用了 CS 的设计架构,Eureka Sever 作为服务注册功能的服务器,它是服务注册中心。而系统中的其它微服务,使用 Eureka Client 连接到 Eureka Server 并维持心跳连接。这样就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。

搭建 Eureka Server

准备工作

创建一个 Module,一个空的 Maven 项目。并导入下面的依赖。

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
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>ml.guest997</groupId>
<artifactId>Commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
</dependencies>

创建主启动类。

1
2
3
4
5
6
7
8
9
10
11
12
13
package ml.guest997;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer //启动 Eureka Server
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class, args);
}
}

编写配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 7001

eureka:
instance:
hostname: localhost #eureka server 的实例名称
client:
#false 表示不向注册中心注册自己。
register-with-eureka: false
#false 表示自己就是注册中心,我的职责就是维护服务实例,并不需要去检索服务。
fetch-registry: false
service-url:
#设置 eureka server 交互的地址,查询和注册服务都需要依赖这个地址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

测试

运行模块之后,浏览器访问 127.0.0.1:7001,出现如下页面就算成功。

注册服务

导入依赖

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

编写配置文件

1
2
3
4
5
6
7
8
9
10
11
spring:
application:
name: order #一定要配置名字,要不然服务注册中心会找不到服务。

eureka:
client:
register-with-eureka: true
#是否从 Eureka Server 抓取已有的注册信息,默认为 true。单节点无所谓,集群必须设置为 true 才能配合 ribbon 使用负载均衡。
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka

启动 Client

1
2
3
4
5
6
7
8
9
10
11
12
13
package ml.guest997;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient //启动 Eureka Client
public class Order {
public static void main(String[] args) {
SpringApplication.run(Order.class, args);
}
}

测试

在将之前的两个模块都按照上面的步骤进行服务注册之后,启动所有的模块,浏览器访问 127.0.0.1:7001,就会发现之前的两个模块都注册进去了。

搭建 Eureka 集群(相互注册)

模拟多个 Eureka Server 地址

修改 hosts 文件。

1
2
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

搭建另一个 Eureka Server

跟上面的步骤是一样的,就不再赘述了。

更改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 7001

eureka:
instance:
hostname: eureka7001.com #eureka server 的实例名称
client:
#false 表示不向注册中心注册自己。
register-with-eureka: false
#false 表示自己就是注册中心,我的职责就是维护服务实例,并不需要去检索服务。
fetch-registry: false
service-url:
#设置 eureka server 交互的地址,查询和注册服务都需要依赖这个地址。
defaultZone: http://eureka7002.com:7002/eureka/
1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 7002

eureka:
instance:
hostname: eureka7002.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
#指向其它 eureka server,如果有多个,用逗号隔开。
defaultZone: http://eureka7001.com:7001/eureka/

测试

浏览器分别访问:eureka7001.com:7001 和 eureka7002.com:7002 会发现,在 DS Replicas 项下各自都多了一个相互的域名地址。

注册服务2

要想将之前的模块注册进 Eureka 集群中,只需要改下配置文件,将 defaultZone 设置为多个即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 80

spring:
application:
name: order

eureka:
client:
register-with-eureka: true
#是否从 Eureka Server 抓取已有的注册信息,默认为 true。单节点无所谓,集群必须设置为 true 才能配合 ribbon 使用负载均衡。
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka

支付模块集群配置

需要新建一个支付模块,也是更之前一样的步骤,就不再赘述了。注意:记得修改新模块的端口。

修改订单模块的 Config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package ml.guest997.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //配置负载均衡
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

修改订单模块的 Controller

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
package ml.guest997.controller;

import ml.guest997.pojo.CommonResult;
import ml.guest997.pojo.Payment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class OrderController {
public static final String PAYMENT_URL = "http://PAYMENT"; //不再是固定地址,而是 eureka 的服务名地址。

@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/payment/add")
public CommonResult add(Payment payment) {
return restTemplate.postForObject(PAYMENT_URL + "/payment/add", payment, CommonResult.class);
}

@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
}

修改支付模块的 Controller

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
package ml.guest997.controller;

import lombok.extern.slf4j.Slf4j;
import ml.guest997.pojo.CommonResult;
import ml.guest997.pojo.Payment;
import ml.guest997.service.PaymentService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort; //为了方便显示负载均衡是否成功

@PostMapping("/payment/add")
public CommonResult add(@RequestBody Payment payment) {
int res = paymentService.add(payment);
log.info("插入结果:" + res);
if (res > 0) {
return new CommonResult(200, "插入数据库成功,支付服务端口为:" + serverPort, res);
} else {
return new CommonResult(400, "插入数据库失败");
}
}

@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
if (payment != null) {
return new CommonResult(200, "查询成功", payment);
} else {
return new CommonResult(404, "没有对应记录,查询 id 为:" + id);
}
}
}

测试

启动所有的模块,浏览器访问 127.0.0.1/consumer/payment/add?serial=Guest004,会发现页面显示的端口在来回切换,就说明负载均衡功能生效了。