Spring Cloud基础

微服务框架Spring Cloud

第1章Spring Cloud入门

Spring Cloud 简介

image-20200329173058595

IoT(Internet of Things)物联网,mobile移动端,browser浏览器统称为客户端

API Gateway网关

microservices微服务

breaker dashboard熔断仪表盘

config dashboard 配置仪表盘

service registry 服务注册中心

distributed tracing 分布链跟踪

官网简介

打开Spring 官网http://spring.io 首页的中部,可以看到Spring Cloud 的简介。

【原文】Building distributed systems doesn’t need to be complex and error-prone(易错). Spring Cloud offers a simple and accessible(易接受的) programming model to the most common distributed system patterns(模式), helping developers build resilient(有弹性的), reliable(可靠的), and coordinated(协调的) applications. Spring Cloud is built on top of Spring Boot, making it easy for developers to get started and become productive quickly.

【翻译】构建分布式系统不需要复杂和容易出错。Spring Cloud 为最常见的分布式系统模式提供了一种简单且易于接受的编程模型,帮助开发人员构建有弹性的、可靠的、协调的应用程序。Spring Cloud 构建于Spring Boot 之上,使得开发者很容易入手并快速应用于生产中。

百度百科

Spring Cloud 是一系列框架的有序集合。它利用Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot 的开发风格做到一键启动和部署。Spring Cloud 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者提供了一套简单易懂、易部署和易维护的分布式系统开发工具包。

总结

Spring Cloud 是什么?

阿里高级框架师、Dubbo 项目的负责人刘军说,Spring Cloud 是微服务系统架构的一站式解决方案。

Spring Cloud 与Spring Boot 是什么关系呢?Spring Boot 为Spring Cloud 提供了代码实现环境,使用Spring Boot 将其它组件有机融合到了Spring Cloud 的体系架构中了。所以说,Spring Cloud 是基于Spring Boot 的、微服务系统架构的一站式解决方案。

Spring Cloud 的国内使用情况

image-20200329182821251

Spring Cloud 在线资源

Spring Cloud官网

https://projects.spring.io/spring-cloud/

Spring Cloud中文网

https://springcloud.cc/

Spring Cloud中国社区

http://springcloud.cn/

Spring Cloud 版本

版本号来源

Spring Cloud 的版本号并不是我们通常见的数字版本号,而是一些很奇怪的单词。这些单词均为英国伦敦地铁站的站名。同时根据字母表的顺序来对应版本时间顺序,比如:最早的Release 版本Angel(天使),第二个Release 版本Brixton(英国地名),然后是Camden、Dalston、Edgware,目前使用较多的是Finchley(英国地名)版本,而最新版本为Hoxton(英国地名),而我们这里要使用的是Greenwich(格林威治)。

  • SNAPSHOP:快照版,可以使用,但其仍处理连续不断的开发改进中,不建议使用。

    若干个SNAPSHOP形成一个大版本M

  • M:里程碑版。其也会标注上PRE,preview,预览版,内测版,不建议使用。

  • RC:Release Candidate,发行候选版,主要是用于修复BUG,一般该版本中不会再添加大的功能修改了。正式发行前的版本。

  • SR:Service Release,服务发行版,正式发行版。一般还会被标注上GA,General Available

Spring Cloud与Spring Boot版本

某一版本的Spring Cloud 要求必须要运行在某一特定Spring Boot 版本下。它们的对应关系在Spring Cloud 官网可以看到版本对应说明。

image-20200329202849134

Spring Cloud 与Dubbo 技术选型

Spring Cloud 与Dubbo 均为微服务框架,开发团队在进行技术选型时,总会将它们进行对比,考虑应该选择哪一个。可以从以下几方面考虑:

  • 架构完整度 Dubbo 仅提供了服务注册与服务治理两个模块,Dubbo 可能存在兼容性问题

  • 社区活跃度 Dubbo 暂时暂停版本

  • 通讯协议 Dubbo 通讯使用的是RPC,Spring Cloud 是HTTP REST,Spring Cloud gRPC,

    Dubbo有形成Spring Cloud一部分模块的趋势,即Spring Cloud Dubbo

  • 技术改造与微服务开发,传统项目改为微服务项目使用Dubbo,则改动比较小,如果项目是新项目,就是用Spring Cloud

第一个服务提供者/消费者项目

本例实现了消费者对提供者的调用,但并未使用到Spring Cloud,但其为后续Spring Cloud的运行测试环境。使用MySQL 数据库,使用Spring Data JPA 作为持久层技术。

消费者如何调用提供者,这里使用的是RestTemplate

先创建一个空的Project工程

image-20200402222145112

image-20200402222510845

再在Project中创建Module,进行练习

创建提供者工程01-provider-8081

创建工程

创建一个Spring Initializr 工程,并命名为01-provider-8081。导入Lombok、Web、JPA 及MySQL 驱动依赖。

image-20200402223234534

image-20200402223326986

image-20200402223458823

pom.xml文件中的

1
2
<name>01-provider-8081</name>
<description>Demo project for Spring Boot</description>

可以删除

导入Druid 依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependency> 
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--修改MySQL驱动版本-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>

SpringBoot中默认的mysql版本太高,这里不使用默认版本,而是使用5.1.47

定义实体类

image-20200403081440755

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
@Entity //使用自动建表
//HttpMessageConverter Jackson ->完成Java对象与Json数据间的转换工作
//JPA的默认实现是Hibernate,而Hibernate默认对于对象的查询是基于延迟加载的
// Depart depart =service.findById(5); 这里的depart实际上是一个javasist动态代理
// String name=depert.getName();
// javasist是一种字节码动态代理,说明depart这个对象封装了要查询的真正的对象
// 写这个JsonIgnoreProperties会关闭延迟加载,不会报错
@JsonIgnoreProperties({"hibernateLazyInitializer","handler","fieldHandler"})
public class Depart {

@Id //表示当前属性为自动键的表的主键
@GeneratedValue(strategy = GenerationType.IDENTITY) //主键自动递增
private Integer id;
private String name;

}

注:

@Entity

表明该类 (UserEntity) 为一个实体类,它默认对应数据库中的表名是user_entity。这里也可以写成

@Entity(name = “xwj_user”) 或者

1
2
3
4
5
@Entity

@Table(name = "xwj_user", schema = "test")

查看@Entity注解,发现其只有一个属性name,表示其所对应的数据库中的表名

定义Repository 接口

1
2
3
4
5
//第一个泛型是当前Repository所操作的对象的类型
//第二个泛型是当前Repository所操作的对象的id类型
public interface DepartRepository extends JpaRepository<Depart,Integer> {

}

定义Service 接口

1
2
3
4
5
6
7
public interface DepartService {
boolean saveDepart(Depart depart);
boolean removeDepartById(Integer id);
boolean modifyDepart(Depart depart);
Depart getDepartById(int id);
List<Depart> listAllDeparts();
}

定义Service 实现类

添加数据

image-20200403132520639
1
2
3
4
5
6
7
8
9
10
11
12
@Autowired
private DepartRepository repository;
@Override
public boolean saveDepart(Depart depart) {
//对于save()的参数,根据其id的不同,有以下三种情况
//depart的id为null,save()执行的是插入操作
//depart的id不为null,且DB中该id存在:save()执行的是修改操作
//depart的id不为null,但DB中该id不存在:save()执行的是插入操作
// 但是其插入后的记录id值并不是这里指定的id,而是其根据指定的id生成策略所生成的id
Depart save = repository.save(depart);
return save!=null?true:false;
}

删除数据

1
2
3
4
5
6
7
8
9
@Override
public boolean removeDepartById(Integer id) {
if (repository.existsById(id)) {
//在DB中指定的id若不存在,该方法会抛出异常
repository.deleteById(id);
return true;
}
return false;
}

修改数据

1
2
3
4
5
@Override
public boolean modifyDepart(Depart depart) {
Depart save = repository.save(depart);
return save!=null?true:false;
}

根据id查询

1
2
3
4
5
6
7
8
9
10
@Override
public Depart getDepartById(int id) {
if (repository.existsById(id)) {
//在DB中指定的id若不存在,该方法会抛出异常
return repository.getOne(id);
}
Depart depart=new Depart();
depart.setName("no this depart");
return depart;
}

查询所有

1
2
3
4
@Override
public List<Depart> listAllDeparts() {
return repository.findAll();
}

定义处理器

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
@RestController
@RequestMapping("/provider/depart")
public class DepartController {
@Autowired
private DepartService service;

@PostMapping("/save")
public boolean saveHandler(@RequestBody Depart depart){
return service.saveDepart(depart);
}

@DeleteMapping("/del/{id}")
public boolean delHandler(@PathVariable("id") Integer id){
return service.removeDepartById(id);
}

@PutMapping("/update")
public boolean updateHandler(@RequestBody Depart depart){
return service.modifyDepart(depart);
}

@GetMapping("/get/{id}")
public Depart getHandler(@PathVariable("id") Integer id){
return service.getDepartById(id);
}

@GetMapping("/list")
public List<Depart> listHandler(){
return service.listAllDeparts();
}
}

修改配置文件

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
server:
port: 8081


spring:
# 配置spring data jpa
jpa:
# 指定在控制台是否显示SQL语句,默认false
generate-ddl: true
# 指定在控制台是否显示SQL语句,默认false
show-sql: true
# 指定应用启动后不重新更新表内容
hibernate:
ddl-auto: none

# 配置数据源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/sptest?useUnicode=true&amp;characterEncoding=utf8
username: root
password: root

# 配置文件
logging:
pattern:
console: level-%level %msg%n
level:
root: info
org.hibernate: info
# 在show-sql为true时显示SQL中的动态参数值,就是把?显示出来
org.hibernate.type.descriptor.sql.BasicBinder: trace
# 在show-sql为true时显示查询结果
org.hibernate.hql.internal.ast.exec.BasicBinder: trace

cn.itcast: debug

创建消费者工程01-consumer-8080

创建工程

创建一个Spring Initializr 工程,并命名为01-consumer-8080,导入Lombok 与Web 依赖。

image-20200404092632114

定义实体类

image-20200406203358153

定义JavaConfig 容器类

image-20200406203506338

定义处理器类

添加数据

image-20200406203612377

删除与修改数据

image-20200406203654297

两个查询

image-20200406203733357

第2章微服务中心Eureka

注:客户机一般是产生数据的,所以是生产者;服务器是处理客户机产生的数据的,所以是消费者

Eureka 概述

CAP定理

概念

CAP 定理指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

  • 一致性(C):分布式系统中多个主机之间是否能够保持数据一致的特性。即,当系统数据发生更新操作后,各个主机中的数据仍然处于一致的状态。
  • 可用性(A):系统提供的服务必须一直处于可用的状态,即对于用户的每一个请求,系统总是可以在有限的时间内对用户做出响应。
  • 分区容错性(P):分布式系统在遇到任何网络分区故障时,仍能够保证对外提供满足一致性和可用性的服务。

注:

base理论是对cap的折中

定理

CAP 定理的内容是:

对于分布式系统,网络环境相对是不可控的,出现网络分区是不可避免的,因此系统必须具备分区容错性。但系统不能同时保证一致性与可用性。即要么CP,要么AP。

Eureka简介

Eureka 是Netflix 开发的服务发现框架,本身是一个基于REST 的服务,主要用于定位运行在AWS(Amazon Web Services,亚马逊网络服务,亚马逊云)域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud 将它集成在其子项目spring-cloud-netflix中,实现SpringCloud 的服务发现功能。

其实,Eureka 就是一个专门用于服务发现的服务器,一些服务注册到该服务器,而另一些服务通过该服务器查找其所要调用执行的服务。可以充当服务发现服务器的组件很多,例如Zookeeper、Consul、Eureka 等。

Eureka体系架构

image-20200406204323037

Eureka与Zookeeper对比

Eureka 与Zookeeper 都可以充当服务中心,那么它们有什么区别呢?它们的区别主要体现在对于CAP 原则的支持的不同。

  • Eureka:AP
  • zk:CP

Eureka的闭源谣言

Eureka 官网的wiki 中公布了如下内容:

image-20200406204419599

【翻译】现在的关于eureka 2.0 的开源工作已经终止。已经发布的现存库中的关于2.x 分支部分的代码库与工程,你的使用将自负风险。

Erueka 1.x 是Netflix 服务发现系统的核心部分,其仍是一个活跃项目。

创建Eureka 服务中心00-eurekaserver-8000

这里创建的Eureka 服务中心,即服务发现服务器。

总步骤

  • 添加Eureka Server 依赖
  • 在配置文件中配置Eureka Server
  • 在启动类上添加@EnableEurekaServer 注解,启动Eureka Server 功能

创建工程

创建一个Spring Initializr 工程,命名为00-eurekaserver-8000,仅导入Eureka Server 依赖即可。

服务器是以00开头的

image-20200406220315848

image-20200406220921821

image-20200406204558024

image-20200406221119935

工程创建完毕后,在pom 文件中可以看到如下的版本信息。

image-20200406204610937

image-20200406204616498

image-20200406204622820

导入依赖

若你使用的是JDK6、7、8,那么这些依赖无需导入。而JDK9 及其以上版本需要导入。

JAXB,Java Architecture for XML,XML绑定的Java技术,其可以根据XML Schema生成Java类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId> <artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>

创建并配置yml文件

server本身也可以作为client出现

image-20200406224055966

image-20200406204828846

定义spring boot启动类

image-20200406204840432

image-20200406225517495

创建提供者工程02-provider-8081

