image-20200704103126138

什么是服务治理?

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

在传统的rpc远程调用框架中,管理每个服务服务之间的依赖关系比较复杂,管理比较复杂,就像我们之前的基础项目一样,虽然能实现功能,但是一旦服务多了起来就非常难管理,所以需要使用服务治理,管理服务与服务之间的依赖关系,可以实现服务调用,负载均衡,容错等,实现服务发现与注册。

什么是服务注册与发现

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

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址 通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC。远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))

image-20200706142638527

Eureka两组件

Eureka Server

Eureka Server提供服务注册服务,各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在页面中直观看到。

Eureka Client

Eureka Client是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的,使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)

构建单机版Eureka

Eureka Server

创建新模块cloud-eureka-server7001

image-20200706143743559

pom

image-20200706143911591

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
<dependencies>
<!-- eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.kylin</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</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>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

创建application.yml配置文件

image-20200706145238309

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

eureka:
instance:
hostname: localhost #eureka服务端的实例名字
client:
register-with-eureka: false #表示不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
#设置于Eureka Serve交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  • fetch-registry: 检索服务选项,当设置为True(默认值)时,会进行服务检索,注册中心不负责检索服务。
  • register-with-eureka: 服务注册中心也会将自己作为客户端来尝试注册自己,为true(默认)时自动生效
  • eureka.client.serviceUrl.defaultZone是一个默认的注册中心地址。配置该选项后,可以在服务中心进行注册。

由于这个服务模块自己就是注册中心,职责是维护服务实例,并不需要去检索服务,和在服务中心注册自己

所以一般情况下,当我们设置服务为注册中心时,需要关闭eureka.client.fetch-registry与eureka.client.register-with-eureka,在做注册中心集群的时候,register-with-eureka必须打开,因为需要进行相互注册,不然副本无法可用

主启动类

使用@EnableEurekaServer开启Eureka服务

