【Spring Cloud】Eureka

Eureka 简介

服务治理

Spring Cloud封装了Netflix公司开发的Eureka模块来实现服务治理

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

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

服务注册与服务发现

服务注册:在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。

服务发现:另一方(消费者服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,之后再实现本地RPC调用。

总结:

  • 服务注册:将服务信息注册进注册中心
  • 服务发现:从注册中心上获取服务信息

RPC远程调用框架核心设计思想在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何RPC远程框架中,都会有一个注册中心存放服务地址相关信息(接口地址)

img

Eureka包含两个组件:Eureka ServerEureka Client

  • Eureka Server:提供服务注册服务。各个微服务节点通过配置启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
  • Eureka Client:注册到注册中心的服务。是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器(Ribbon)。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)

Eureka Server

  1. 创建名为cloud-eureka-server7001的Maven工程
  2. 修改pom.xml,添加依赖:
1
2
3
4
5
6
7
8
9
10
11
<!-- 老版本(2018)-->
<dependency>
<groupid>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

<!-- 新版本(2020.2)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
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
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.zhao.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>cloud-eureka-server7001</artifactId>

<dependencies>
<!-- eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.lun.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- boot web actuator -->
<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>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>

</project>
  1. 修改application.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 7001

eureka:
instance:
hostname: locathost #eureka服务端的实例名称
client:
# alse表示不向注册中心注册自己。
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  1. 创建主启动类
1
2
3
4
5
6
7
8
9
10
11
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer // 标注当前Spring Boot服务为Server端
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class, args);
}
}
  1. 测试运行EurekaMain7001,浏览器输入http://localhost:7001/回车,会查看到Spring Eureka服务主页。

Eureka Client

创建Eureka Client端:cloud-provider-payment8001订单提供者服务,并注册进Eureka Server成为服务提供者provider。

  1. 创建cloud-provider-payment8001模块(订单提供者服务)作为Eureka Client端
  2. 修改pom.xml,添加依赖:
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  1. 修改application.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
application:
name: cloud-payment-service

eureka:
client:
# 表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合Ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka # 指向Eureka Server的地址
instance:
instance-id: payment8001 # 设置可读性高的名称,将显示在Eureka主页服务列表里,代替ip地址
prefer-ip-address: true # 将鼠标指针移至payment8001名下,会有IP地址提示
  1. 创建主启动类
1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient // 标注当前Spring Boot服务为Client端
public class PaymentMain001 {

public static void main(String[] args) {
SpringApplication.run(PaymentMain001.class, args);
}
}
  1. 测试:启动cloud-provider-payment8001cloud-eureka-server7001工程。浏览器输入 - http://localhost:7001/。

Eureka 集群原理

img

  • 服务注册:将服务信息注册进注册中心
  • 服务发现:从注册中心上获取服务信息

实质:存key服务名,取value调用地址

流程:

  • 先启动Eureka注册中心
  • 启动服务提供者payment支付服务
  • 支付服务启动后会把自身信息(以服务地址URL以别名方式注册进Eureka)
  • 消费者order服务在需要调用接口时,使用服务别名去注册中心获取实际的RPC远程调用地址
  • 消费者调用地址后,底层实际是利用HttpClient技术实现远程调用
  • 消费者获得的服务地址后会缓存在本地jvm内存中,默认每间隔30秒更新—次服务调用地址

问题:微服务RPC远程服务调用最核心的是什么?

答:高可用,试想注册中心假设只有一个,万一它出故障了,会导致整个为服务环境不可用。

解决办法:搭建Eureka注册中心集群,实现负载均衡 + 故障容错。互相注册,相互守望

Eureka 集群环境构建

创建cloud-eureka-server7002工程作为第二个Eureka Server,与cloud-eureka-server7001相互注册。

首先修改本地的host映射:

1
2
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
  1. 修改cloud-eureka-server7001的配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 7001

eureka:
instance:
hostname: eureka7001.com# Eureka服务端的实例名称
client:
register-with-eureka: false # false表示不向注册中心注册自己。
fetch-registry: false # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
# 指向其它Eureka集群
defaultZone: http://eureka7002.com:7002/eureka/
# 单机就是7001自己
# defaultZone: http://eureka7001.com:7001/eureka/
  1. 修改cloud-eureka-server7002配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 7002