总步骤

  • 添加Eureka Client 依赖
  • 在配置文件中指定要注册的Eureka Server 地址,指定自己微服务名称

创建工程

复制01-provider-8081,并重命名为02-provider-8081。

添加依赖管理及依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<properties> 
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>

<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>

<!--eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

注:

spring-boot-starter-parent版本可能会不一样,要进行统一

修改yml文件

image-20200406205427853

image-20200406205433651

1
2
3
4
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka

如果在服务端,表示暴露出来的连接地址,如果在客户端,表示想要连接的连接地址

即指定当前Client要连接的eureka或者指定当前要被连接的eureka

有的人会说在客户端添加@EnableEurekaClient@EnableDiscoveryClient

但其实,这两个注解是不需要的

输出结果

在服务端显示出

image-20200407103800933

路径上必须加上/eureka,否则其他客户端连接时会出现错误

actuator 完善微服务info

提供者工程添加依赖

在提供者(客户端中)工程的pom中添加actuator 监控依赖。

1
2
3
4
5
<!--actuator依赖--> 
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
修改配置文件

在客户端中的application.yml文件中配置文件

image-20200406205810496

输出结果

image-20200407120024958

image-20200407120355677

创建消费工程02-consumer-8080

消费者将使用提供者暴露的服务名称(spring.application.name)来消费服务。

总步骤

  • 添加Eureka Client 依赖
  • 在配置文件中指定要注册的Eureka Server 地址,指定自己微服务名称
  • 在JavaConfig 类中为RestTemplate 添加@LoadBalance 注解,实现负载均衡
  • 修改处理器,将“主机名:端口” -> “提供者微服务名称”

创建工程

复制01-consumer-8080,并重命名为02-consumer-8080。

添加依赖管理及依赖

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
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<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>
<!--actuator依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

修改yml文件

image-20200407182943851

修改处理器

image-20200412214622237

image-20200407183005754

修改JavaConfig类

image-20200407183023488

修改启动类

image-20200407183036156

服务发现Discovery

直接修改处理器。

image-20200413102855892

要做服务发现,就需要有一个进行翻译的客户端,这个客户端是系统里有的,我们只需要注册就行

image-20200407183101901

image-20200407183109616

一个微服务可以对应多个提供者

image-20200413093920174

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
------
serviceId=ABCMSC-PROVIDER-DEPART
instanceId=192.168.0.198:abcmsc-provider-depart:8081
host=192.168.0.198
uri=http://192.168.0.198:8081
port=8081
metadata={management.port=8081}
------
serviceId=ABCMSC-CONSUMER-DEPART
instanceId=192.168.0.198:abcmsc-consumer-depart
host=192.168.0.198
uri=http://192.168.0.198:8080
port=8080
metadata={management.port=8080}

Eureka 的自我保护机制

自我保护机制

在Eureka 服务页面中看到如下红色字体内容,表示当前EurekaServer 启动了自我保护机制,进入了自我保护模式。

image-20200407183215138

【原文】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.

【翻译】紧急情况!当微服务主机联系不上时,Eureka 不能够正确判断它们是否处于up 状态。当更新(指收到的微服务主机的心跳)小于阈值时,为了安全,微服务主机将不再失效。

默认情况下,EurekaServer 在90 秒内没有检测到服务列表中的某微服务,则会自动将该微服务从服务列表中删除。但很多情况下并不是该微服务节点(主机)出了问题,而是由于网络抖动等原因使该微服务无法被EurekaServer 发现,即无法检测到该微服务主机的心跳。若在短暂时间内网络恢复正常,但由于EurekaServer 的服务列表中已经没有该微服务,所以该微服务已经无法提供服务了。

在短时间内若EurekaServer 丢失较多微服务,即EurekaServer 收到的心跳数量小于阈值,为了保证系统的可用性(AP),给那些由于网络抖动而被认为宕机的客户端“重新复活”的机会,Eureka 会自动进入自我保护模式:服务列表只可读取、写入,不可执行删除操作。当EurekaServer 收到的心跳数量恢复到阈值以上时,其会自动退出Self Preservation 模式。

默认值修改

启动自我保护的阈值因子默认为0.85,即85%。即EurekaServer 收到的心跳数量若小于应该收到数量的85%时,会启动自我保护机制。

自我保护机制默认是开启的,可以通过修改EurekaServer 中配置文件来关闭。但不建议关闭,如果关闭,整个集群无法保证其可用性

在eureka的微服务中心(00-eurekaserver-8000)设置

1
2
3
4
5
6
7
8
eureka:
server:
# 关闭自我保护机制
enable-self-preservation: false
# 指定自我保护机制的开启阈值
# renewal-percent-threshold: 0.75
# 设置server端剔除不可用服务的时间窗,单位毫秒,默认值是60秒
eviction-interval-timer-in-ms: 40000

在提供者中(02-provider-8081)设置

1
2
3
4
5
6
eureka:
instance:
#设置当前Client每1秒像Server发送一次心跳,单位秒,默认30秒
lease-renewal-interval-in-seconds: 1
# 指定让Server认定当前Client已经失效的时间,将来可以从注册表中删除了,单位秒
lease-expiration-duration-in-seconds: 3

image-20200413110319617

所以最好不要关闭

GUI上的属性值

image-20200413134944086

  • Renews threshold:Eureka Server 期望每分钟收到客户端的续约总数。

    count:微服务中心收到的心跳数

    15:从显示的时刻之前推15分钟,计算15分钟中收到多少心跳

    count * 0.85 / 15

  • Renews (last min):Eureka Server 实际在最后一分钟收到客户端的续约数量。

  • 说明:若Renews (last min) < Renews threshold ,就会启动自我保护,但是数据显示有时候可能不准确

服务离线

服务离线,即某服务不能对外提供服务了。服务离线的原因有两种:服务下架与服务下线。这两种方案都是基于Actuator 监控器实现的。

  • 服务下架:将注册到Eureka Server 中的Eureka Client 从Server 的注册表中移除,这样其实Client 就无法发现该Client 了。
  • 服务下线:Client 并没有从Eureka Server 的注册表中移除(其它Client 仍可发现该服务),而是通过修改服务的状态来到达其它Client 无法调用的目的。

准备工作

为Eureka Client 添加actuator 依赖。

image-20200407183515495

服务下架

修改配置文件

image-20200407183541902

1
2
3
4
5
6
7
8
9
10
management:
#开启所有监控终端
endpoints:
web:
exposure:
include: "*"
#开启shutdown监控终端
endpoint:
shutdown:
enabled: true
运行测试

在Restlet 中提交如下POST 请求即可关闭该应用。

image-20200407183559721

image-20200413140939952

服务平滑上下线

前面的“服务下架”方式存在一个不足是,若还需要再启用该服务,则必须再次启动该应用。我们也可以通过修改服务的状态为UP 或DOWN 来设置提供者是否可用,而无需重启应用。这种方式通常称为服务的平滑上下线。

image-20200407183626649

运行测试

在Restlet 中提交如下POST 请求,然后再查看Eureka 页面,发现服务状态已经变为了DOWN。

image-20200413141956620

image-20200407183647620

image-20200413142030607

此时,该服务就不能执行

image-20200413142422064

image-20200413142513329

提供者和消费者两者的配置文件要分别编写,不同的模块配置可能不同

EurekaServer 集群

这里要搭建的EurekaServer 集群中包含三个EurekaServer 节点,其端口号分别为8100、8200 与8300。

设置域名

在C:\Windows\System32\drivers\etc 的host 文件中添加如下域名映射信息。

image-20200407183725740

创建00-eurekaserver-8100

复制工程

复制00-eurekaserver-8000 工程,并重命名为00-eurekaserver-8100。

修改pom

image-20200407183814895

修改配置文件
1
2
3
4
5
6
7
8
9
10
11
server:
port: 8100

eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka8100.com:8100/eureka,http://eureka8200.com:8200/eureka,http://eureka8300.com:8300/eureka
instance:
hostname: eureka8100.com

将上面的localhost 更换为eureka8100.com、eureka8200.com、eureka8300.com。

创建00-eurekaserver-8200

再以相同的方式再复制出00-eurekaserver-8200。

修改配置文件

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

eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka8100.com:8100/eureka,http://eureka8200.com:8200/eureka,http://eureka8300.com:8300/eureka
instance:
hostname: eureka8200.com

创建00-eurekaserver-8300

修改配置文件

再以相同的方式再复制出00-eurekaserver-8300。

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

eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka8100.com:8100/eureka,http://eureka8200.com:8200/eureka,http://eureka8300.com:8300/eureka
instance:
hostname: eureka8300.com

第3章OpenFeign与Ribbon

概述

OpenFeign简介

官网简介

image-20200413143759667

【翻译】声明式REST 客户端:Feign 通过使用JAX-RS(Java Api Extensions for RESTful Web Services,简单来说,就是一种使用注解来实现RESTful 的技术)或SpringMVC 注解的装饰方式,生成接口的动态实现。

综合说明

Feign,假装,伪装。

OpenFeign可以将提供者提供的Restful 服务伪装为接口进行消费,消费者只需使用“feign
接口+ 注解”的方式即可直接调用提供者提供的Restful 服务,而无需再使用RestTemplate。
需要注意:

  • 该伪装的Feign 接口是由消费者调用,与提供者没有任何关系。
  • Feign 仅是一个伪客户端,其不会对请求做任何处理。
  • Feign 是通过注解的方式实现RESTful 请求的。

OpenFeign与Feign

Spring Cloud D 版及之前的版本使用的是Feign,而该项目现已更新为了OpenFeign。所以后续使用的依赖也发生了变化

image-20200413143910066

Ribbon与OpenFeign

说到OpenFeign,不得不提的就是Ribbon。Ribbon 是Netflix 公司的一个开源的负载均衡项目,是一个客户端负载均衡器,运行在消费者端。

OpenFeign 也是运行在消费者端的,使用Ribbon 进行负载均衡,所以OpenFeign 直接内置了Ribbon。即在导入OpenFeign 依赖后,无需再专门导入Ribbon 依赖了。

声明式Rest客户端OpenFeign

创建消费者工程03-consumer-feign-8080

这里无需修改提供者工程,只需修改消费者工程即可。

总步骤

  • 添加OpenFeign 依赖
  • 定义Feign接口,指定要访问的微服务
  • 修改处理器,使用Feign接口来消费微服务
  • 将JavaConfig中的RestTemplate的创建方法删除
  • 在启动类上添加@EnableFeignClients 注解

创建工程

复制02-consumer-8080,并重命名为03-consumer-feign-8080。

添加openfeign 依赖

1
2
3
4
5
<!--feign依赖--> 
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

定义Feign 接口

一般情况下,包名为service,定义的接口名都和业务接口名相同,但是完全可以不一样

image-20200414102154380

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//指定当前为Feign客户端,参数为提供者的微服务的名称
@FeignClient("abcmsc-provider-depart")
@RequestMapping("/provider/depart")
@Service
public interface DepartService {
@PostMapping("/save")
boolean saveDepart(@RequestBody Depart depart);

@DeleteMapping("/del/{id}")
boolean removeDepartById(@PathVariable("id") Integer id);

@PutMapping("/update")
boolean modifyDepart(@RequestBody Depart depart);

@GetMapping("/get/{id}")
Depart getDepartById(@PathVariable("id") Integer id);

@GetMapping("/list")
List<Depart> listAllDeparts();
}

关于Feign的说明:

1)Feign接口一般与是业务名相同,但不是必须的

2)Feign接口中的方法名一般也是与业务接口方法名相同,但也不是必须的

3)Feign接口中的方法返回值类型,方法参数要求与业务接口中的相同

4)接口上与方法上的Mapping的参数URI要与提供者处理器相应方法上的Mapping的URI相同

修改JavaConfig 类

image-20200413144153507

同样可以直接删除这个类

修改处理器

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
@RestController
@RequestMapping("/consumer/depart")
public class SomeController {

@Autowired
private DepartService service;

@PostMapping("/save")
public boolean saveHandler(@RequestBody Depart depart){
return service.saveDepart(depart);
}

@DeleteMapping("/del/{id}")
public boolean deleteHandler(@PathVariable("id") int id){
return service.removeDepartById(id);
}

@PutMapping("/update")
public boolean updateHandler(@RequestBody Depart depart){
return service.modifyDepart(depart);
}

@GetMapping("/get/{id}")
public Depart getByIdHandler(@PathVariable("id") int id){
return service.getDepartById(id);
}

@GetMapping("/list")
public List<Depart> listHandler(){
return service.listAllDeparts();
}
}

image-20200413144204640

image-20200413144210717

修改启动类

在03-consumer-feign-8080中的加载类中配置

image-20200413144225933

超时设置

Feign 连接提供者、对于提供者的调用均可设置超时时限。

修改配置文件

在03-consumer-feign-8080 工程的配置文件中直接添加如下内容

image-20200413144437458

connectTimeout:指定Feign客户端连接提供者的超时时限

readTimeout:指定Feign客户端连接上提供者后,向提供者进行提交请求,从提交开始到接收响应,这个时段的超时时间

创建提供者工程03-provider-8081

创建工程

复制工程02-provider-8081,并重命名为03-provider-8081。

修改Service接口实现类

在提供者中的service接口中修改

image-20200413145252300

输出结果

读超时

image-20200415214436114

image-20200415214543929

正常情况

image-20200415214658641

image-20200415214735373

Gzip压缩设置

Feign 支持对请求(Feign 客户端向提供者的请求)和响应(Feign 客户端向客户端浏览器的响应)进行Gzip 压缩以提高通信效率。
image-20200413145331814