image-20200706144805891

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
//启动Eureka服务
@EnableEurekaServer
public class EurekaMain7001 {

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

测试

接着启动主启动类,访问http://localhost:7001/进行测试

image-20200706151131294

配置eureka.client.service-url.defaultZone为什么要加eureka做后缀。而在访问eureka服务的时候却不加这个内容到url中,通过localhost:7001就可以访问控制台?这是因为在eureka server的实现中存在一个web请求过滤器,其url模式就是[/eureka/*]

Eureka Client Provider

EurekaClient端cloud-provider-payment8001将注册进EurekaServer成为服务提供者provider

pom

修改pom.xml文件,导入Eureka Client依赖。

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

image-20200706152014474

yml

配置文件添加Eureka配置

image-20200706170014681

1
2
3
4
5
6
7
8
9
eureka:
client:
# 表示是否将自己注册进EurekaServer 默认为true
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息 ,默认为true。 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
#设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://localhost:7001/eureka
  • register-with-eureka: true 由于我们是服务的提供者所以要注册进注册中心

  • fetch-registry: true 是否从EurekaServer抓取已有的注册信息 ,默认为true。 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡

注册的名字就是spring.application.name配置的名字

image-20200706153223911

主启动类

添加@EnableEurekaClient注解开启

image-20200706152756213

测试

启动测试,先启动Eureka Server在启动Eureka Client

image-20200706153045140

输入地址http://localhost:7001/访问页面,成功

image-20200706153146502

Eureka的自我保护机制

image-20200706153244804

Eureka Client Consumer

EurekaClient端cloud-consumer-order80将注册进EurekaServer成为服务消费者consumer

pom

修改pom.xml文件,导入Eureka Client依赖。

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

image-20200706161226822

yml

添加服务名称和eureka配置

image-20200706170056586

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

spring:
application:
name: cloud-order-service #服务名称

eureka:
client:
# 表示是否将自己注册进EurekaServer 默认为true
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息 ,默认为true。 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
#设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://localhost:7001/eureka

主启动类

添加@EnableEurekaClient注解开启

image-20200706161511654

测试

启动项目进行测试,成功

image-20200706161727588

image-20200706161814520

单机版Eureka代建成功!

image-20200706162525477

构建集群版Eureka Server

为什么要使用集群版?

微服务RPC远程服务调用最核心的是什么?高可用,当我们的注册中心只有一个only one时,它如果出现了故障会导致整个服务环境不可用,所以要搭建Eureka注册中心集群,实现负载均衡+故障容错

image-20200706162837811

7001在7002中注册,7002在7001注册,互相注册,相互守望。

在单机版的基础之上进行修改和添加

Eureka Server

创建新模块cloud-eureka-server7002

image-20200706163459298

pom

cloud-eureka-server7001一样

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
<dependencies>
<!-- eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.kylin</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</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>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

新建application.yml编写配置文件

由于defaultZone中都是使用的localhost所以配置host文件进行一下区分(可以不用配置,为了好看)

image-20200706163937883

作用就是当输入eureka7001.com或者eureka7002.com时实际代表的还是127.0.0.1也就是localhost

image-20200706164326606

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

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

同时修改7001的Eureka Server配置文件

image-20200706164518498

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

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

从而达到相互注册,互相守望!

主启动类

使用@EnableEurekaServer开启Eureka服务

image-20200706164640109

测试

分别启动7001,和7002。分别访问各自的页面查看效果http://eureka7001.com:7001/http://eureka7001.com:7002/

image-20200706164952978

image-20200706165004584

成功!

Eureka Client Provider

将支付服务8001微服务发布到上面2台Eureka集群配置中

yml

修改8001,yml配置文件的Eureka服务地址

image-20200706170216206

defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com/eureka #集群版

Eureka Client Consumer

将订单服务80微服务发布到上面2台Eureka集群配置中

yml

修改80,yml配置文件的Eureka服务地址

image-20200706170349591

defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com/eureka #集群版

启动8001,和80端口。(7001,7002已启动)

7001

image-20200706170555285

7002

image-20200706170613312

搭建成功!

image-20200706170635698

构建集群版Eureka Client

同样当我们代建好了集群本的Eureka注册中心后,我们的服务提供者还是只有一个,当服务提供者出现故障,同样会导致整个服务受到影响,所以我们还要搭建集群版的服务提供者

支付服务提供者集群环境构建

image-20200706171436087

新建cloud-provider-payment8002参考8001

image-20200706171544776

pom

pom.xml与8001一样

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
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.kylin</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</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.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<!--如果没写版本,从父层面找,找到了就直接用,全局统一-->
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Mapper和各种类

把8001中的controller,service,dao,mapper都复制一份到8002中

image-20200706172737877

yml

yml也与8001一致,唯一的不同点在于端口号修改为8002

image-20200706172249022

主启动类

PaymentMain8002

image-20200706172802091

controller

image-20200706172908624

由于我们配置了服务提供者的集群,当我们使用服务的时候无法分辨我们正在使用那台服务提供者,不知道是8001还是8002,所以我们要修改一下controller,方便我们查看测试效果~

将8001,和8002的controller进行同样的修改

image-20200706173323989

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
@RestController
@Slf4j
public class PaymentController {
@Resource
PaymentService paymentService;

@Value("${server.port}")
private String serverPort;

@PostMapping("/payment/create")
public CommonResult create(@RequestBody Payment payment) {

int result = paymentService.create(payment);

log.info("******插入结果" + result);

if (result > 0) {
return new CommonResult(200, "插入数据库成功,serverPort:" + serverPort, result);
} else {
return new CommonResult(444, "插入数据库失败", null);
}
}
@GetMapping("/payment/get/{id}")
public CommonResult getPaymentId(@PathVariable("id") Long id) {

Payment payment = paymentService.getPaymentById(id);
System.out.println("test");
log.info("******插入结果" + payment);

if (payment != null) {
return new CommonResult(200, "查询成功,serverPort:" + serverPort, payment);
} else {
return new CommonResult(444, "查询失败,没有对应记录" + id, null);
}
}
}

会把当前的服务端口号一起展示出来,方便我们知道当前调用的是哪个服务提供者

测试

启动7001,7002,80,8001,8002

7001

image-20200706181313894

7002

image-20200706181326487

首先我们各自不同端口的访问服务提供者的服务是否正常

8001

image-20200706175216301

8002

image-20200706175232117

一起正常。接着查看我们的consumer是否能正常使用服务提供者的服务

80

image-20200706175340750

访问正常,但是当我们不断刷新它使用的还是8001服务提供者提供的服务,而没有使用一次8002的,这是不符合我们的初衷的!这是为什么呢?

这是因为我们80中的controller的url地址是写死的

image-20200706175552996

我们将地址修改为我们在注册中心注册的服务名称

image-20200706181343159

image-20200706175801900

public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

重新启动80,重新访问80.直接报错

image-20200706175926079

这是为什么呢?

image-20200706180039987

这是因为我们此时访问的是一个整体的CLOUD-PAYMENT-SERVICE我们访问时不知道应该调用这个整体中的哪一个?是调用8001,还是8002我们不知道,所以报错。

负载均衡

使用@LoadBalanced注解赋予RestTemplate负载均衡的能力,在RestTemplate配置文件中加上注解

image-20200706180353214

重新启动80,测试。成功实现集群的效果!

GIF

actuator微服务信息完善

主机名称

image-20200706200925265

想对这个名称进行规范统一的修改配置

只需要通过配置eureka.instance.instance-id即可。分别修改8001,8002的配置文件

8001

image-20200706201536153

8002

image-20200706201555233

image-20200706201616111

修改成功!

ip信息提示

image-20200706201702833

点击实例名称,左下角是没有ip提示的,这样不方便我们进行调试。

我们需要让其显示ip信息提示

添加配置eureka.instance.prefer-ip-address为true即可

8001

image-20200706201954567

8002

image-20200706202007574

重启查看效果,成功显示ip

image-20200706202212742

服务发现Discovery

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

cloud-provider-payment8001为例

controller

首先注入一个DiscoveryClient对象

image-20200706202746530

接着添加一个请求映射

image-20200706202802281

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@GetMapping("/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;
}

discoveryClient.getServices()获取微服务名称

image-20200706202943077

discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");获取此微服务实例对象

image-20200706203037083

image-20200706203233925

主启动类

主启动类上加上@EnableDiscoveryClient开启服务发现

image-20200706203541531

测试

重启8001服务,访问http://localhost:8001/payment/discovery

image-20200706203343057

控制台结果

image-20200706203431602

Eureka自我保护

理论知识

自我保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式。

Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务

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

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

image-20200706203926915

为什么会产生Eureka自我保护机制?

为了放在Eureka Client可以正常运行,但是与Eureka Server网络不通情况下,Eureka Server不会立刻将Eureka Client服务剔除

什么是自我保护模式?

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

image-20200706204713518

在自我保护模式中,Eureka Server会保存服务注册表中的信息,不再注销任何服务实例。

它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着

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

结果体现

我们把正在运行的cloud-provider-payment8001先关掉

image-20200706205414904

在修改一下8001的配置文件,将前文配置的主机名称注释,使用默认的名称注册到注册中心

image-20200706205511194

重新启动8001,查看Eureka Server页面

7001

image-20200706205705972

7002

image-20200706205719697

可以发现默认的名称已经注册到了注册中心,但是payment8001还是存在!这就是自我保护模式

关闭自我保护

一般生产环境中不会禁止自我保护

由于使用集群版Eureka Server太费时间所以修改7001为单机版,和只使用8001

image-20200706210424130

出厂默认,自我保护机制是开启的

eureka.server.enable-self-preservation = true修改为false

eureka.server.eviction-interval-timer-in-mseureka server清理无效节点的时间间隔,默认90000毫秒,即90秒,修改为两秒

image-20200706211124216

启动查看Eureka Server界面

image-20200706211345839

THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.

提示自我保护模式已经被关闭

生产者客户端eureakeClient端8001修改配置

eureka.instance.lease-renewal-interval-in-seconds=30修改为1。Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)

eureka.instance.lease-expiration-duration-in-seconds=90修改为 2。Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务

加快我们查看测试效果,不用进行较长等待才能看到效果。

启动8001,注册成功

image-20200706211839801

关闭8001

image-20200706211907476

查看效果,马上剔除

image-20200706211926015

成功关闭自我保护!