eureka:
instance:
hostname: eureka7002.com # Eureka服务端的实例名称
client:
register-with-eureka: false # false表示不向注册中心注册自己。
fetch-registry: false # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
# 指向其它Eureka集群
defaultZone: http://eureka7001.com:7001/eureka/
# 单机就是7002自己
# defaultZone: http://eureka7002.com:7002/eureka/
  1. 将支付服务8001微服务注册到上面2台Eureka集群配置中
1
2
3
4
5
6
7
8
9
10
11
eureka:
client:
# 表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001 # 设置可读性高的名称,将显示在Eureka主页服务列表里,代替ip地址
prefer-ip-address: true # 将鼠标指针移至payment8001名下,会有IP地址提示

支付服务集群配置:https://blog.csdn.net/u011863024/article/details/114298270

  1. 支付服务搭建集群后,订单服务需要开启负载均衡功能,此注解在Spring Cloud提供的LoadBalacer包下
1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.cloud.client.loadbalancer.LoadBalanced;

@Configuration
public class ApplicationContextConfig {

// 赋予RestTemplate负载均衡的能力
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}

}

此时PAYMENT_URL 需要写成支付服务对应的服务名: http://CLOUD-PAYMENT-SERVICE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Slf4j
@RestController
public class OrderController {
// public static final String PAYMENT_URL = "http://localhost:8001";
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

@Autowired
private RestTemplate restTemplate;

@GetMapping(value = "/consumer/payment/create")
public CommonResult create(Payment payment) {
// 以JSON的形式发送POST请求给8001端口,payment数据信息保存在request body中,因此对应的Controller需要加@RequestBody注解
return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
}

@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
log.info("1211");

return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
}

Discovery 服务发现

对于注册进Eureka里面的微服务,可以通过服务发现来获得该服务的信息。

  1. 修改cloud-provider-payment8001的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
@RestController
@Slf4j
public class PaymentController{
...

// 用于获取注册中心的服务信息
@Resource
private DiscoveryClient discoveryClient;

...

@GetMapping(value = "/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("*****element: "+element);
}

List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}

return this.discoveryClient;
}
}
  1. 修改主启动类
1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient // 开启服务发现功能
public class PaymentMain001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain001.class, args);
}
}

Eureka 自我保护机制

保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。

如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:

1
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THANTHRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUSTTO BE SAFE

导致原因:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。

Eureka属于CAP里面的AP类型(ZooKeeper属于CP),即保证服务高可用,即使数据不一致也必须要可用。

为什么会产生Eureka自我保护机制:为了EurekaClient可以正常运行,在与Eureka Server网络不通情况下,Eureka Server不会立刻将Eureka Client服务剔除

什么是自我保护模式:默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与Eureka Server之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。

自我保护机制:默认情况下Eureka Client定时向Eureka Server端发送心跳包。如果Eureka Server在一定时间内(默认90秒)没有收到Eureka Client发送心跳包,便会直接从服务注册列表中剔除该服务,但是在短时间(90秒)内丢失了大量的服务实例心跳,这时候Eureka Server会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是Eureka Client未出现宕机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误,因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的)。

在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。

综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

三个注册中心异同点

组件名 语言 CAP 对外暴露接口 Spring Cloud集成
Eureka Java AP 可配支持 HTTP
Consul Go CP 支持 HTTP/DNS
Zookeeper Java CP 支持客户端 已集成

CAP:

  • C:Consistency (强一致性)
  • A:Availability (可用性)
  • P:Partition tolerance (分区容错性)

img

最多只能同时较好的满足两个。

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求。因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:

  • CA - 单点集群,满足—致性,可用性的系统,通常在可扩展性上不太强大。
  • CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
  • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

AP 架构(Eureka)

当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。结论:违背了一致性C的要求,只满足可用性和分区容错,即AP。

img

CP 架构(ZooKeeper/Consul)

当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。结论:违背了可用性A的要求,只满足一致性和分区容错,即CP。例如ZooKeeper在进行Leader选举时不能响应客户端的请求,此时不满足高可用性A,而是选择了满足数据一致性。

img