image-20200414143604791

image-20200413145338979

Ribbon 负载均衡

系统结构

image-20200413145403419

创建提供者03-provider-8082

复制提供者工程8081

复制02-provider-8081 工程,并重命名为03-provider-8082。

修改配置文件

image-20200413145502046

修改处理器

image-20200413145518046

image-20200413145529452

image-20200413145534799

image-20200414223937762

image-20200414224016539

创建提供者03-provider-8083

以相同的方式创建提供者工程03-provider-8083。

创建提供者03-provider-8084

以相同的方式创建提供者工程03-provider-8084。

输出结果

image-20200414224455718

image-20200414224541342

image-20200414224608017

更换负载均衡策略

03-consumer-loadbalance-8080

内置负载均衡策略

1RoundRobinRule

轮询策略。Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的provider,其最多轮询10 轮。若最终还没有找到,则返回null。

2RandomRule

随机策略,从所有可用的provider 中随机选择一个。

3RetryRule

重试策略。先按照RoundRobinRule 策略获取provider,若获取失败,则在指定的时限内重试。默认的时限为500 毫秒。

4BestAvailableRule

最可用策略。选择并发量最小的provider,即连接的消费者数量最少的provider。

5AvailabilityFilteringRule

可用过滤算法。该算法规则是:过滤掉处于熔断状态的provider 与已经超过连接极限的
provider,对剩余provider 采用轮询策略。

6ZoneAvoidanceRule

zone 回避策略。根据provider 所在zone 及provider 的可用性,对provider 进行选择。

7WeightedResponseTimeRule

“权重响应时间”策略。根据每个provider 的平均响应时间计算其权重,响应时间越快权重越大,被选中的机率就越高。在刚启动时采用轮询策略。后面就会根据权重进行选择了。

更换内置策略

Ribbon 默认采用的是RoundRobinRule,即轮询策略。但通过修改消费者工程的配置文件,或修改消费者的启动类或JavaConfig 类可以实现更换负载均衡策略的目的。

创建工程

复制03-consumer-feign-8080 工程,并重命名为03-consumer-loadbalance-8080。

方式一:修改配置文件

在消费者端

修改配置文件,在其中添加如下内容:

1
2
3
4
# 修改负载均衡策略
abcmsc-provider-depart: # 要负载均衡的提供者微服务名称
ribbon: # 指定要使用的负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

image-20200415070621033

Copy Reference获取NFLoadBalancerRuleClassName路径

第一种方式可以根据不同的微服务设置不同的负载均衡策略

方式二:修改JavaConfig 类

在JavaConfig 类中添加负载负载Bean 方法。

image-20200415083905803

image-20200413145930046

JavaConfig类中的配置信息的优先级要比配置文件中的配置信息的优先级要高

注:

Spring中@Configuration 和 @Component 区别

一句话概括就是 @Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。

下面看看实现的细节。

@Configuration 注解:

1
2
3
4
5
6
7
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
String value() default "";
}

从定义来看, @Configuration注解本质上还是 @Component,因此 <context:component-scan/>或者@ComponentScan都能处理@Configuration 注解的类。

@Configuration 标记的类必须符合下面的要求:

  • 配置类必须以类的形式提供(不能是工厂方法返回的实例),允许通过生成子类在运行时增强(cglib 动态代理)。
  • 配置类不能是 final 类(没法动态代理)。
  • 配置注解通常为了通过 @Bean 注解生成 Spring 容器管理的类。
  • 配置类必须是非本地的(即不能在方法中声明,不能是 private)。
  • 任何嵌套配置类都必须声明为static。
  • @Bean 方法可能不会反过来创建进一步的配置类(也就是返回的 bean 如果带有 @Configuration,也不会被特殊处理,只会作为普通的 bean)。

加载过程

Spring 容器在启动时,会加载默认的一些 PostPRocessor,其中就有 ConfigurationClassPostProcessor,这个后置处理程序专门处理带有@Configuration注解的类,这个程序会在 bean定义加载完成后,在bean 初始化前进行处理。主要处理的过程就是使用 cglib 动态代理增强类,而且是对其中带有 @Bean注解的方法进行处理。

ConfigurationClassPostProcessor中的 postProcessBeanFactory方法中调用了下面的方法:

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
/**

* Post-processes a BeanFactory in search of Configuration class BeanDefinitions;
* any candidates are then enhanced by a {@link ConfigurationClassEnhancer}.
* Candidate status is determined by BeanDefinition attribute metadata.
* @see ConfigurationClassEnhancer
*/
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
//省略部分代码
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
// nothing to enhance -> return immediately
return;
}
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// If a @Configuration class gets proxied, always proxy the target class
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
try {
// Set enhanced subclass of the user-specified bean class
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
//省略部分代码
beanDef.setBeanClass(enhancedClass);
}
}
catch (Throwable ex) {
throw new IllegalStateException(
"Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
}
}
}

在方法的第一次循环中,查找到所有带有 @Configuration注解的 bean 定义,然后在第二个 for 循环中,通过下面的方法对类进行增强:

1
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);

然后使用增强后的类替换了原有的beanClass

1
beanDef.setBeanClass(enhancedClass);

所以到此时,所有带有 @Configuration注解的 bean 都已经变成了增强的类。

下面关注上面的enhance增强方法,多跟一步就能看到下面的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**

* Creates a new CGLIB {@link Enhancer} instance.
*/
private Enhancer newEnhancer(Class<?> superclass, ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(superclass);
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}

通过 cglib 代理的类在调用方法时,会通过 CallbackFilter 调用,这里的 CALLBACK_FILTER 如下:

1
2
3
4
5
6
7
8
9
// The callbacks to use. Note that these callbacks must be stateless.
private static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};

private static final ConditionalCallbackFilter CALLBACK_FILTER =
new ConditionalCallbackFilter(CALLBACKS);

其中 BeanMethodInterceptor 匹配方法如下:

1
2
3
4
5
6
7
8
9
@Override
public boolean isMatch(Method candidateMethod) {
return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
}

//BeanAnnotationHelper
public static boolean isBeanAnnotated(Method method) {
return AnnotatedElementUtils.hasAnnotation(method, Bean.class);
}

也就是当方法有 @Bean注解的时候,就会执行这个回调方法。

另一个 BeanFactoryAwareMethodInterceptor匹配的方法如下:

1
2
3
4
5
6
7
@Override
public boolean isMatch(Method candidateMethod) {
return (candidateMethod.getName().equals("setBeanFactory") &&
candidateMethod.getParameterTypes().length == 1 &&
BeanFactory.class == candidateMethod.getParameterTypes()[0] &&
BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
}

当前类还需要实现BeanFactoryAware接口,上面的isMatch 就是匹配的这个接口的方法。

@Bean 注解方法执行策略

先给一个简单的示例代码:

@Configuration
public class MyBeanConfig {

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class MyBeanConfig {

@Bean
public Country country(){
return new Country();
}

@Bean
public UserInfo userInfo(){
return new UserInfo(country());
}
}

相信大多数人第一次看到上面userInfo() 中调用 country()时,会认为这里的 Country和上面 @Bean 方法返回的 Country可能不是同一个对象,因此可能会通过下面的方式来替代这种方式:

1
2
@Autowired
private Country country;

实际上不需要这么做(后面会给出需要这样做的场景),直接调用country()方法返回的是同一个实例。

下面看调用 country()userInfo()方法时的逻辑。

现在我们已经知道 @Configuration注解的类是如何被处理的了,现在关注上面的 BeanMethodInterceptor,看看带有@Bean 注解的方法执行的逻辑。下面分解来看 intercept方法。

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
//首先通过反射从增强的 Configuration 注解类中获取 beanFactory
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);

//然后通过方法获取 beanName,默认为方法名,可以通过 @Bean 注解指定
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

//确定这个 bean 是否指定了代理的范围
//默认下面 if 条件 false 不会执行
Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}

//中间跳过一段 Factorybean 相关代码

//判断当前执行的方法是否为正在执行的 @Bean 方法
//因为存在在 userInfo() 方法中调用 country() 方法
//如果 country() 也有 @Bean 注解,那么这个返回值就是 false.
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// 判断返回值类型,如果是 BeanFactoryPostProcessor 就写警告日志
if (logger.isWarnEnabled() &&
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
logger.warn(String.format(
"@Bean method %s.%s is non-static and returns an object " +
"assignable to Spring's BeanFactoryPostProcessor interface. This will " +
"result in a failure to process annotations such as @Autowired, " +
"@Resource and @PostConstruct within the method's declaring " +
"@Configuration class. Add the 'static' modifier to this method to avoid " +
"these container lifecycle issues; see @Bean javadoc for complete details.",
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
}
//直接调用原方法创建 bean
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
//如果不满足上面 if,也就是在 userInfo() 中调用的 country() 方法
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);

关于 isCurrentlyInvokedFactoryMethod方法

可以参考 SimpleInstantiationStrategy中的 instantiate方法,这里先设置的调用方法:

1
2
currentlyInvokedFactoryMethod.set(factoryMethod);
return factoryMethod.invoke(factoryBean, args);

而通过方法内部直接调用 country()方法时,不走上面的逻辑,直接进的代理方法,也就是当前的 intercept方法,因此当前的工厂方法和执行的方法就不相同了。

obtainBeanInstanceFromFactory 方法比较简单,就是通过 beanFactory.getBean 获取 Country,如果已经创建了就会直接返回,如果没有执行过,就会通过invokeSuper 首次执行。

因此我们在 @Configuration 注解定义的 bean 方法中可以直接调用方法,不需要@Autowired注入后使用。

@Component 注意

@Component 注解并没有通过 cglib 来代理@Bean方法的调用,因此像下面这样配置时,就是两个不同的 country。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class MyBeanConfig {

@Bean
public Country country(){
return new Country();
}

@Bean
public UserInfo userInfo(){
return new UserInfo(country());
}
}

有些特殊情况下,我们不希望 MyBeanConfig被代理(代理后会变成WebMvcConfig$$EnhancerBySpringCGLIB$$8bef3235293)时,就得用 @Component,这种情况下,上面的写法就需要改成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class MyBeanConfig {
@Autowired
private Country country;

@Bean
public Country country(){
return new Country();
}

@Bean
public UserInfo userInfo(){
return new UserInfo(country);
}
}

自定义负载均衡策略

直接在03-consumer-loadbalance-8080 工程上修改。

定义CustomRule 类

在consumer中进行编写

Ribbon 支持自定义负载均衡策略。负载均衡算法类需要实现IRule 接口。

该负载均衡策略的思路是:从所有可用的provider 中排除掉指定端口号的provider,剩余provider 进行随机选择。


image-20200413150014534

image-20200415093824829


注:

image-20200415093457720

1
2
3
getAllServers():获取所有的(包括up和down)状态的Server

getReachableServers():只获取所有up状态的Server

getReachableServers():


使用普通代码方式实现

image-20200413150031814

image-20200413150037606

使用Lambda方式实现

image-20200413150044420

修改JavaConfig 类

将原来的负载均衡Bean 方法注释掉,添加新的负载均衡策略方法。

image-20200413150110975

输出结果

只能出现8082和8084,不能出现8083

image-20200415102905491

image-20200415102929280

第4章Hystrix服务熔断与服务降级

官网https://github.com/netflix/hystrix/wiki

前置概念

服务熔断

雪崩效应

在复杂的系统中,经常会出现A依赖于B,B依赖于C,C依赖于D,……这种依赖将会产生很长的调用链路,这种复杂的调用链路称为1->N的扇出。

如果在A 的调用链路上某一个或几个被调用的子服务不可用或延迟较高,则会导致调用A 服务的请求被堵住。

堵住的A 请求会消耗占用系统的线程、IO 等资源,当对A 服务的请求越来越多,占用的计算机资源越来越多的时候,会导致系统瓶颈出现,造成其他的请求同样不可用,最终导致业务系统崩溃,这种现象称为雪崩效应。

image-20200413150212030

此为1->N的扇出

服务雪崩

雪崩效应发生在分布式SOA 系统中,则称为服务雪崩。

image-20200413150238821

上图是用户请求的多个服务(A,H,I,P)均能正常访问并返回的情况。

image-20200413150249412

上图为请求服务I 出现问题时,一个用户请求被阻塞的情况。

image-20200413150257904

上图为大量用户请求服务I 出现异常全部陷入阻塞的的情况,即服务发生雪崩的情况。

熔断机制

熔断机制是服务雪崩的一种有效解决方案。常见的熔断有两种:

  • 预熔断

    预熔断也叫提前熔断,根据以往的经验,可以提前预料到在某个时段会出现访问的高峰,即出现峰值,为了保证消费者能够对重要或基本的服务进行正常的访问,可以通过这个服务治理的方式,预先把不重要的服务暂停或延迟

  • 即时熔断

    在非预知的情况下,比如由于网络导致消费者对于提供者的访问出现了问题,可能出现响应过慢导致超时或者没有响应

    在指定的之前设置的时间戳内,消费者对于某一个提供者的请求的失败率达到了预先设定的阈值的时候,为了防止雪崩的发生,消费者就会自动把提供者的访问链路给断开

    类似于家里的保险丝,当使用大功率电器的时候,保险丝突然跳闸

可以看出熔断机制是消费者端的一种保护措施,和提供者端是没有关系的

服务降级

熔断以后对于用户来说体验会变得很差,为了提升用户体验,就会预先设定一些值来返回给用户

服务降级是请求发生问题后的一种增强用户体验的方式。

发生服务熔断,一定会发生服务降级。但发生服务降级,并不意味着一定是发生了服务熔断。

假设大多数用户都是正常的,只有一个用户在访问的时候出现问题,超时导致没有得到响应结果,这时候,提供者就会将预设的降级结果提供给用户,比如服务器忙请重试,此时并没有发生熔断,但是对于这个出现问题的用户,它发生了降级,所以发生服务熔断,一定会发生服务降级。但发生服务降级,并不意味着一定是发生了服务熔断。

服务降级是消费者本身可以提供的,能够给出服务降级结果的地方有很多

除了消息中间件给消费者提供的是真的数据以外,其他服务降级给消费者提供的是假数据,是事先设置好的结果

Hystrix 简介

Spring Cloud 是通过Hystrix 来实现服务熔断与降级的。

官网Wiki

image-20200413150502697

image-20200413150509207

【翻译】在分布式环境中,许多服务依赖中的一些服务发生失败是不可避免的。Hystrix 是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix 通过隔离服务之间的访问点、停止跨服务的级联故障以及提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。

综合说明

Hystrix 是一种开关装置,类似于熔断保险丝。在消费者端安装一个Hystrix 熔断器,当
Hystrix 监控到某个服务发生故障后熔断器会开启,将此服务访问链路断开。不过Hystrix 并不会将该服务的消费者阻塞,或向消费者抛出异常,而是向消费者返回一个符合预期的备选响应(FallBack)。通过Hystrix 的熔断与降级功能,避免了服务雪崩的发生,同时也考虑到了用户体验。故Hystrix 是系统的一种防御机制。

fallbackMethod 服务降级

Hystrix 对于服务降级的实现方式有两种:

方法级别的服务降级:fallbackMethod 服务降级

类级别的服务降级:fallbackFactory服务降级

首先来看fallbackMethod 服务降级

总步骤

  • 添加Hystrix 依赖
  • 修改处理器方法。在处理器方法上添加@HystrixCommond 注解
  • 在处理器中定义服务降级方法
  • 在启动类上添加@EnableCircuitBreaker 注解(或将@SpringBootApplication 注解替换为@SpringCloudApplication 注解)

案例一:hystrix 本身与feign 是没有关系的

创建消费者工程04-consumer-fallbackmethod-8080

创建工程

复制02-consumer-8080 工程,并重命名为04-consumer-fallbackmethod-8080。

该工程的运行说明hystrix 本身与feign 是没有关系的

添加hystrix依赖

1
2
3
4
5
<!--hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

修改处理器类

SomeController类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//指定该方法要使用服务降级,即当前处理器方法在运行过程中若发生异常
//无法给客户端正常响应时,就会调用fallbackMethod指定方法
@HystrixCommand(fallbackMethod = "getHystrixHandler")
@GetMapping("/get/{id}")
public Depart getByIdHandler(@PathVariable("id") int id){
return service.getDepartById(id);
}

//定义服务降级方法,即响应给客户端的备选方案
public Depart getHystrixHandler(@PathVariable("id") int id){
Depart depart = new Depart();
depart.setId(id);
depart.setName("no this depart");
return depart;
}

降级方法的名称可以随便起,方法签名要一样,即方法声明的两个组件构成了方法签名 - 方法的名称和参数类型


image-20200413150739645

在启动类添加注解@EnableCircuitBreaker

image-20200413150813843

这一个类上添加的注解太多了,为了避免这种情况的发生,Spring Cloud 专门定义了一个组合注解@SpringCloudApplication,就包含了这三个注解。所以可以用它直接替换掉那三个注解。

@SpringCloudApplication中的内容

image-20200415145844410

即:

image-20200413150823305

输出结果

案例二:Hystrix 与Feign 结合使用

创建消费者工程04-consumer-feign-fallbackmethod-8080

创建工程

复制03-consumer-feign-8080 工程,并重命名为04-consumer-feign-fallbackmethod-8080。

该工程是Hystrix 与Feign 结合使用

当然,一般情况下都是这样使用的。

添加hystrix 依赖

1
2
3
4
5
<!--hystrix依赖--> 
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

修改处理器

image-20200415151044776

在启动类添加注解@SpringCloudApplication

image-20200413151006720

输出结果

image-20200415195026650

fallbackFactory 服务降级

当一个服务同时存在类级别与方法级别的降级时,方法级别的降级优先级高。

总步骤

  • 添加Hystrix 依赖
  • 定义服务降级类
  • 在Feign 接口中指定服务降级类
  • 修改配置文件,开启Feign 对Hystrix 的支持
  • 在启动类上添加@EnableCircuitBreaker 注解(或将@SpringBootApplication 注解替换为@SpringCloudApplication 注解)
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
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
@AliasFor("name")
String value() default "";

/** @deprecated */
@Deprecated
String serviceId() default "";

String contextId() default "";

@AliasFor("value")
String name() default "";

String qualifier() default "";

String url() default "";

boolean decode404() default false;

Class<?>[] configuration() default {};

Class<?> fallback() default void.class;

Class<?> fallbackFactory() default void.class;

String path() default "";

boolean primary() default true;
}

可以看出FeignClient中有两种fallbackFactory服务降级的方式,一种是fallback(),另一种是fallbackFactory()方式

使用fallbackFactory()进行服务降级

创建消费者工程04-consumer-fallbackfactory-8080

创建工程

复制04-consumer-hystrix-8080 工程,并重命名为04-consumer-fallbackfactory-8080。

同样是在消费者端

修改配置文件

如果要使用fallbackfactory,需要对配置文件进行修改,需要添加feign对hystrix的支持

image-20200415200235544

定义降级处理类

一般定义在service包下

image-20200415200451472

image-20200413151153479

image-20200413151158369

image-20200413151205761

不要忘记交给Spring容器管理

修改Feign 接口

将降级处理类定义到DepartService中

image-20200413151220239

image-20200416090552512

类级别的优先级要高于方法级别的优先级

使用fallback()进行服务降级

该实现方案会直接将fallbackMethod 的服务降级给屏蔽掉。

创建消费者工程04-consumer-fallbackfeign-8080

创建工程

复制04-consumer-fallbackfactory-8080 工程,并重命名为04-consumer-fallbackfeign-8080。

定义降级处理类

image-20200416090828538

image-20200413151306662

image-20200413151317513

image-20200413151325234

修改Feign 接口

将原来的fallbackFactory 属性更换为fallback 属性。

将降级处理类定义到DepartService中

image-20200413151346820

输出结果

image-20200416090337473

说明类级别的优先级要高于方法级别的优先级

Hystrix 高级属性配置

执行隔离策略

执行隔离策略有两大作用:防止服务熔断,防止服务雪崩。

对某种依赖的请求数量进行限制的方式,称为执行隔离。

image-20200416102206464

image-20200416103214219

这里超时发生在Network Read/Connect Timeouts

image-20200416104251046

这里超时发生在Thread Timeouts

类型

隔离请求的方式有两种类型:

  • 线程隔离:Hystrix 的默认隔离策略。系统会创建一个依赖线程池,为每个依赖请求分配一个独立的线程,而每个依赖所拥有的线程数量是有上限的。当对该依赖的调用请求数量达到上限后再有请求,则直接拒绝该请求,并对该请求做降级处理。所以对某依赖的并发量取决于为该依赖线程池所分配的线程数量。

    image-20200416104533278

  • 信号量隔离:对依赖的调用所使用的线程仍为请求线程,即不会为依赖请求再新创建新的线程。但系统会为每种依赖分配一定数量的信号量,而每个依赖请求分配一个信号。当对该依赖的调用请求数量达到上限后再有请求,则直接拒绝该请求,并直接对该请求做降级处理。所以对某依赖的并发量取决于为该依赖所分配的信号量数量。

    image-20200416104606113

    信号量没有信号量池的概念,只是为了与上面线程隔离做对比

对比

  • 线程是进程的一个执行体,其具有独立运行的特性,而信号量却不是,其仅仅是线程执行的条件。
  • 线程隔离中请求线程与提供者调用线程不是同一个线程,而信号量隔离中请求线程与调用线程是同一个线程。
  • 线程隔离的执行效率要高于信号量隔离的,因为线程隔离的执行体数量是信号量隔离的2 倍。
  • 线程隔离使每台主机处理请求的数量是有限制的,因为主机线程数量是有上限的。而信号量隔离不同,其没有上限,因为所谓信号量就是一个计数器,是一个数值,其不存在上限。
  • 在服务器少而请求并发量大的情况下不建议使用线程隔离,否则可能会使系统对请求的并发能力下降。
  • 线程隔离便于控制反馈给客户端的降级时间。

修改策略

若是在配置文件中,则可以通过以下设置修改:

  • hystrix.command.default.execution.isolation.strategy=thread
  • hystrix.command.default.execution.isolation.strategy=semaphore

若是在代码中,则可通过以下语句修改。

  • HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)
  • HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)

默认值

在ystrixCommandProperties 类的构造器中设置有这些高级属性的默认值。

image-20200413151613472

执行隔离其它属性

线程执行超时时限时间设置

execution.isolation.thread.timeoutInMilliseconds

Default Value 1000
Default Property hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
Instance Property hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds
How to Set Instance Default HystrixCommandProperties.Setter() .withExecutionTimeoutInMilliseconds(int value)

配置文件中进行设置(全局性设置)

image-20200416092901543

方法中设置(局部性设置)

image-20200416095628784

可以在HystrixCommandProperties.java中查看execution.isolation.thread.timeoutInMilliseconds的属性和默认值

设置超时时限是否开启

execution.timeout.enabled

在默认的线程执行隔离策略中,关于线程的执行时间,可以为其设置超时时限。当然,首先通过下面的属性开启该超时时限,该属性默认是开启的,即默认值为true。若要关闭,则可以配置文件中设置该属性的值为false。

execution.timeout.enabled

Default Value true
Default Property hystrix.command.default.execution.timeout.enabled
Instance Property hystrix.command.HystrixCommandKey.execution.timeout.enabled
How to Set Instance Default HystrixCommandProperties.Setter() .withExecutionTimeoutEnabled(boolean value)

可以在HystrixCommandProperties.java中查看execution.timeout.enabled的属性和默认值

image-20200413151639477

在开启了执行线程超时时限后,可以通过以下属性设置时限长度。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds

其默认值为1000 毫秒。这就是前面引入时为什么sleep(4)是也报超时异常的原因,只要超过了1 秒就会超时。

image-20200413151701021

超时中断

当线程执行超时时是否中断线程的执行。默认为true,即超时即中断。通过以下属性进行设置。

execution.isolation.thread.interruptOnTimeout

hystrix.command.default.execution.isolation.thread.interruptOnTimeout

取消中断

在线程执行过程中,若请求取消了,当前执行线程是否结束呢?由该值设置。默认为false,即取消后不中断。通过以下属性进行设置。

execution.isolation.thread.interruptOnCancel

hystrix.command.default.execution.isolation.thread.interruptOnCancel

信号量数量

若采用信号量执行隔离策略,则可通过以下属性修改信号量的数量,即对某一依赖所允许的请求的最高并发量,默认是10

execution.isolation.semaphore.maxConcurrentRequests

hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests

服务降级属性

降级请求最大数量

fallback.isolation.semaphore.maxConcurrentRequests

该属性仅限于信号量隔离。当信号量已用完后再有请求到达,并不是所有请求都会进行降级处理,而是在该属性设置值范围内的请求才会发生降级,其它请求将直接拒绝。

hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests

服务降级开关

fallback.enabled

无论是线程隔离还是信号量隔离,当请求数量到达其设置的上限后再有请求到达是否会对请求进行降级处理,取决于该属性值的设置。若该属性值设置为false,则不进行降级,而是直接拒绝请求。

hystrix.command.default.fallback.enabled

服务熔断属性

熔断功能开关

circuitBreaker.enabled

设置当前应用是否开启熔断器功能,默认值为true。

hystrix.command.default.circuitBreaker.enabled

熔断器开启阈值

circuitBreaker.requestVolumeThreshold

当在时间窗内(10 秒)收到的请求数量超过该设置的数量后,将开启熔断器。默认值为20。

注意,开启熔断器是指将拒绝所有请求;关闭熔断器是指将使所有请求通过。

hystrix.command.default.circuitBreaker.requestVolumeThreshold

熔断时间窗

circuitBreaker.sleepWindowInMilliseconds

当熔断器开启该属性设置的时长后,会尝试关闭熔断器,以恢复被熔断的服务。默认值为5000 毫秒。

hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds

熔断开启错误率

circuitBreaker.errorThresholdPercentage

当请求的错误率高于该百分比时,开启熔断器。默认值为50,即50%。

hystrix.command.default.circuitBreaker.errorThresholdPercentage

强制开启熔断器

circuitBreaker.forceOpen

设置熔断器无需条件开启,拒绝所有请求。默认值为false。

hystrix.command.default.circuitBreaker.forceOpen

强制关闭熔断器

circuitBreaker.forceClosed

设置熔断器无需条件的关闭,通过所有请求。默认值为false。

hystrix.command.default.circuitBreaker.forceClosed

线程池相关属性

关于执行线程的线程池,可以通过以下的这些属性设置。

image-20200413152031954

Dashboard 监控仪表盘

Hystrix Dashboard 仪表盘用于以GUI 的形式展示消费者的执行情况,包括其处理器方法与Service 方法的调用行情况,及熔断器CircuitBreaker 的状态等。当然,这些显示出的数据都是在指定时间窗内的执行情况及状态信息。

总步骤

  • 添加hystrix-dashboard 与actuator 依赖
  • 配置文件中开启actuator 的hystrix.stream 监控终端
  • 在启动类上添加@EnableHystrixDashboard 注解

定义消费者工程04-consumer-dashboard-8080

Hystrix-dashboard 用于监控Hystrix 服务降级情况,所以应添加在消费者工程中。

创建工程

本例完全可以直接在04-consumer-fallbackfeign-8080 工程中进行修改,为了便于演示,这里又新建了一个工程。复制工程04-consumer-fallbackfeign-8080,并重命名为04-consumer-dashboard-8080。

添加依赖

1
2
3
4
5
6
7
8
9
10
<!-- hystrix-dashboard依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--actuator依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

修改配置文件

在配置文件中添加如下内容,用于开启actuator 的相关监控终端,并调整Hystrix 隔离线程执行的超时时限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 开启actuator的hystrix.stream 相关监控终端,通过SSE对hystrix.stream中的数据进行解析
management:
endpoints:
web:
exposure:
include: hystrix.stream
# 设置服务熔断时限
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000

修改启动类

image-20200416111025360

image-20200413152501144

定义提供者工程04-provider-8081

创建工程

复制03-provider-8081 工程,并重命名为04-provider-8081。因为03-provider-8081 工程的Service 接口实现类中的getDepartById()方法中具有sleep()休眠功能。

修改Service 实现类

image-20200416124541810

这里仅修改getDepartById()方法。

为了测试方便,这里将id 值设置为sleep()的时间,这样设置没有业务上的意义。

为了演示效果,这里测试时id 的取值为2、4。当取2 时,不会超时,但当取4 时,隔离线程执行会超时。

image-20200413152556915

仪表盘

SSE,Server-Send Events,H5 中的一个子规范。

image-20200419084433132

localhost:8080是设置包含Hystrix的Eureka的地址

GUI 介绍

image-20200413152625006

关于Turbine

我们在使用前面的方式通过Dashboard 监控仪表盘来查看应用的运行状态是很方便的,但只能查看到某一个主机的情况。若该应用是一个集群,想查看整个集群的运行状态是无法实现的。

此时Turbine 就解决了这个问题,其可以使用一个Dashboard 来查看你想查看的一个集群或若干个集群的运行状态。

Turbine 聚合监控–监控默认组集群

整体架构

image-20200413152709302

总步骤

Turbine Client

  • 至少要有actuator 与neflix-hystrix 依赖
  • 在配置文件中必须开启acturator 的hystrix.stream 监控终端

Turbine Server

  • 至少要有如下依赖:
    • netflix-turbine 依赖
    • netflix -hystrix-dashboard 依赖
    • netflix -hystrix 依赖
    • actuator 依赖
    • eureka client 依赖
  • 在配置文件中要配置turbine:指定要监控的group 及相应的微服务名称

实现

创建消费者工程04-consumer-turbine-client-8180

创建工程

复制04-consumer-dashboard-8080 工程,并重命名为04-consumer-turbine-client-8180。

修改依赖

由于当前应用无需使用Dashboard,所以可以将Dashboard 依赖去掉。但必须要保证Turbine 所监控的应用中具有actuator 与hystrix 依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- hystrix-dashboard依赖-->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>-->
<!--</dependency>-->
<!--actuator依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

修改配置文件

image-20200413153003688

修改启动类

image-20200413153013674

创建消费者工程04-consumer-turbine-client-8280

创建工程

复制04-consumer-turbine-client-8180 工程,并重命名为04-consumer-turbine-client-8280。

修改配置文件

image-20200413153048008

创建Turbine工程00-hystrix-turbine-8888

创建工程

复制00-eurekaserver-8000 工程,并重命名为00-hystrix-turbine-8888。

修改依赖

由于这是一个Eureka Client,所以将原来的Eureka Server 依赖删除,添加Eureka Client依赖。

  • netflix-turbine 依赖
  • netflix -hystrix-dashboard 依赖
  • netflix -hystrix 依赖
  • actuator 依赖
  • eureka client 依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- hystrix-turbine依赖--> 
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<!-- hystrix-dashboard依赖--> <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--actuator依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

修改启动类

image-20200413153524515

修改配置文件

image-20200413153535563

image-20200413153541385

输出结果

image-20200419101344449

Turbine 聚合监控–监控多个组集群

为了更加方便对集群的运行状态的监控,Turbine 将集群进行了分组。前面我们监控了两个集群,这两个集群默认被分为了一个组,是默认组。我们也可以将集群划分到多个组中使用同一个Turbine 进行分别监控。

整体架构

image-20200413153615634

分组监控原理

Turbine 对于集群分组进行监控的原理是,在集群之上再添加一种分类:组。为每个集群中的Sever 都添加一个groupId,而Turbine 是基于groupId 进行监控的。

这个groupId 是基于自定义的Eureka 元数据实现的。

Eureka 元数据是指,Eureka Client 向Eureka Server 注册时的描述信息,注册数据。其有两种类型:

  • 标准元数据:Eureka 中已经定义好的客户端描述信息。
  • 自定义元数据:在客户端配置文件中自己指定的key-value 数据。

实现

创建消费者工程04-consumer-turbine-client-8380

创建工程

复制04-consumer-turbine-client-8180 工程,并重命名为04-consumer-turbine-client-8380。

修改配置文件

image-20200413153723148

创建消费者工程04-consumer-turbine-client-8480

创建工程

复制04-consumer-turbine-client-8380 工程,并重命名为04-consumer-turbine-client-8480。

修改配置文件

image-20200413153755939

创建消费者工程04-consumer-turbine-client-8580

创建工程

复制04-consumer-turbine-client-8480 工程,并重命名为04-consumer-turbine-client-8580。

修改配置文件

image-20200413153828342

创建消费者工程04-consumer-turbine-client-8680

创建工程

复制04-consumer-turbine-client-8580 工程,并重命名为04-consumer-turbine-client-8680。

修改配置文件

image-20200413153910729

创建Turbine工程00-hystrix-turbine-7777

创建工程

复制00-hystrix-turbine-8888 工程,并重命名为00-hystrix-turbine-7777。

修改配置文件

image-20200413153947927

输出结果

image-20200419141524253

image-20200419141652816

服务降级报警机制

无论哪种原因启用了服务降级,系统都应该向管理员发出警报通知管理员,例如向管理员发送短信。这种发生服务降级后向管理员发出警报的机制称为服务降级报警机制。

实现

创建工程04-consumer-fallbackalarm-8080

复制前面任意的服务熔断消费者工程,这里复制04-consumer-feign-fallbackmethod-8080
工程,并重命名为04-consumer-fallbackalarm-8080。

报警标识要增加一个有效时间,这样才最好,增加有效时间放在Redis中最好

高并发情况下Redis 存在的问题:

  • 缓存穿透
  • 缓存雪崩
  • 热点缓存:双重检测锁

添加依赖

1
2
3
4
5
<!--Spring Boot与Redis整合依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

修改配置文件

image-20200413154252699

修改处理器

修改处理器,首先创建一个线程池,包含5 个线程,然后再修改服务降级方法。

image-20200413154316168

image-20200413154324943

然后再定义两个方法。

image-20200413154334811

image-20200413154341461

第5章微服务网关Zuul

简介

网关简介

网关是系统唯一对外的入口,介于客户端与服务器端之间,用于对请求进行鉴权、限流、路由、监控等功能。

网关就像海关一样

Zuul官网简介

image-20200413154500668

【原文】Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application. As an edge service application, Zuul is built to enable dynamic routing, monitoring, resiliency and security. It also has the ability to route requests to multiple Amazon Auto Scaling Groups as appropriate.

【翻译】ZUUL 是从设备和web 站点到Netflix 流应用后端的所有请求的前门。作为边界服务应用,ZUUL 是为了实现动态路由、监视、弹性和安全性而构建的。它还具有根据情况将请求路由到多个Amazon Auto Scaling Groups 的能力。

Zuul综合说明

Zuul 主要提供了对请求的路由与过滤功能。

路由:将外部请求转发到具体的微服务实例上,是外部访问微服务的统一入口。

过滤:对请求的处理过程进行干预,对请求进行校验、鉴权等处理。

image-20200413154554222

将官方的架构图再进一步抽象,就变为了下图。

消费者调用提供者是通过Eureka进行调用,提供者注册Eureka之后,消费者通过Eureka获取到注册列表然后进行负载均衡调用

客户端可以通过网关来调用消费者

image-20200413154604629

consumer通过Eureka Server获取到provider的信息

Zuul通过Eureka Server获得consumer的信息

基本环境搭建

这里需要一个EurekaServer,一个zuul 网关,及两个消费者。

创建工程05-zuul-consumer–8080

创建工程

复制工程04-consumer-feign-fallbackmethod-8080,并重命名为05-zuul-consumer–8080。

修改配置文件

image-20200416132748394

修改处理器

image-20200413154718415

image-20200413154724972

创建工程05-zuul-consumer–8090

创建工程

复制工程05-zuul-consumer–8080,并重命名为05-zuul-consumer–8090。

修改配置文件

image-20200413154805161

修改处理器

image-20200413154824919

image-20200413154830026

服务路由00-zuul-9000

当用户提交某请求后,该请求应交给哪个微服务的哪个主机来处理,通过配置可以完成。

1
2
3
4
5
指定微服务的路径规则
*为通配符
/** 可以匹配0到多级路径
/* 只能匹配一级路经
/? 只能匹配一级路经,且路径只能包含一个字符

创建zuul网关工程

创建工程

复制00-hystrix-turbine-8888 工程,并重命名为00-zuul-9000。

导入依赖

首先除了eureka client 依赖后,将其它依赖全部删除,然后再导入zuul 依赖。

1
2
3
4
5
<!--zuul依赖--> 
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

修改启动类

image-20200413155041373

修改配置文件

image-20200413155104984

输出结果

image-20200416135403039

image-20200416135324115

路由策略配置

修改配置文件

前面的访问方式,需要将微服务名称暴露给用户,会存在安全性问题。所以,可以自定义路径来替代微服务名称,即自定义路由策略。

在配置文件中添加如下配置。
image-20200413155135734

输出结果

image-20200416135952942

image-20200416140812125

路由前辍

在配置路由策略时,可以为路由路径配置一个统一的前辍,以便为请求归类。在前面的配置文件中增加如下配置。

image-20200413155205285

服务名屏蔽

前面的设置方式可以使用指定的路由路径访问到相应微服务,但使用微服务名称也可以访问到,为了防止服务侵入,可以将服务名称屏蔽。

在配置文件中添加如下内容:

image-20200413155256728

举例

image-20200416142333847

输出结果

image-20200416142430247

image-20200416142533256

路径屏蔽

可以指定屏蔽掉的路径URI,即只要用户请求中包含指定的URI 路径,那么该请求将无法访问到指定的服务。通过该方式可以限制用户的权限。

image-20200413155314649

敏感请求头屏蔽

image-20200416145300050

默认情况下,像Cookie、Set-Cookie 等敏感请求头信息会被zuul 屏蔽掉,我们可以将这些默认屏蔽去掉,当然,也可以添加要屏蔽的请求头。

修改05-zuul-consumer-8080 工程

修改该工程处理器中的方法和服务降级方法

image-20200413155416772

修改00-zuul-9000 配置文件

屏蔽掉指定的敏感头信息,其会将原来默认的Cookie、SetCookie、Authorization敏感头信息放开

image-20200413155429467

输出结果

未修改敏感请求头屏蔽之前

image-20200416151609393

image-20200416151636878

修改敏感请求头屏蔽之后

image-20200416151720944

负载均衡

image-20200416152240740

用户提交的请求被路由到一个指定的微服务中,若该微服务名称的主机有多个,则默认采用负载均衡策略是轮询。

创建三个消费者工程

这里要创建三个工程。这三个工程均复制于04-consumer-fallbackmethod-8080 工程。

创建05-consumer-8080

image-20200413155514494

修改处理器

image-20200413155602017

创建05-consumer-8081

修改配置文件

image-20200413155630896

修改处理器

image-20200413155639276

创建05-consumer-8082

修改配置文件

image-20200413155700131

修改处理器

image-20200413155708914

修改配置文件

在配置文件中添加如下内容

image-20200413155815984

输出结果

这里的8180相当于8081,8280相当于8082,8380相当于8083

image-20200416160705064

image-20200416160802215

image-20200416160824580

修改负载均衡策略

修改00-zuul-9000 的启动类,在其中直接添加如下代码,更换负载均衡策略为随机算法。

image-20200413155838950

服务降级

当消费者调用提供者时由于各种原因出现无法调用的情况时,消费者可以进行服务降级。那么,若客户端通过网关调用消费者无法调用时,是否可以进行服务降级呢?当然可以,zuul具有服务降级功能。

创建工程00-zuul-fallback-9000

复制00-zuul-9000 工程,并重命名为00-zuul-fallback-9000。

修改配置文件

这里其实并没有对配置文件进行设置,仅仅是将一些不必要的设置给删除了。

image-20200413160036055

定义fallback 类

这里仅仅是对名称为abcmsc-consumer-depart-8080 的微服务进行降级处理。

实现接口FallbackProvider

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
@Component
public class ConsumerFallback implements FallbackProvider {
@Override
public String getRoute() {
// 对指定的微服务进行降级
// return "abcmsc-consumer-depart-8080";
// 指定对所有微服务进行降级
return "*";
}

/**
*
* @param route 请求中的微服务名称
* @param cause
* @return
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
// 若微服务不是abcmsc-consumer-depart-8080,则不进行降级
// if (!"abcmsc-consumer-depart-8080".equals(route)) {
// return null;
// }

// 仅对abcmsc-consumer-depart-8080进行降级
return new ClientHttpResponse() {
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}

@Override
public InputStream getBody() throws IOException {
String msg = "fallback:" + route;
return new ByteArrayInputStream(msg.getBytes());
}

@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.SERVICE_UNAVAILABLE;
}

@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.SERVICE_UNAVAILABLE.value();
}

@Override
public String getStatusText() throws IOException {
return HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase();
}

@Override
public void close() {
// 写资源释放代码
}
};
}
}

image-20200413155952145

参数route:请求中的微服务名称

image-20200413160002019

image-20200413160006682

image-20200413160012981

输出结果

image-20200416165046853

请求过滤

在服务路由之前、中、后,可以对请求进行过滤,使其只能访问它应该访问到的资源,增强安全性。此时需要通过ZuulFilter 过滤器来实现对外服务的安全控制。

路由过滤架构

image-20200416165634058

需求

该过滤的条件是,只有请求参数携带有user 的请求才可访问/abc8080 工程,否则返回401,未授权。当然,对/abc8090 工程的访问没有限制。简单来说就是,只有当访问/abc8080且user 为空时是通不过过滤的,其它请求都可以。

实现

路由过滤实现00-zuul-filter-9000

创建工程

复现00-zuul-9000 工程,并重命名为00-zuul-filter-9000。

此时配置文件不需要修改

定义RouteFilter 类

1
2
3
4
5
6
7
8
9
/**
* to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
* "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
* We also support a "static" type for static responses see StaticResponseFilter.
* Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
*
* @return A String representing that type
*/
abstract public String filterType();

指定过滤的时机,即pre(前)、route(过程)、post(后)、error(发生异常)

1
2
3
4
5
6
7
/**
* filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not
* important for a filter. filterOrders do not need to be sequential.
*
* @return the int order of a filter
*/
abstract public int filterOrder();

指定过滤的顺序,值越小优先级越高,而且值支持负值,最小值为-3

image-20200413160928237

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface IZuulFilter {
/**
* a "true" return from this method means that the run() method should be invoked
*
* @return true if the run() method should be invoked. false will not invoke the run() method
*/
boolean shouldFilter();

/**
* if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter
*
* @return Some arbitrary artifact may be returned. Current implementation ignores it.
* @throws ZuulException if an error occurs during execution.
*/
Object run() throws ZuulException;

}

只要shouldFilter()方法返回true,就意味着下面的run()方法被调用,而且run()方法是IZuulFilter的核心方法

image-20200416212122913

注:

注解@Slf4j的使用

声明:

如果不想每次都写private final Logger logger = LoggerFactory.getLogger(当前类名.class);

可以用注解@Slf4j

1.使用idea首先需要安装Lombok插件

image-20200416174046199

2.在pom文件加入lombok的依赖

1
2
3
4
5
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version><!--版本号自己选一个就行-->
</dependency>

3.类上面添加@Sl4j注解,然后使用log打印日志

image-20200416195233322

请求过滤的应用

应用一:令牌桶限流

通过对请求限流的方式避免系统遭受“雪崩之灾”。

我们下面的代码使用Guava 库的RateLimit 完成限流的,而其底层使用的是令牌桶算法实现的限流,所以我们先来学习一下令牌桶限流算法。

原理

令牌桶算法

该算法可以应对突发流量情况。主动获取令牌

image-20200413161019194

扩展:漏斗限流算法

被动获取水滴

image-20200413161042763

实现

实现00-zuul-tokenbucket-9000

创建工程

复制工程00-zuul-filter-9000,并命名为00-zuul-tokenbucket-9000。

修改RouteFilter 类

这里导入的RateLimiter 是Google 的类。这里为了测试的方便,使令牌桶每秒仅生成2个令牌。即每秒可以处理2 个请求。

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
@Component
public class RouteFilter extends ZuulFilter {
// 创建一个令牌桶,每秒生成2个令牌
private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);

@Override
public String filterType() {
// "pre",进行路由之前过滤
return FilterConstants.PRE_TYPE;
}

@Override
public int filterOrder() {
return -5;
}

@Override
public boolean shouldFilter() {
// 获取当前的请求上下文对象
RequestContext context = RequestContext.getCurrentContext();
// RATE_LIMITER.tryAcquire():
// 若可以立即获取到1个令牌,则返回true,否则返回false。不阻塞
// RATE_LIMITER.tryAcquire(5, 3, TimeUnit.SECONDS):
// 若在3秒内可以立即获取到5个令牌,则返回true,否则返回false。不阻塞
// RATE_LIMITER.acquire():获取1个令牌,若获取不到,则阻塞直到获取到为止
// RATE_LIMITER.acquire(5):获取5个令牌,若获取不到,则阻塞直到获取到为止
if (!RATE_LIMITER.tryAcquire()) {
// 指定当前请求未通过zuul过滤
context.setSendZuulResponse(false);
// 向客户端响应“请求数量太多”,429
context.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
}
return true;
}

@Override
public Object run() throws ZuulException {
System.out.println("通过过滤");
return null;
}
}

image-20200413161129106

image-20200417072625203

应用二:多维请求限流

原理

使用Guava 的RateLimit 令牌桶算法可以实现对请求的限流,但其限流粒度有些大。有个老外使用路由过滤,针对Zuul 编写了一个限流库(spring-cloud-zuul-ratelimit),提供多种细粒度限流策略,在导入该依赖后我们就可以直接使用了。

其限流策略,即限流查验的对象类型有:

  • user:针对用户的限流,即对单位时间窗内经过网关的用户数量的限制。
  • origin:针对客户端IP 的限流,即对单位时间窗内经过网关的IP 数量的限制。
  • url:针对请求URL 的限流,即对单位时间窗内经过网关的URL 数量的限制。

实现

实现00-zuul-ratelimit-9000

创建工程

复制工程00-zuul-tokenbucket-9000,并命名为00-zuul-ratelimit-9000。

删除RouteFilter

删除RouteFilter 类,该工程中不使用。

添加依赖

若要使用spring-cloud-zuul-ratelimit 限流库,首先需要导入该依赖,然后再在配置文件中对其进行相关配置。

1
2
3
4
5
<!-- spring-cloud-zuul-ratelimit依赖-->
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId> <version>2.0.5.RELEASE</version>
</dependency>

修改配置文件

在配置文件中添加如下配置。

image-20200417074252284

添加异常处理页面

在src/main/resources 目录下再定义新的目录public/error,必须是这个目录名称。然后在该目录中定义一个异常处理页面,名称必须是异常状态码,扩展名必须为html,其他不需要配置

image-20200413161402256

应用三:灰度发布

原理

什么是灰度发布

灰度发布,又名金丝雀发布,是系统迭代更新、平滑过渡的一种上线发布方式。

Zuul 灰度发布原理

生产环境中,可以实现灰度发布的技术很多,我们这里要讲的是zuul 对于灰度发布的实现。而其实现也是基于Eureka 元数据的。

实现

修改三个消费者工程

修改05-consumer-8080

添加Eureka的元数据

image-20200413161540476

创建05-consumer-8081

添加Eureka的元数据

image-20200413161605986

创建05-consumer-8082

添加Eureka的元数据

image-20200413161626817

哪一部分转到running-host,哪一部分转到gray-host,实际上由Zuul来进行过滤

添加元数据对整个Eureka生态没有任何影响

创建zuul工程00-zuul-gray-9000

创建工程

复制工程00-zuul-9000,并重命名为00-zuul-gray-9000。

添加依赖

在pom 文件中增加新的依赖。

通过Eureka元数据来实现灰度发布的

1
2
3
4
5
<dependency>
<groupId>io.jmnarloch</groupId>
<artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
<version>2.1.0</version>
</dependency>

修改配置文件

在00-zuul-gray-9000中修改

将配置文件内容修改如下(仅仅就是将之前配置的那些多余内容删除了):

image-20200417083744766

定义过滤器一

定义类GrayFilter

image-20200413161834942

image-20200413161842001

image-20200413161848400

这里的返回值不需要处理

这里将选择方式放到请求头里,实现了不同的浏览器转向不同的主机,这就需要开发两套前端页面,当然选择方式也可以放到其他地方,即不在请求头中实现

定义过滤器二(另一种灰度发布规则)

image-20200413161905115

image-20200413161914257

Zuul 的高可用

Zuul 的高可用非常关键,因为外部请求到后端微服务的流量都会经过Zuul。故而在生产环境中,我们一般都需要部署高可用的Zuul 以避免单点故障。

作为整个系统入口路由的高可用,需要借助额外的负载均衡器来实现,例如Nginx、HAProxy、F5 等。在Zuul 集群的前端部分部署负载均衡服务器。Zuul 客户端将请求发送到负载均衡器,负载均衡器将请求转发到其代理的其中一个Zuul 节点。这样,就可以实现Zuul的高可用。

对于Zuul的高可用,要加上SpringCloud之外的技术

第6章分布式配置管理Spring Cloud Config

集群中每一台主机的配置文件都是相同的,对配置文件的更新维护就成为了一个棘手的问题,Spring Cloud Config 是负责Spring Cloud 中配置文件维护管理的配置中心。

spring cloud config 概述

官网介绍

image-20200413173612295

【原文】Spring Cloud Config provides server and client-side support for externalized configuration in a distributed system. With the Config Server you have a central place to manage external properties for applications across all environments.

【翻译】Spring Cloud Config 为分布式系统中的外部化配置提供服务器和客户端支持。使用Config 服务器,可以在中心位置管理所有环境中应用程序的外部属性。

统合说明

Spring Cloud Config 就是对微服务的配置文件进行统一管理的。其工作原理是,我们首先需要将各个微服务公共的配置信息推送到GitHub 远程版本库。然后我们再定义一个Spring Cloud Config Server,其会连接上这个GitHub 远程库。这样我们就可以定义Config 版的EurekaServer、提供者与消费者了,它们都将作为Spring Cloud Config Client 出现,它们都会通过连接Spring Cloud Config Server 连接上GitHub 上的远程库,以读取到指定配置文件中的内容。

相关产品:
百度:disconf,阿里:diamand,zookeeper这些都可以作为配置中心

原理

Config Server 可以组装的最终配置文件格式有三种:yml、properties、json。

远程库可以是github、gitLab、SVN等

image-20200417092113097

消费者集群、Eureka集群和提供者集群的配置文件都不存放在本地,而是存放在远程库,需要配置文件的时候提交下载请求,Config Server会把请求提交给远程库,Config Server再从远程库中拉取请求相关的配置文件,之后Config Server会按需求要求将配置文件进行再组装,就是进行修改,变成用户需要的,然后各个Config Client再拉取组装过的配置文件

需要注意的是Eureka Server都是作为Config Client出现的,也就是Config Server不需要注册到Config Client里面,这就意味着在Eureka里面要添加Config Client依赖,而在Config Server中不需要添加Eureka Client的依赖

实现配置文件工程

总步骤

  • 导入config server 的依赖,将其它所有依赖删除
  • 在启动类上添加@EnableConfigServer 注解
  • 在配置文件中指定要连接的git 远程库地址等信息,如果是http格式,别忘记输入用户名和密码

创建工程

创建配置文件工程00-configserver-9999

复制00-zuul-9000 工程,并重命名为00-configserver-9999。

添加依赖

删除全部依赖,然后再导入Spring Cloud Config Server 依赖。

1
2
3
4
<dependency> 
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

修改配置文件

配置文件中所有内容全部删除,加入如下内容:

image-20200417154511701

image-20200413174200154

修改启动类

image-20200413174240187

image-20200417160555196

image-20200417160903390

image-20200417160944411

1.加载的过程比较慢,但是不会影响执行效率,因为是启动过程较慢,而不是执行慢,启动与用户是没有关系的,用户是体现不到的

2.可以保证安全性,第一点Config Server一般不会放在公网上,都是公司内部的局域网访问,第二点所有的访问都需要通过网关,网关可以过滤请求

修改windows的host文件

在其中添加如下内容

image-20200413174337495

实现Config 版的Eureka 服务器

总步骤

  • 导入config 的客户端依赖
  • 定义bootstrap.yml 配置文件,在其中指定要连接的config server 地址
  • 删除之前的application.yml 文件

创建工程

定义Config 版的Eureka 服务器06-config-eurekaserver

复制00-eurekaserver-8000 工程,并重命名为06-config-eurekaserver。

添加config客户端依赖

在原工程依赖的基础上添加spring cloud config 的客户端依赖。

1
2
3
4
5
<!--spring cloud config客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

定义bootstrap.yml

  • bootstrap.yml 中配置的是应用启动时所必须的配置信息数据,即若没有这些配置,则应用无法启动。
  • application.yml 中配置的是应用运行过程中所必须的配置信息,即若没有这些配置文件,则应用无法运行。
  • bootstrap.yml 优先于application.yml 进行加载。

image-20200413174725190

启动类

image-20200417163104153

image-20200417163428174

才能进行如下输入地址

image-20200417163758089

此时Eureka已经成为Config Client

实现Config 版的提供者

创建工程

定义Config 版的提供者06-config-provider

复制03-provider-8082 工程,重命名为06-config-provider。

添加config客户端依赖

在原工程依赖的基础上添加spring cloud config 的客户端依赖。

1
2
3
4
5
<!--spring cloud config客户端依赖--> 
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

定义bootstrap.yml

image-20200413174948625

实现Config 版的消费者

创建工程

定义Config 版的消费者06-config-consume

复制04-consumer-hystrix-8080 工程,并重命名为06-config-consumer。

添加config客户端依赖

在原工程依赖的基础上添加spring cloud config 的客户端依赖。

1
2
3
4
5
<!--spring cloud config客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

定义bootstrap.yml

image-20200413190531439

此时要启动4个工程,分别按顺序是Config Server、Eureka Server、提供者、消费者

输出结果

image-20200417170204943

image-20200417170306981

配置自动更新

Webhooks

GitHub 中提供了Webhooks 功能来确保远程库中的配置文件更新后,客户端中的配置信息也可以实时更新。具体实现方式可参考如下一篇博文:

https://blog.csdn.net/qq_32423845/article/details/79579341

这种方式存在很大的弊端,并不适合生产环境下的使用,而Spring Cloud Bus 消息总线系统解决了这些问题。所以,生产环境下一般使用的是Spring Cloud Bus 完成配置文件的自动更新。

webhooks 存在的弊端:

  • 每个config client 都需要在git 远程库中注册,若config client 数量发生变化,则需要修改git 远程库。
  • 每个config client 若要在不重启的情况下更新配置,则都需要提交一个actuator 的post请求。这样的话,若存在多个config client 需要更新,则需要提交多个这种post 请求。

Spring Cloud Bus概述

官方简介

image-20200413190748187

【翻译】用于将服务和服务实例与分布式消息系统链接在一起的事件总线。在集群中传播状态更改很有用(例如配置更改事件)。

工作原理

消费总结系统整合了Java 的事件处理机制和消费中间件

image-20200413190846044

配置自动更新原理

image-20200413190909248

修改远程库中的配置文件

为了方便后面的测试,这里分别在提供者配置文件application-provider-config.yml 与消费者配置文件application-consumer-config.yml 中各添加了一个自定义属性。

未修改application-consumer-config.yml

image-20200418080708691

未修改application-provider-config.yml

image-20200418080910498

修改application-consumer-config.yml

image-20200418081113965

修改application-provider-config.yml

image-20200418082236005

实现

创建提供者工程06 -config-provider-bus

总步骤

  • 导入actuator 与bus-kafka 依赖
  • 在配置文件中指定要连接的kafka 集群,并开启actuator 的bus-refresh 监控终端
  • 在需要自动更新的类上添加@RefreshScope 注解

创建工程

复制06-config-provider,并重命名为06 -config-provider-bus。

导入依赖

1
2
3
4
5
6
7
8
9
<dependency> 
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-kafka</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>

修改配置文件

image-20200413191508428

修改接口实现类

image-20200413191532506

image-20200413191542392

创建消费者工程06 -config-consumer-bus

创建工程

复制06-config-consumer,并重命名为06 -config-consumer-bus。

导入依赖

1
2
3
4
5
6
7
8
9
<dependency> 
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-kafka</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>

修改配置文件

image-20200413191753725

修改处理器类

image-20200413191820277

提交修改配置文件的数据

image-20200418082638239

这里的端口号可以更改,可以是消费者、生产者、Eureka这些Config Client的端口号

此时不需要重启系统就能更改配置文件

输出结果

image-20200418083106850

image-20200418083133884

第7章调用链跟踪Spring Cloud Sleuth+Zipkin

Google—Dapper
淘宝-鹰眼-Eagleeye
京东-Hydra
大众点评-cat
新浪-watchman
唯品会-microscope
Twitter-Zipkin

Sleuth 简介

打开官网就可以看到对Sleuth 的一个简单功能介绍。

image-20200413192001927

【翻译】(Spring Cloud Sleuth 可以实现)针对Spring Cloud 应用程序的分布式跟踪,兼容Zipkin、HTrace 和基于日志的(如Elk)跟踪。

image-20200413192033412

【翻译】Spring Cloud Sleuth 为Spring Cloud 实现了一个分布式跟踪解决方案,大量借鉴了Dapper、Zipkin 和HTrace。对于大多数用户来说,Sleuth 是不可见的,并且你的当前应用与外部系统的所有交互都是自动检测的。你可以简单地在日志中捕获数据,或者将其发送到远程收集器中。

Spring Cloud Sleuth作为日志生成器,发送给Zipkin,Zipkin作为日志的收集器

Sleuth 基本理论

Spring Cloud Sleuth文档

Spring Cloud Sleuth 的官方文档中可以查看到服务跟踪的基本理论。

三大概念

服务跟踪理论中存在有跟踪单元的概念,而跟踪单元中涉及三个重要概念:trace、span,与annotation。

image-20200418090142761

trace 与span

  • trace:跟踪单元是从客户端所发起的请求抵达被跟踪系统的边界开始,到被跟踪系统向客户返回响应为止的过程,这个过程称为一个trace。

  • span:每个trace 中会调用若干个服务,为了记录调用了哪些服务,以及每次调用所消耗的时间等信息,在每次调用服务时,埋入一个调用记录,这样两个调用记录之间的区域称为一个span。

  • 关系:一个Trace 由若干个有序的Span 组成。
    Spring Cloud Sleuth 为服务之间调用提供链路追踪功能。为了唯一的标识trace 与span,系统为每个trace 与span 都指定了一个64 位长度的数字作为ID,即traceID 与spanID。

annotation

用于及时记录事件的实体,表示一个事件发生的时间点。这些实体本身仅仅是为了原理叙述的方便,对于Spring Cloud Sleuth 本身并没有什么必要性。这样的实体有多个,常用的有四个:

cs:Client Send,表示客户端发送请求的时间点。

sr,Server Receive,表示服务端接收到请求的时间点。

ss:Server Send,表示服务端发送响应的时间点。

cr:Client Receive,表示客户端接收到服务端响应的时间点。

Sleuth的日志采样

日志生成

只要在工程中添加了Spring Cloud Sleuth 依赖,那么工程在启动与运行过程中就会自动生成很多的日志。Sleuth 会为日志信息打上收集标记,需要收集的设置为true,不需要的设置为false。这个标记可以通过在代码中添加自己的日志信息看到。

image-20200413192558610

日志采样率

leuth 对于这些日志支持抽样收集,即并不是所有日志都会上传到日志收集服务器,日志收集标记就起这个作用。默认的采样比例为: 0.1,即10%。在配置文件中可以修改该值。若设置为1 则表示全部采集,即100%。

Sleuth 默认采用的是水塘抽样算法。

“跟踪日志”的生产者Sleuth

实现

创建提供者工程07-sleuth-provider-8081

创建工程

复制02-provider-8081,并重命名为07-sleuth-provider-8081。

导入依赖

1
2
3
4
5
<!--sleuth依赖--> 
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

修改处理器

如果有需要的话,可以在业务代码中添加自己的跟踪日志信息。

image-20200413200436198

image-20200413200444951

修改配置文件

将配置文件中有关日志的配置注释,否则看不到后面的演示结果。

image-20200413200508045

创建提供者工程07-sleuth-consumer-8081

创建工程

复制02-consume -8081,并重命名为07-sleuth-consumer-8081。

导入依赖.

1
2
3
4
5
<!--sleuth依赖--> 
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

修改处理器

image-20200413200655488

image-20200413200701684

配置文件

image-20200418092608813

zipkin 工作过程

zipkin简介

zipkin 是Twitter 开发的一个分布式系统APM(Application Performance Management,应用程序性能管理)工具,其是基于Google Dapper 实现的,用于完成日志的聚合。其与Sleuth联用,可以为用户提供调用链路监控可视化UI 界面。

官网

image-20200418093801520

zipkin系统结构

服务器组成

image-20200413200755937

zipkin 服务器主要由4 个核心组件构成:

  • Collector:收集组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为Zipkin 内部处理的Span 格式,以支持后续的存储、分析、展示等功能。
  • Storage:存储组件,它主要用于处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,也可以修改存储策略,例如,将跟踪信息存储到数据库中。
  • API:外部访问接口组件,外部系统通过这里的API 可以实现对系统的监控。
  • UI:用于操作界面组件,基于API 组件实现的上层应用。通过UI 组件用户可以方便而有直观地查询和分析跟踪信息。

日志发送方式

在Spring Cloud Sleuth + Zipkin 系统中,客户端中一旦发生服务间的调用,就会被配置在微服务中的Sleuth 的监听器监听,然后生成相应的Trace 和Span 等日志信息,并发送给Zipkin 服务端。

发送的方式主要有两种:

一种是通过via(经过) HTTP 报文的方式

image-20200418111413529

一种是通过Kafka、RabbitMQ 发送的方式

image-20200418111511600

zipkin 服务端搭建

image-20200423211418924

启动zipkin服务器

image-20200423211705305

下载

image-20200413200924131

启动

image-20200413200937075

访问zipkin服务器

image-20200413200953744

实现

创建zipkin 客户端工程-via

总步骤

  • 导入zipkin 客户端依赖
  • 在配置文件中指定zipkin 服务器地址,并设置Sleuth 采样率

创建提供者07-via-sleuth-provider-8081

创建工程

复制07-sleuth-provider-8081,并重命名为07-via-sleuth-provider-8081。

导入依赖

删除原来的sleuth 依赖,导入zipkin 依赖。

1
2
3
4
5
6
7
8
9
<!--sleuth依赖--> 
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-sleuth</artifactId>-->
<!--</dependency>-->
<!--zipkin客户端依赖,其包含了sleuth依赖-->
<dependency> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

打开spring-cloud-starter-zipkin 依赖,可以看到其已经包含了spring-cloud-starter-sleuth
依赖,所以可以将原来导入的sleuth 依赖删除。

image-20200413211732155

修改配置文件

在spring 属性下注册zipkin 服务器地址,并设置采样比例。

image-20200413211752610

创建消费者工程07-via-sleuth-consumer-8080

创建工程

复制07-sleuth-consumer-8081,并重命名为07-via-sleuth-consumer-8080。

导入依赖

删除原来的sleuth 依赖,导入zipkin 依赖。

1
2
3
4
5
6
7
8
9
10
<!--sleuth依赖--> 
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-sleuth</artifactId>-->
<!--</dependency>-->
<!--zipkin客户端依赖,其包含了sleuth依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

修改配置文件

在spring 属性下注册zipkin 服务器地址,并设置采样比例。

image-20200413211932913

sleuth + kafka + zipkin

默认情况下,Sleuth 是通过将调用日志写入到via 头部信息中的方式实现链路跟踪的,但在高并发下,这种方式的效率会非常低,会影响链路信息查看的。此时,可以让Sleuth将其生成的调用日志写入到Kafka 或RabbitMQ 中,让zipkin 从这些中间件中获取日志,效率会提高很多。

image-20200413212016732

创建提供者工程07-kafka-sleuth-provider -8081

总步骤

  • 导入kafka 依赖,注意仍需保留zipkin 客户端依赖
  • 在配置文件中指定要连接的kafka 集群,并指定zipkin 服务器的日志发送者是kafka

创建工程

复制07-sleuth-provider-8081,并重命名为07-sleuth-provider-kafka-8081。

导入依赖

添加kafka 依赖。

1
2
3
4
5
<!--kafka依赖--> 
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>

修改配置文件

image-20200413212158736

创建消费者工程07-kafka-sleuth-consumer-8080

创建工程

复制07-sleuth-consumer-8081,并重命名为07-sleuth-consumer-kafka-8081。

导入依赖

添加kafka 依赖。

1
2
3
4
5
<!--kafka依赖-->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>

修改配置文件

image-20200413212608373

启动运行

zk 启动

kafka 集群启动

zipkin 启动

在命令行启动zipkin

1
java -DKAFKA_BOOTSTRAP_SERVERS=kafkaOS1:9092 –jar zipkin.jar

启动应用

  • 启动Eureka

  • 启动提供者工程07-kafka-sleuth-provider -8081

  • 启动消费者工程07-kafka-sleuth-consumer-8080

第8章消息系统整合框架Spring Cloud Stream

简介

官网

image-20200413212904799

【原文】A lightweight event-driven microservices framework to quickly build applications that can connect to external systems. Simple declarative(声名式的)model to send and receive messages using Apache Kafka or RabbitMQ between Spring Boot apps.

【翻译】一个轻量级的事件驱动微服务框架,用于快速构建可连接到外部系统的应用程序。在Spring Boot 应用程序之间使用Kafka 或RabbitMQ 发送和接收消息的简单声明式模型。##

综合

Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力的框架。通过使用Spring Cloud Stream,可以有效简化开发人员对消息中间件的使用复杂度,让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。但是目前Spring Cloud Stream 只支持RabbitMQ 和Kafka 的自动化配置。

程序模型

spring cloud 官网首页中点击相应版本的参考文档。

image-20200413213025859

应用程序的核心部分(Application Core)通过inputs 与outputs 管道,与中间件连接,而管道是通过绑定器Binder 与中间件相绑定的。

stream kafka 微服务

总步骤

创建生产者步骤

  • 导入spring-cloud-stream-binder-kafka 依赖
  • 创建生产者类。在生产者类上添加@EnableBinding()注解,并声明管道
  • 定义处理器,在处理器中调用消息生产者,使其发送消息
  • 在配置文件中注册kafka 集群,并指定管道所绑定的主题及类型

创建消费者步骤

  • 导入spring-cloud-stream-binder-kafka 依赖
  • 创建消费者类。在消费者类上添加@EnableBinding()注解,并声明管道。
  • 在消费者类中定义消费方法,在方法上添加相应的注解。
  • 在配置文件中注册kafka 集群,并指定管道所绑定的主题

消息发送给一个主题的生产者

创建工程08-spring-clourd-stream-kafka

任意复制前面的一个提供者或消费者工程,将其中的除启动类之外的其它代码全部删除。这里复制02-consumer-8080 工程,并命名为08-spring-clourd-stream-kafka。

导入依赖

仅需再添加一个Spring Cloud Stream Kafka 相关的依赖即可。

1
2
3
4
<dependency> 
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>

创建发生产者类

image-20200413221132612

创建处理器

image-20200413221147919

创建配置文件

image-20200413221202257

消息发送给多个主题的生产者

前面工程中通过Source 的MessageChannel 完成了将消息发送给某个指定主题的功能,若要将消息发送给多个主题,则需要自定义Channel。

创建工程08-spring-clourd-stream-kafka2

复制前面的08-spring-clourd-stream-kafka,重命名为08-spring-clourd-stream-kafka2。

定义Source 接口

模拟Source 接口自定义一个Source 接口。

image-20200413221253239

修改发布者类

image-20200413221305909

修改配置文件

在配置文件中添加如下输出目标。

image-20200413221325305

创建消息消费者– @PostConstruct方式

Spring Cloud Stream 提供了三种创建消费者的方式,这三种方式的都是在消费者类的“消费”方法上添加注解。只要有新的消息写入到了管道,该“消费”方法就会执行。只不过三种注解,其底层的实现方式不同。即当新消息到来后,触发“消费”方法去执行的实现方式不同。

  • @PostConstruct:以发布/订阅方式实现
  • @ServiceActivator:以新消息激活服务的方式实现
  • @StreamListener:以监听方式实现

创建消费者类

image-20200413221718732

修改配置文件

image-20200413221743500

创建消息消费者– @ServiceActivator方式

该注解所标注的方法是以服务的形式出现的,只要管道中的数据发生了变化就会激活该服务。

注释掉前面的消费者

将前面的消费者类注释掉,其将不会出现在Spring 容器。

image-20200413221821438

创建消费者类

image-20200413221833521

创建消息消费者– @StreamListener方式

该方式是以监听的方式实现,只要管道中的流数据发生变化,其就会触发该注解所标注的方法的执行。

注释掉前面的消费者

image-20200413221910281

创建消费者类

image-20200413221923452

文章目录
  1. 1. 微服务框架Spring Cloud
    1. 1.1. 第1章Spring Cloud入门
      1. 1.1.1. Spring Cloud 简介
        1. 1.1.1.1. 官网简介
        2. 1.1.1.2. 百度百科
        3. 1.1.1.3. 总结
      2. 1.1.2. Spring Cloud 的国内使用情况
      3. 1.1.3. Spring Cloud 在线资源
        1. 1.1.3.1. Spring Cloud官网
        2. 1.1.3.2. Spring Cloud中文网
        3. 1.1.3.3. Spring Cloud中国社区
      4. 1.1.4. Spring Cloud 版本
        1. 1.1.4.1. 版本号来源
        2. 1.1.4.2. Spring Cloud与Spring Boot版本
      5. 1.1.5. Spring Cloud 与Dubbo 技术选型
      6. 1.1.6. 第一个服务提供者/消费者项目
        1. 1.1.6.1. 创建提供者工程01-provider-8081
        2. 1.1.6.2. 创建消费者工程01-consumer-8080
    2. 1.2. 第2章微服务中心Eureka
      1. 1.2.1. Eureka 概述
        1. 1.2.1.1. CAP定理
          1. 1.2.1.1.1. 概念
          2. 1.2.1.1.2. 定理
        2. 1.2.1.2. Eureka简介
        3. 1.2.1.3. Eureka体系架构
        4. 1.2.1.4. Eureka与Zookeeper对比
        5. 1.2.1.5. Eureka的闭源谣言
      2. 1.2.2. 创建Eureka 服务中心00-eurekaserver-8000
      3. 1.2.3. 总步骤
        1. 1.2.3.1. 创建工程
        2. 1.2.3.2. 导入依赖
        3. 1.2.3.3. 创建并配置yml文件
        4. 1.2.3.4. 定义spring boot启动类
      4. 1.2.4. 创建提供者工程02-provider-8081
        1. 1.2.4.1. 创建工程
        2. 1.2.4.2. 添加依赖管理及依赖
        3. 1.2.4.3. 修改yml文件
          1. 1.2.4.3.1.
        4. 1.2.4.4. 输出结果
        5. 1.2.4.5. actuator 完善微服务info
          1. 1.2.4.5.1. 提供者工程添加依赖
          2. 1.2.4.5.2. 修改配置文件
      5. 1.2.5. 创建消费工程02-consumer-8080
        1. 1.2.5.1. 创建工程
        2. 1.2.5.2. 添加依赖管理及依赖
        3. 1.2.5.3. 修改yml文件
        4. 1.2.5.4. 修改处理器
        5. 1.2.5.5. 修改JavaConfig类
        6. 1.2.5.6. 修改启动类
        7. 1.2.5.7. 服务发现Discovery
      6. 1.2.6. Eureka 的自我保护机制
        1. 1.2.6.1. 自我保护机制
        2. 1.2.6.2. 默认值修改
        3. 1.2.6.3. GUI上的属性值
      7. 1.2.7. 服务离线
        1. 1.2.7.1. 准备工作
        2. 1.2.7.2. 服务下架
          1. 1.2.7.2.1. 修改配置文件
          2. 1.2.7.2.2. 运行测试
        3. 1.2.7.3. 服务平滑上下线
          1. 1.2.7.3.1. 运行测试
      8. 1.2.8. EurekaServer 集群
        1. 1.2.8.1. 设置域名
          1. 1.2.8.1.1. 复制工程
          2. 1.2.8.1.2. 修改pom
          3. 1.2.8.1.3. 修改配置文件
    3. 1.3. 第3章OpenFeign与Ribbon
      1. 1.3.1. 概述
        1. 1.3.1.1. OpenFeign简介
        2. 1.3.1.2. OpenFeign与Feign
        3. 1.3.1.3. Ribbon与OpenFeign
      2. 1.3.2. 声明式Rest客户端OpenFeign
        1. 1.3.2.1. 创建消费者工程03-consumer-feign-8080
        2. 1.3.2.2. 超时设置
          1. 1.3.2.2.1. 修改配置文件
          2. 1.3.2.2.2. 创建提供者工程03-provider-8081
        3. 1.3.2.3. Gzip压缩设置
      3. 1.3.3. Ribbon 负载均衡
        1. 1.3.3.1. 系统结构
        2. 1.3.3.2. 创建提供者03-provider-8082
        3. 1.3.3.3. 创建提供者03-provider-8083
        4. 1.3.3.4. 创建提供者03-provider-8084
      4. 1.3.4. 更换负载均衡策略
        1. 1.3.4.1. 内置负载均衡策略
        2. 1.3.4.2. 更换内置策略
        3. 1.3.4.3. 自定义负载均衡策略
    4. 1.4. 第4章Hystrix服务熔断与服务降级
      1. 1.4.1. 前置概念
        1. 1.4.1.1. 服务熔断
        2. 1.4.1.2. 服务降级
      2. 1.4.2. Hystrix 简介
        1. 1.4.2.1. 官网Wiki
        2. 1.4.2.2. 综合说明
      3. 1.4.3. fallbackMethod 服务降级
        1. 1.4.3.1. 总步骤
        2. 1.4.3.2. 案例一:hystrix 本身与feign 是没有关系的
        3. 1.4.3.3. 案例二:Hystrix 与Feign 结合使用
        4. 1.4.3.4. 在启动类添加注解@SpringCloudApplication
      4. 1.4.4. fallbackFactory 服务降级
        1. 1.4.4.1. 总步骤
        2. 1.4.4.2. 使用fallbackFactory()进行服务降级
        3. 1.4.4.3. 使用fallback()进行服务降级
      5. 1.4.5. Hystrix 高级属性配置
        1. 1.4.5.1. 执行隔离策略
        2. 1.4.5.2. 执行隔离其它属性
        3. 1.4.5.3. 服务降级属性
        4. 1.4.5.4. 服务熔断属性
        5. 1.4.5.5. 线程池相关属性
      6. 1.4.6. Dashboard 监控仪表盘
        1. 1.4.6.1. 总步骤
        2. 1.4.6.2. 仪表盘
      7. 1.4.7. Turbine 聚合监控–监控默认组集群
        1. 1.4.7.1. 整体架构
        2. 1.4.7.2. 总步骤
        3. 1.4.7.3. 实现
      8. 1.4.8. Turbine 聚合监控–监控多个组集群
        1. 1.4.8.1. 整体架构
        2. 1.4.8.2. 分组监控原理
        3. 1.4.8.3. 实现
      9. 1.4.9. 服务降级报警机制
        1. 1.4.9.1. 实现
    5. 1.5. 第5章微服务网关Zuul
      1. 1.5.1. 简介
        1. 1.5.1.1. 网关简介
        2. 1.5.1.2. Zuul官网简介
        3. 1.5.1.3. Zuul综合说明
      2. 1.5.2. 基本环境搭建
      3. 1.5.3. 服务路由00-zuul-9000
        1. 1.5.3.1. 创建zuul网关工程
        2. 1.5.3.2. 路由策略配置
        3. 1.5.3.3. 路由前辍
        4. 1.5.3.4. 服务名屏蔽
        5. 1.5.3.5. 路径屏蔽
        6. 1.5.3.6. 敏感请求头屏蔽
        7. 1.5.3.7. 负载均衡
        8. 1.5.3.8. 服务降级
      4. 1.5.4. 请求过滤
        1. 1.5.4.1. 路由过滤架构
        2. 1.5.4.2. 需求
        3. 1.5.4.3. 实现
      5. 1.5.5. 请求过滤的应用
        1. 1.5.5.1. 应用一:令牌桶限流
        2. 1.5.5.2. 原理
        3. 1.5.5.3. 实现
        4. 1.5.5.4. 应用二:多维请求限流
        5. 1.5.5.5. 原理
        6. 1.5.5.6. 实现
        7. 1.5.5.7. 应用三:灰度发布
        8. 1.5.5.8. 原理
        9. 1.5.5.9. 实现
      6. 1.5.6. Zuul 的高可用
    6. 1.6. 第6章分布式配置管理Spring Cloud Config
      1. 1.6.1. spring cloud config 概述
        1. 1.6.1.1. 官网介绍
        2. 1.6.1.2. 统合说明
        3. 1.6.1.3. 原理
      2. 1.6.2. 实现配置文件工程
        1. 1.6.2.1. 总步骤
        2. 1.6.2.2. 创建工程
        3. 1.6.2.3. 添加依赖
        4. 1.6.2.4. 修改配置文件
        5. 1.6.2.5. 修改启动类
        6. 1.6.2.6. 修改windows的host文件
      3. 1.6.3. 实现Config 版的Eureka 服务器
        1. 1.6.3.1. 总步骤
        2. 1.6.3.2. 创建工程
        3. 1.6.3.3. 添加config客户端依赖
        4. 1.6.3.4. 定义bootstrap.yml
        5. 1.6.3.5. 启动类
      4. 1.6.4. 实现Config 版的提供者
        1. 1.6.4.1. 创建工程
        2. 1.6.4.2. 添加config客户端依赖
        3. 1.6.4.3. 定义bootstrap.yml
      5. 1.6.5. 实现Config 版的消费者
        1. 1.6.5.1. 创建工程
        2. 1.6.5.2. 添加config客户端依赖
        3. 1.6.5.3. 定义bootstrap.yml
      6. 1.6.6. 配置自动更新
        1. 1.6.6.1. Webhooks
        2. 1.6.6.2. Spring Cloud Bus概述
        3. 1.6.6.3. 修改远程库中的配置文件
        4. 1.6.6.4. 实现
    7. 1.7. 第7章调用链跟踪Spring Cloud Sleuth+Zipkin
      1. 1.7.1. Sleuth 简介
      2. 1.7.2. Sleuth 基本理论
        1. 1.7.2.1. Spring Cloud Sleuth文档
        2. 1.7.2.2. 三大概念
        3. 1.7.2.3. Sleuth的日志采样
      3. 1.7.3. “跟踪日志”的生产者Sleuth
        1. 1.7.3.1. 实现
      4. 1.7.4. zipkin 工作过程
        1. 1.7.4.1. zipkin简介
        2. 1.7.4.2. 官网
        3. 1.7.4.3. zipkin系统结构
      5. 1.7.5. zipkin 服务端搭建
        1. 1.7.5.1. 启动zipkin服务器
        2. 1.7.5.2. 访问zipkin服务器
      6. 1.7.6. 实现
        1. 1.7.6.1. 创建zipkin 客户端工程-via
        2. 1.7.6.2. 总步骤
      7. 1.7.7. sleuth + kafka + zipkin
        1. 1.7.7.1. 创建提供者工程07-kafka-sleuth-provider -8081
        2. 1.7.7.2. 创建消费者工程07-kafka-sleuth-consumer-8080
        3. 1.7.7.3. 启动运行
    8. 1.8. 第8章消息系统整合框架Spring Cloud Stream
      1. 1.8.1. 简介
        1. 1.8.1.1. 官网
        2. 1.8.1.2. 综合
          1. 1.8.1.2.1. 程序模型
      2. 1.8.2. stream kafka 微服务
        1. 1.8.2.1. 总步骤
        2. 1.8.2.2. 消息发送给一个主题的生产者
        3. 1.8.2.3. 消息发送给多个主题的生产者
        4. 1.8.2.4. 创建消息消费者– @PostConstruct方式
        5. 1.8.2.5. 创建消息消费者– @ServiceActivator方式
        6. 1.8.2.6. 创建消息消费者– @StreamListener方式
|