# SpringCloudStudy
**Repository Path**: jianghaok/SpringCloudStudy
## Basic Information
- **Project Name**: SpringCloudStudy
- **Description**: 尚硅谷SpringCloud相关内容学习
- **Primary Language**: Java
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2022-03-22
- **Last Updated**: 2024-10-28
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 一、微服务架构理论入门
### 1.1)微服务架构
微服务架构是一种架构模式,提倡将单一应用程序划分为一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个微服务运行在其独立的进程中,服务与服务之间采用轻量级的通信机制互相协作(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境和类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。
### 1.2)SpringCloud介绍
SpringCloud 就是分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶,其架构图如下:

SpringCloud 中包含重点模块如下图:

京东电商网站架构图如下:

### 1.3)SpringCloud技术栈
SpringCloud 核心组件说明:
| 相关服务模块 | 服务组件 |
| -------------- | ------------------- |
| 服务注册与发现 | Eureka |
| 服务负载均衡 | Ribbion |
| 服务REST调用 | Feign |
| 服务熔断降级 | hystrix |
| 服务网关 | Zuul/Gateway |
| 服务分布式配置 | Spring Cloud Config |
| 服务开发 | Spring Boot |
| 服务跟踪 | Sleuth |
### 1.4)版本选择
#### 1.4.1)SpringBoot版本选择 - 2.X版
git源码地址 : https://github.com/spring-projects/spring-boot/releases/
SpringBoot2.0新特性 : https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes [通过上面官网发现,Boot官方强烈建议你升级到2.X以上版本]
官网版本:
#### 1.4.2)SpringCloud版本选择 - H版
git源码地址 : https://github.com/spring-projects/spring-cloud
官网:https://spring.io/projects/spring-cloud
官网版本:
版本命名规则:SpringCloud的版本关系 Spring Cloud 采用了英国伦敦地铁站的名称来命名,并由地铁站名称字母A-Z依次类推的形式来发布迭代版本SpringCloud是一个由许多子项目组成的综合项目,各子项目有不同的发布节奏。为了管理SpringCloud与各子项目的版本依赖关系,发布了一个清单,其中包括了某个SpringCloud版本对应的子项目版本。为了避免SpringCloud版本号与子项目版本号混淆,SpringCloud版本采用了名称而非版本号的命名,这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来对应版本时间顺序。例如Angel是第一个版本, Brixton是第二个版本。当SpringCloud的发布内容积累到临界点或者一个重大BUG被解决后,会发布一个"service releases"版本,简称SRX版本,比如Greenwich.SR2就是SpringCloud发布的Greenwich版本的第2个SRX版本。
#### 1.4.3)SpringCloud和SpringBoot之间的依赖关系如何看
官网说明:https://spring.io/projects/spring-cloud#overview
依赖:Finchley 是基于 Spring Boot 2.0.x 构建的不再 Boot 1.5.xDalston 和 Edgware 是基于 Spring Boot 1.5.x 构建的,不支持 Spring Boot 2.0.xCamden 构建于 Spring Boot 1.4.x,但依然能支持 Spring Boot 1.5.x
更详细的版本对应查看方法:https://start.spring.io/actuator/info
将上述内容中的 json 字符串格式化 ,即可得到版本的详细对应信息,如下图所示:

#### 1.4.4)开发相关所需版本
- SpringCloud : Hoxton.SR1
- SpringBoot : 2.2.2.RELEASE
- SpringCloud alibaba: 2.1.0.RELEASE
- Java : Java8
- Maven:3.5及以上
- Mysql:5.7及以上
### 1.5)关于SpringCloud各种组件的停更/升级/替换
SpringCloud各种组件分析详情如下图:[ × :已停止维护 √ :替换方案]

#### 1.5.1)官网参考资料
SpringCloud :https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/
SpringCloud中文文档:https://www.bookstack.cn/read/spring-cloud-docs/docs-index.md
SpringBoot :https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/htmlsingle/
## 二、微服务架构编码构建
### 2.1)微服务SpringCloud 整体聚合父工程Project
#### 2.1.1)创建过程
【核心原则:约定 > 配置 > 编码 】
1. New Project
2. 聚合总父工程名字 :SpringCloudStudy
3. Maven选版本 :3.8.5
4. 工程名字:SpringCloudStudy
5. 字符编码 :选择 utf-8

6. 注解生效激活

7. Java编译版本选8

8. File Type过滤 : 不展示配置相关的文件

#### 2.1.2)Maven中的DependencyManagement和Dependencies
dependencyManagement Maven 使用dependencyManagement 元素来提供了一种管理依赖版本号的方式。通常会在一个组织或者项目的最顶层的父POM中看到dependencyManagement 元素。 使用pom.xml 中的dependencyManagement 元素能让所有在子项目中引用一个依赖而不用显式的列出版本号。Maven 会沿着父子层次向上走,直到找到一个拥有dependencyManagement 元素的项目,然后它就会使用这个dependencyManagement 元素中指定的版本号。
这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改 ;另外如果某个子项目需要另外的一个版本,只需要声明version就可。
* dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖
* 如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;
* 如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。
### 2.2)微服务SpringCloud模块构建
#### 2.2.1)创建过程
1. 建立Module模块:在上述父工程下新建,依赖于父工程
2. 修改POM文件:修改Module模块下的POM文件,引入自身所需要的包
3. 新建YML文件 :新建 application.yml文件,编辑相关配置信息
4. 新建启动文件:新建主启动类,作为该模块启动的入口
5. 编写业务代码:编写其他业务代码,与模块构建无关
建表SQL:
实体类entities:包含主实体和Json封装体
dao:构建 Dao相关接口(增删改查)以及实体对应的Mapper文件
service:构建服务相关接口及其实现
controller:服务主逻辑
6. 测试
get请求:浏览器和Postman都支持
post请求:浏览器不支持,Postman都支持
#### 2.2.2)热部署Devtools
**流程如下:**
1. 子工程中:Adding devtools to your project
```xml
org.springframework.boot
spring-boot-devtools
runtime
true
```
2. 父工程中:Adding plugin to your pom.xml
```xml
org.springframework.boot
spring-boot-maven-plugin
true
true
```
3. 修改配置:Enabling automatic build

4. Update the value of

5. 重启IDEA
#### 2.2.3)RestTemplate
简介: RestTemplate提供了多种便捷访问远程Http服务的方法, 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集。
官网地址: https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html
使用:使用restTemplate访问restful接口非常的简单粗暴无脑。(url, requestMap, ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
#### 2.2.4)工程结构
支付和消费模块当前工程结构如下图:

新增测试:http://127.0.0.1/consumer/payment/create?serial=100005
输出:

查询测试:http://127.0.0.1/consumer/payment/get/5
输出:

## 三、Eureka服务注册与发现
### 3.1)Eureka基础知识
#### 3.1.1)什么是服务治理
Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理。在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,**管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册**。
#### 3.1.2)什么是服务注册
Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息, 比如 服务地址通讯地址等以别名方式注册到注册中心上;另一方(消费者|服务提供者)以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用。
RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))。
**下左图是Eureka系统架构,右图是Dubbo的架构,请对比**

#### 3.1.3)Eureka两组件
Eureka包含两个组件:Eureka Server和Eureka Client 。
**Eureka Server**:提供服务注册服务各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
**EurekaClient**:通过注册中心进行访问是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒) 。
### 3.2)单机Eureka构建步骤
#### 3.2.1)IDEA生成eurekaServer端服务注册中心
1. 建立Module模块:在工程的父工程下新建,依赖于父工程,新建 cloud-eureka-server7001
2. 修改POM文件:修改Module模块下的POM文件,引入自身所需要的包
```xml
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
```
3. 新建YML文件 :新建 application.yml文件,编辑Eureka相关配置信息
4. 新建启动文件:新建主启动类,作为该模块启动的入口,配置 @SpringBootApplication 注解
5. 测试:浏览器访问: http://127.0.0.1:7001/ ,能够出现Eureka服务页面即通过
#### 3.2.2)注册工程进EurekaServer成为服务提供者provider
1.修改POM文件:修改Module模块下的POM文件,引入Eureka相关包
```xml
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
```
2.修改YML文件 :新建 application.yml文件,编辑Eureka相关配置信息
```yml
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机版
defaultZone: http://localhost:7001/eureka
```
3.修改启动文件:修改主启动类,作为该模块启动的入口,配置 @EnableEurekaClient 注解
4.测试:先启动EurekaServer工程,再启动要注册成为服务提供者的工程,浏览器访问: http://127.0.0.1:7001/ ,即可在页面看到Eureka的注册相关信息,且注册的服务名和配置中的spring工程名相同。

#### 3.2.3)注册工程进EurekaServer成为消费者consumer
1.修改POM文件:修改Module模块下的POM文件,引入Eureka相关包
```xml
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
```
2.修改YML文件 :新建 application.yml文件,编辑Eureka相关配置信息
```yml
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机
defaultZone: http://localhost:7001/eureka
```
3.修改启动文件:修改主启动类,作为该模块启动的入口,配置 @EnableEurekaClient 注解
4.测试:先启动EurekaServer工程,再启动要注册成为服务提供者的工程,浏览器访问: http://127.0.0.1:7001/ ,即可在页面看到Eureka的注册相关信息,且注册的服务名和配置中的spring工程名相同。

结合上述的服务提供者,架构如下图:

### 3.3)集群Eureka构建步骤
#### 3.3.1)Eureka集群原理说明
上述工程在加入Eureka的工作流程如下如:

问题:微服务RPC远程服务调用最核心的是什么?
回答:**高可用,**试想你的注册中心只有一个only one, 它出故障了那就呵呵( ̄▽ ̄)"了,会导致整个为服务环境不可用,所以**解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错**
问题:Eureka集群部署原则?
回答:作为整体对外暴露,对内则是互相注册,相互守望,每一台Eureka都会注册其余Eureka在自己的服务上
#### 3.3.2)EurekaServer集群环境构键步骤
1. 参考之前的eurekaServer工程-cloud-eureka-server7001,新建eurekaServer工程-cloud-eureka-server7002;
2. 修改POM文件:修改Module模块下的POM文件,引入Eureka相关包
3. 修改修改映射配置文件
找到C:\Windows\System32\drivers\etc路径下的hosts文件,如下图:

添加内容:
```
#######SpringCloudStudy#######
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
```
4. 修改YML文件 :修改7002下的 application.yml文件,修改Eureka相关配置信息
```yml
server:
port: 7002
eureka:
instance:
#eureka服务端的实例名称
# 单机
#hostname: localhost
# 集群
hostname: eureka7002.com
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
#单机就是7002自己
#defaultZone: http://eureka7002.com:7002/eureka/
#集群指向其它eureka
defaultZone: http://eureka7001.com:7001/eureka/
```
5. 修改启动文件:新增主启动类,将7001的复制过来即可
6. 测试:先启动EurekaServer工程,再启动要注册成为服务提供者的工程,浏览器访问:http://eureka7001.com:7001/ ,即可在页面看到7002下Eureka的注册相关信息;浏览器访问:http://eureka7002.com:7002,即可在页面看到7001下Eureka的注册相关信息【互相注册,相互守望】;
#### 
#### 3.3.3)注册工程进EurekaServer集群成为服务提供者provider和消费者consumer
1. 修改YML文件:
将之前工作配置文件中 eureka配置:
```yml
service-url:
#单机版
defaultZone: http://localhost:7001/eureka
```
修改为:
```yml
service-url:
# 集群
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
```
2. 测试:先启动EurekaServer工程,再启动要注册成为服务提供者的工程,浏览器访问:http://eureka7001.com:7001/ ,即可在页面看到7002下Eureka的注册相关信息;浏览器访问:http://eureka7002.com:7002,即可在页面看到7001下Eureka的注册相关信息【互相注册,相互守望】;
#### 
#### 3.3.4)支付模块集群配置
1. 参考之前的支付模块工程-cloud-provider-payment8001,新建支付模块工程-cloud-provider-payment8002;
2. 修改POM文件:修改Module模块下的POM文件,引入Eureka相关包;
3. 新增YML文件 :复制8001下的 application.yml文件,修改端口为8002;
4. 新增启动文件:复制8001下的启动文件PaymentMain8001.java,修改为PaymentMain8002.java;
5. 新增业务代码:复制8001下的相关业务代码至cloud-provider-payment8002下,在PaymentController.java中添加下列代码来区分服务
```java
// 区分不同服务(8001和8002)的端口
@Value("${server.port}")
private String serverPort;
...
log.info("*****插入结果:" + result);
if (result > 0) {
return new CommonResult(200, "插入数据库成功,serverPort: " + serverPort, result);
}
```
6. 修改消费者模块中的OrderController.java
```java
// 单机
public static final String PAYMENT_URL = "http://127.0.0.1:8001";
```
修改为:
```java
// 集群
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
```
修改消费者模块中的ApplicationContextConfig.java
添加注解 :**@LoadBalanced** ,使用@LoadBalanced注解赋予RestTemplate负载均衡的能力,解决只知道根据服务名CLOUD-PAYMENT-SERVIC访问,两个同名服务不知道访问哪一个的问题,默认为轮询策略;
7. 测试
访问Eureka的注册相关信息,如下图:

访问:http://127.0.0.1/consumer/payment/get/4 ,根据消费者模块查询,采取轮询策略,交替访问8001和8002服务,如下图:

8. 补充完善
修改在Eureka注册信息中的主机服务名称,并且使其访问信息有IP信息提示,修改8001和8002下application.yml中的Eureka相关信息,添加如下信息:
```yml
instance:
#主机服务名称
instance-id: payment8001
#访问路径可以显示IP地址
prefer-ip-address: true
```
如图:左下角会展示对应的IP地址

### 3.4)服务发现Discovery
#### 3.4.1) 服务发现Discovery功能
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
#### 3.4.2) 代码实现
修改支付模块8001下的PaymentController.java,新增 discovery()方法;
启动类PaymentMain8001.java中添加注解:@EnableDiscoveryClient
#### 3.4.3)测试
浏览器访问:http://127.0.0.1:8001/payment/discovery
浏览器输出:

控制台输出:可以得到服务CLOUD-PAYMENT-SERVICE对应的服务信息

### 3.5)Eureka自我保护
#### 3.5.1)故障现象
概述:保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是**不会注销任何微服务**。一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存;属于CAP里面的AP分支。【Consistency(一致性) / Availability(可用性) / Partition tolerance(分区容忍性)】
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:

#### 3.5.2)导致原因
- **为什么会产生Eureka自我保护机制?**
为了防止EurekaClient可以正常运行,但是与 EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除
- **什么是自我保护模式?**
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。 **在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。**一句话讲解:好死不如赖活着 综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
#### 3.5.3)怎么禁止自我保护
注册中心eureakeServer端7001修改配置文件application.yml,在其中添加下列配置:
```yml
server:
#关闭自我保护机制,(默认是打开的)保证不可用服务被及时踢除
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000
```
支付服务8001中修改配置文件application.yml,在其中添加下列配置:
```yml
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 30
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 90
```
测试:将上述工程修改为单机部署,当超过所设置的时间,服务会被EurekaServer服务剔除

## 四、Zookeeper服务注册与发现
### 4.1)Zookeeper简介
zookeeper是一个分布式协调工具,可以实现注册中心功能
关闭Linux服务器防火墙后启动zookeeper服务器
zookeeper服务器取代Eureka服务器,zk作为服务注册中心
### 4.2)SpringCloud整合Zookeeper代替Eureka
#### 4.2.1)服务提供者
1. 建立Module模块:参照上述支付模块,新建工程cloud-provider-payment8004;
2. 修改POM文件:修改Module模块下的POM文件,引入zookeeper所需要的包;
3. 新建YML文件 :新建 application.yml文件,编辑zookeeper相关配置信息
```yml
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
```
4. 新建启动文件:新建主启动类,作为8004模块启动的入口;
5. 编写业务代码:编写其他业务代码,与模块构建无关
controller:服务主逻辑,判断8004服务是否能够注册进zookeeper服务
6. 测试
说明: 启动本地zookeeper服务,再启动8004服务;
浏览器访问: http://127.0.0.1:8004/payment/zk
浏览器返回:springcloud with zookeeper: 8004 6e13aa76-03ac-429c-937f-2f7ae297f3b2
说明已经注册进zookeeper服务
#### 4.2.2)服务节点是临时节点还是持久节点
临时节点,心跳监听服务不存在会直接将所注册的服务剔除 ,属于CAP里面的CP分支。【Consistency(一致性) / Availability(可用性) / Partition tolerance(分区容忍性)】
#### 4.2.3)服务消费者
1. 建立Module模块:参照上述消费者模块,新建工程cloud-consumerzk-order80;
2. 修改POM文件:修改Module模块下的POM文件,引入zookeeper所需要的包;
3. 新建YML文件 :新建 application.yml文件,编辑zookeeper相关配置信息
4. 新建启动文件:新建主启动类,作为cloud-consumerzk-order80模块启动的入口;
5. 编写业务代码:编写其他业务代码,与模块构建无关
controller:服务主逻辑,判断80服务是否能够注册进zookeeper服务
6. 测试
说明: 启动本地zookeeper服务,再启动8004服务和cloud-consumerzk-order80服务;
浏览器访问: http://127.0.0.1:8004/payment/zk 和 http://127.0.0.1/consumer/payment/zk
浏览器输出:

## 五、Consul服务注册与发现
### 5.1)Consul简介
#### 5.1.1)概述
Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。
它具有很多优点。包括: 基于 raft 协议,比较简洁; 支持健康检查, 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群 提供图形界面 跨平台,支持 Linux、Mac、Windows 。
Consul 官网:https://www.consul.io/intro/index.html
#### 5.1.2)Spring Cloud Consul特性
- 服务发现:提供HTTP和DNS两种发现方式;
- 健康监测:支持多种方式,HTTP、TCP、Docker、Shell脚本定制化监控
- KV存储:Key、Value的存储方式
- 多数据中心:Consul支持多数据中心
- 可视化Web界面
下载地址:https://www.consul.io/downloads.html
使用手册:https://www.springcloud.cc/spring-cloud-consul.html
官网安装说明:https://learn.hashicorp.com/consul/getting-started/install.html
下载完成后只有一个consul.exe文件,硬盘路径下双击运行,查看版本号信息
**使用开发模式启动**
1. consul agent -dev
2. 通过地址可以访问Consul的首页:http://localhost:8500
3. 显示结果页面,如下图

### 5.2)SpringCloud整合Consul代替Eureka
#### 5.2.1)服务提供者
1. 建立Module模块:参照上述支付模块,新建工程cloud-providerconsul-payment8006;
2. 修改POM文件:修改Module模块下的POM文件,引入Consul所需要的包;
```xml
org.springframework.cloud
spring-cloud-starter-consul-discovery
```
3. 新建YML文件 :新建 application.yml文件,编辑consul相关配置信息
```yml
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
```
4. 新建启动文件:新建主启动类,作为8006模块启动的入口;
5. 编写业务代码:编写其他业务代码,与模块构建无关
controller:服务主逻辑,判断8006服务是否能够注册进consul服务
6. 测试
浏览器访问:http://localhost:8500/ui/dc1/services
浏览器返回:

浏览器访问: http://127.0.0.1:8006/payment/consul
浏览器返回:springcloud with consul: 8006 511ef923-5421-411c-91d5-960e198ba1bf
说明已经注册进consul服务
#### 5.2.2)服务消费者
1. 建立Module模块:参照上述消费者模块,新建工程cloud-consumerconsul-order80;
2. 修改POM文件:修改Module模块下的POM文件,引入consul所需要的包;
```xml
org.springframework.cloud
spring-cloud-starter-consul-discovery
```
3. 新建YML文件 :新建 application.yml文件,编辑consul相关配置信息
4. 新建启动文件:新建主启动类,作为cloud-consumerconsul-order80模块启动的入口;
5. 编写业务代码:编写其他业务代码,与模块构建无关
controller:服务主逻辑,判断80服务是否能够注册进consul服务
6. 测试
说明: 启动本地consul服务,再启动8006服务和cloud-consumerconsul-order80服务;
浏览器访问: http://127.0.0.1/consumer/payment/consul 和 http://127.0.0.1:8006/payment/consul
浏览器输出:

### 5.3)三个注册中心异同点
异同点如下图:

CAP理论如下图:

C:Consistency(强一致性)A:Availability(可用性)P:Partition tolerance(分区容错性)
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
- CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大;
- CP - 满足一致性,分区容忍必的系统,通常性能不是特别高;
- AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些;
CAP理论关注粒度是数据,而不是整体系统设计的策略,分布式系统必须满足P(分区容错性),最多只能同时较好的满足两个,故只有AP和CP两种情况:
- **AP(Eureka)**:AP架构当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。结论:违背了一致性C的要求,只满足可用性和分区容错,即AP;
- **CP(Zookeeper/Consul)**:CP架构当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性结论:违背了可用性A的要求,只满足一致性和分区容错,即CP 。
## 六、Ribbon负载均衡服务调用
### 6.1)Ribbon简介
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套**客户端负载均衡**的工具。 简单的说,Ribbon是Netflix发布的开源项目,主要功能是**提供客户端的软件负载均衡算法和服务调用**。
Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
官网资料:https://github.com/Netflix/ribbon/wiki/Getting-Started
#### 6.1.1)Ribbon的用途
**LB(负载均衡)是什么**
LB负载均衡(Load Balance)就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。常见的负载均衡有软件Nginx,LVS,硬件 F5等。 简言之Ribbon就是负载均衡+RestTemplate调用
- **集中式LB:**在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;
- **进程内LB:**LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。 Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
**Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别**
Nginx:服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现的;
Ribbon:本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。
### 6.2)Ribbon负载均衡演示
**架构说明**

说明:Ribbon在工作时分两步
- 先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server;
- 再根据用户指定的策略,在从 server 取到的服务注册列表中选择一个地址,Ribbon提供了多种策略,比如轮询、随机和根据响应时间加权;
总结:Ribbon其实就是一个软负载均衡的客户端组件,可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
补充:在之前的 Eureka 相关模块7001和7002中,并没有引入Ribbon相关组件,但也实现了负载均衡功能
因为在POM文件中引入了 eureka相关的 jar包
```xml
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
```
在eureka相关的 jar包中自带Ribbon相关组件

### 6.3)RestTemplate 的使用
官网:https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html
getForObject 方法和getForEntity 方法:

GET请求方法
```java
T getForObject(String url, Class responseType, Object... uriVariables);
T getForObject(String url, Class responseType, Map uriVariables);
T getForObject(URI url, Class responseType);
ResponseEntity getForEntity(String url, Class responseType, Object... uriVariables);
ResponseEntity getForEntity(String url, Class responseType, Map uriVariables);
ResponseEntity getForEntity(URI var1, Class responseType);
```
POST请求方法
```java
T postForObject(String url, @Nullable Object request, Class responseType, Object... uriVariables);
T postForObject(String url, @Nullable Object request, Class responseType, Map uriVariables);
T postForObject(URI url, @Nullable Object request, Class responseType);
esponseEntity postForEntity(String url, @Nullable Object request, Class responseType, Object... uriVariables);
ResponseEntity postForEntity(String url, @Nullable Object request, Class responseType, Map uriVariables);
ResponseEntity postForEntity(URI url, @Nullable Object request, Class responseType);
```
### 6.4)Ribbon核心组件IRule
#### 6.4.1)IRule简介
IRule:根据特定算法中从服务列表中选取一个要访问的服务,该接口的实现方法如下图:

Ribbon自带的七种策略如下:
1. com.netflix.loadbalancer.RoundRobinRule:轮询策略(默认策略);
2. com.netflix.loadbalancer.RandomRule:随机策略;
3. com.netflix.loadbalancer.RetryRule:重试策略,先按照轮询策略的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务;
4. com.netflix.loadbalancer.WeightedResponseTimeRule:对轮询策略的扩展,响应速度越快的实例选择权重越大,越容易被选择;
5. com.netflix.loadbalancer.BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务;
6. com.netflix.loadbalancer.AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例;
7. com.netflix.loadbalancer.ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器
#### 6.4.2)如何替换Ribbon策略
**配置细节**:官方文档明确给出了警告,这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。 但启动类中所配置的@SpringBootApplication中就包含了@ComponentScan注解,所以需要额外添加一套启动

**替换步骤**:
1. 新建package ,起名myrule;
```java
@Configuration
public class MySelfRule {
@Bean
public IRule myRule() {
//定义为随机策略
return new RandomRule();
}
}
```
2. 在myrule下新建 Ribbion自定义负载均衡策略 类 MySelfRule;
3. 在启动类中新增注解 @RibbonClient
```java
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration= MySelfRule.class)
```
### 6.5)Ribbon负载均衡算法
#### 6.5.1)负载均衡轮询算法原理
负载均衡轮询算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。
List instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
举例:
List [0] instances = 127.0.0.1:8002
List [1] instances = 127.0.0.1:8001
8001+ 8002 组合成为集群,它们共计2台机器,集群总数为2, 按照轮询算法原理:
当总请求数为1时: 1 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
当总请求数位2时: 2 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
当总请求数位3时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
当总请求数位4时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
如此类推......
#### 6.5.2)Ribbon之手写轮询算法
改造:
1. 8001/8002服务提供者微服务改造,Controller中新增方法:
```java
// Ribbon手写轮询算法
@GetMapping(value = "/payment/lb")
public String getPaymentLB() {
return serverPort;
}
```
2. 80消费者订单微服务改造:
ApplicationContextBean去掉注解@LoadBalanced
新增 LoadBalancer接口
```java
public interface LoadBalancer {
// 获取在Eureka中存活的服务
ServiceInstance instances(List serviceInstances);
}
```
新增服务实现类 MyLB.java
```java
@Component
public class MyLB implements LoadBalancer {
// 初始原子类,默认为 0
private AtomicInteger atomicInteger = new AtomicInteger(0);
// rest接口第几次请求数
public final int getAndIncrement() {
int current;
int next;
do {
current = this.atomicInteger.get();
// 2147483647 : Integer().MAX_VALUE 整型的最大值
next = current >= 2147483647 ? 0 : current + 1;
// 自旋锁
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("*****第几次访问,次数next: " + next);
return next;
}
//负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。
@Override
public ServiceInstance instances(List serviceInstances) {
// rest接口第几次请求数 % 服务器集群总数量
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
```
OrderController 中新增方法:
```java
@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLB() {
// 获取当前Eureka中服务名为 CLOUD-PAYMENT-SERVICE 的服务列表
List instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if (instances == null || instances.size() <= 0) {
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri + "/payment/lb", String.class);
}
}
```
3. 测试:浏览器访问 : http://127.0.0.1/consumer/payment/lb
输出:

## 七、OpenFeign服务接口调用
### 7.1)OpenFeig 简介
#### 7.1.1)概述
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单;
它的使用方法是定义一个服务接口然后在上面添加注解即可;
Feign也支持可拔插式的编码器和解码器,Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters;
Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
官网文档地址:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign
源码地址:https://github.com/spring-cloud/spring-cloud-openfeign
#### 7.1.2)OpenFeig的用途
Feign旨在使编写Java Http客户端变得更容易:
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法;
但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由它来帮助我们**定义和实现依赖服务接口的定义**。
在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
**Feign集成了Ribbon**:利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign**只需要定义服务绑定接口且以声明式的方法**,优雅而简单的实现了服务调用。
#### 7.1.3)Feign和OpenFeign两者区别

### 7.2)OpenFeign使用步骤
核心思想:微服务调用接口+@FeignClient
步骤:
1. 新建消费者模块:新建 cloud-consumer-feign-order80 工程 [Feign在消费端使用]
2. 修改POM文件:
```xml
org.springframework.cloud
spring-cloud-starter-openfeign
```
3. 新增 YML配置文件:
```yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
```
4. 新建启动文件:新建主启动类OrderFeignMain80,作为 cloud-consumer-feign-order80模块启动的入口,添加@EnableFeignClients;
5. 编写业务代码:编写其他业务代码,与模块构建无关【业务逻辑接口+@FeignClient配置调用provider服务】
新建接口对应的服务实现:新建 PaymentFeignService ,其中使用 @FeignClient(value = "CLOUD-PAYMENT-SERVICE")注解,根据服务名 CLOUD-PAYMENT-SERVI 去查找对应接口的服务,方法实现@GetMapping(value = "/payment/get/{id}")表示调用 CLOUD-PAYMENT-SERVI服务 8001和8002下的 /payment/get/{id} 接口对应的服务;
新建接口:服务接口,对外暴露可访问的路径,如下图:

6. 测试:浏览器访问 :http://127.0.0.1/consumer/payment/get/4
结果如下:

### 7.3)OpenFeign超时控制
默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。yml文件中开启配置 :
```yml
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
```
设置超时时间为5秒,这样只有服务端相应超出5秒了,才会直接报错
测试:浏览器访问: http://127.0.0.1/consumer/payment/feign/timeout
返回说明:服务端耗时3秒,如果不进行超时控制,默认为1秒,访问会直接报错;添加上述设置后,超时控制为5秒,再次访问则会返回当前访问的服务端接口,如 8001。
### 7.4)OpenFeign日志打印功能
OpenFeign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。说白了就是对Feign接口的调用情况进行监控和输出
#### 7.4.1)日志级别
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
- FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
#### 7.4.2)开启步骤
配置日志bean:新建 FeignConfig.java,设置日志级别,如下:
```java
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
// 设置日志级别为 FULL
return Logger.Level.FULL;
}
}
```
YML文件中开启日志的Feign客户端,设置监控级别,如下:
```yml
logging:
level:
# feign日志以什么级别监控哪个接口
com.study.springcloud.service.PaymentFeignService: debug
```
测试:浏览器访问 -- http://127.0.0.1/consumer/payment/get/4
后台日志查看,如下图:

## 八、Hystrix断路器
### 8.1)Hystrix简介
#### 8.1.1)分布式系统面临的问题
分布式系统面临的问题复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

服务雪崩:多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。
这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
#### 8.1.2)Hystrix概述
Hystrix是一个用于处理分布式系统的**延迟**和**容错**的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,**不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性**。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),**向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常**,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
#### 8.1.3)Hystrix用途
1. 服务降级
2. 服务熔断
3. 接近实时的监控
官网手册地址:https://github.com/Netflix/Hystrix/wiki/How-To-Use
Hystrix官宣,停更进维:https://github.com/Netflix/Hystrix
#### 8.1.4)Hystrix中的重要概念
##### 8.1.4.1)服务降级(fallback)
概述:服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示【就算不可用也要有最终的兜底方案,不能让报错】
哪些情况会出发降级:
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
##### 8.1.4.2)服务熔断(break)
概述:类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示;就像保险丝:服务的降级->进而熔断->恢复调用链路
##### 8.1.4.3)服务限流(flowlimit)
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
### 8.2)hystrix案例
#### 8.2.1)hystrix 服务提供者工程构建
1. 建立Module模块:参照上述支付模块,新建工程cloud-provider-hystrix-payment8001;
2. 修改POM文件:修改Module模块下的POM文件,引入hystrix所需要的包;
```xml
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
```
3. 新建YML文件 :新建 application.yml文件,编辑eureka相关配置信息;
4. 新建启动文件:新建主启动类,作为8001模块启动的入口;
5. 编写业务代码:编写其他业务代码,与模块构建无关
controller:服务主逻辑,构建正常和超时两个接口;
service:接口所调用的方法的具体实现,正常方法直接返回;超时接口线程沉睡3秒后再返回;
6. 测试
浏览器访问:http://127.0.0.1:8001/payment/hystrix/ok/4
返回:直接返回端口信息
浏览器访问:http://127.0.0.1:8001/payment/hystrix/timeout/4
返回:3秒后再返回端口信息
如下图:

#### 8.2.2)高并发测试
上述在非高并发情形下,还能勉强满足 ,进行Jmeter压测测试:
开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务,再来一个访问:http://127.0.0.1:8001/payment/hystrix/ok/4
看演示结果:两个都在自己转圈圈

为什么会被卡死: SpringBoot中默认集成tomcat,tomcat默认线程池的工作线程数被打满 了,没有多余的线程来分解压力和处理;
Jmeter压测结论:上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死
#### 8.2.3)hystrix 消费者工程构建
1. 建立Module模块:参照上述消费者模块,新建工程 cloud-consumer-feign-hystrix-order80;
2. 修改POM文件:修改Module模块下的POM文件,引入openfeign和hystrix所需要的包;
```xml
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
```
3. 新建YML文件 :新建 application.yml文件,编辑eureka相关配置信息;
4. 新建启动文件:新建主启动类,作为80模块启动的入口,添加@EnableFeignClients注解
5. 编写业务代码:编写其他业务代码,与模块构建无关
controller:服务主逻辑,构建正常和超时两个接口;
service:接口所调用的方法的具体实现,正常方法直接返回;超时接口线程沉睡3秒后再返回;添加@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" )
6. 测试
浏览器访问:http://127.0.0.1/consumer/payment/hystrix/ok/4
返回:直接返回端口信息 线程池: http-nio-8001-exec-4 paymentInfo_OK,id: 4 O(∩_∩)O哈哈~
7. 高并发测试
2W个线程压8001,开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务;
消费端80微服务再去访问正常的Ok微服务8001地址,浏览器访问:http://127.0.0.1/consumer/payment/hystrix/ok/4
消费者80:
- 要么转圈圈等待
- 要么消费端报超时错误
#### 8.2.4)故障原因和解决方案
8001同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕,80此时调用8001,客户端访问响应缓慢,转圈圈;正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生;**解决的要求:**
- 超时导致服务器变慢(转圈):超时不再等待
- 出错(宕机或程序运行出错):出错要有兜底
**解决方案:**
- 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
- 对方服务(8001)宕机了,调用者(80)不能一直卡死等待,必须有服务降级
- 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级
#### 8.2.5)服务降级
##### 8.2.5.1)降级配置
@HystrixCommand
##### 8.2.5.2)服务提供者作服务降级
设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback
1. 在service中设置兜底方法:
```java
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
// 当相应时间 < 3秒,则执行正常方法,若超过则执行兜底方法
public String paymentInfo_TimeOut(Integer id) {
int timeNumber = 5000;
try {
TimeUnit.MILLISECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: " + Thread.currentThread().getName() + " id: " + id + "\t" + "O(∩_∩)O哈哈~" + " 耗时(秒): " + timeNumber;
}
// 兜底所调用的方法
public String paymentInfo_TimeOutHandler(Integer id) {
// 返回提示信息
return "线程池: " + Thread.currentThread().getName() + " 8001系统繁忙或者运行报错,请稍后再试,id: " + id + "\t" + "o(╥﹏╥)o";
}
```
2. 在主启动类中添加新注解@EnableCircuitBreaker,激活 Hystrix 相关配置
3. 测试:浏览器访问 http://127.0.0.1:8001/payment/hystrix/timeout/4
测试输出:线程池: HystrixTimer-1 8001系统繁忙或者运行报错,请稍后再试,id: 4 o(╥﹏╥)o
##### 8.2.5.3)消费者(客户端)作服务降级
1. 修改YML配置文件:
```yml
feign:
hystrix:
enabled: true
```
2. 修改主启动类:
添加注解 @EnableHystrix
在controller中设置兜底方法
```java
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
// 故意制造报错,服务降级也会执行兜底方法
// int age = 10 / 0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
// 服务降级中的兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
```
3. 测试:浏览器访问 http://127.0.0.1/consumer/payment/hystrix/timeout/4
测试输出:我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o
##### 8.2.5.4)全局服务降级
上述方法中每个业务方法对应一个兜底的方法,会造成代码膨胀,且大部分情况下服务降级应当走设置全局服务降级,只有个别方法需要单独设置服务降级的方法,在设置全局服务降级时要将统一和自定义的分开。
通过@DefaultProperties(defaultFallback = "")来设置全局服务降级,没有特别指明的都走该降级方法,设置了@HystrixCommand(fallbackMethod = "...")的走其自定义的服务降级方法,如下图:

测试:http://127.0.0.1/consumer/payment/hystrix/timeout/4
测试输出:Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~
##### 8.2.5.5)服务降级解耦处理
问题:1)和业务逻辑混一起,十分混乱,耦合度太高;
2)服务降级,客户端去调用服务端,碰上服务端宕机或关闭;
常见异常类型:1)运行 2)超时 3)宕机
方案:只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦
根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,
重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理
```java
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
}
}
```
service中修改注解:
```java
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,fallback = PaymentFallbackService.class)
public interface PaymentHystrixService{
...
}
```
测试:暂停 8001 服务,浏览器访问: http://127.0.0.1/consumer/payment/hystrix/ok/4
测试输出:-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o
浏览器访问 :http://127.0.0.1/consumer/payment/hystrix/ok/4
测试输出:-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o
#### 8.2.6)服务熔断
##### 8.2.6.1)熔断机制概述
断路器:一句话就是家里的保险丝;
熔断机制概述:熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息;
当检测到该节点微服务调用响应正常后,**恢复调用链路**;
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。
论文:https://martinfowler.com/bliki/CircuitBreaker.html
##### 8.2.6.2)hystrix 服务提供者熔断机制改造
修改服务提供者工程:cloud-provider-hystrix-payment8001
PaymentService.java
```java
//====================服务熔断===================================
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),// 失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
if (id < 0) {
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " + id;
}
```
PaymentController.java
```java
//====================服务熔断===================================
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result: "+result);
return result;
}
```
测试:正确测试——浏览器访问:http://127.0.0.1:8001/payment/circuit/4
测试输出:hystrix-PaymentService-1 调用成功,流水号: 75b824aae35440ecaa1bc7a2c47f49f6
错误测试——浏览器访问:http://127.0.0.1:8001/payment/circuit/-4
测试输出:id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: -4
重点测试:
多次进行错误测试,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行
【正确->错误->降级熔断->恢复】
##### 8.2.6.3)hystrix 服务熔断总结
论文结论:对于软件断路器,可以让断路器本身检测底层调用是否再次工作;可以通过在适当的时间间隔后再次尝试受保护的调用来实现这种自重置行为,并在成功时重置断路器;

###### 8.2.6.3.1)熔断类型
- 熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态;
- 熔断关闭:熔断关闭不会对服务进行熔断;
- 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
###### 8.2.6.3.2)官网断路器流程图

###### 8.2.6.3.3)官网步骤

###### 8.2.6.3.4)断路器在什么情况下开始起作用
涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。
1:快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒;
```java
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000")
```
2:请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开;
```java
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10")
```
3:错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
```java
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")
```
##### 8.2.6.3.5)断路器开启或者关闭的条件
- 当满足一定的阀值的时候(默认10秒内超过20个请求次数);
- 当失败率达到一定的时候(默认10秒内超过50%的请求失败);
- 到达以上阀值,断路器将会开启;
- 当开启的时候,所有请求都不会进行转发;
- 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。
如果成功,断路器会关闭,若失败,继续开启。重复4和5
###### 8.2.6.3.6)断路器打开之后
1:再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
2:原来的主逻辑要如何恢复呢?
对于这一问题,hystrix也为我们实现了自动恢复功能。当断路器打开,**对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时**。
###### 8.2.6.3.7)全局参数配置
```java
@HystrixCommand(fallbackMethod = "str_fallbackMethod", groupKey = "strGroupCommand", commandKey = "strCommand", threadPoolKey = "strThreadPool", commandProperties = {
// 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 配置命令执行的超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
// 是否启用超时时间
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
// 执行超时的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 执行被取消的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
// 允许回调方法执行的最大并发数
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 服务降级是否启用,是否执行回调函数
@HystrixProperty(name = "fallback.enabled", value = "true"),
// 是否启用断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
// 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50, 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,如果成功就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
// 断路器强制打开
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
// 断路器强制关闭
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
// 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
// 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据 ,设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
// 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
// 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
// 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
// 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
// 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
// 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
// 是否开启请求缓存
@HystrixProperty(name = "requestCache.enabled", value = "true"),
// HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled", value = "true"),
},
threadPoolProperties = {
// 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
@HystrixProperty(name = "coreSize", value = "10"),
// 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,
// 否则将使用 LinkedBlockingQueue 实现的队列。
@HystrixProperty(name = "maxQueueSize", value = "-1"),
// 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
// 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue
// 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),})
public String strConsumer() {
return "hello 2020";
}
public String str_fallbackMethod() {
return "*****fall back str_fallbackMethod";
}
```
#### 8.2.7)hystrix工作流程
官网:https://github.com/Netflix/Hystrix/wiki/How-it-Works
官网步骤:

步骤说明:
1. 创建 HystrixCommand(用在依赖的服务返回单个操作结果的时候) 或 HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候) 对象;
2. 命令执行:其中 HystrixComand 实现了下面前两种执行方式;而 HystrixObservableCommand 实现了后两种执行方式:execute():同步执行,从依赖的服务返回一个单一的结果对象, 或是在发生错误的时候抛出异常。queue():异步执行, 直接返回 一个Future对象, 其中包含了服务执行结束时要返回的单一结果对象。observe():返回 Observable 对象,它代表了操作的多个结果,它是一个 Hot Obserable(不论 "事件源" 是否有 "订阅者",都会在创建后对事件进行发布,所以对于 Hot Observable 的每一个 "订阅者" 都有可能是从 "事件源" 的中途开始的,并可能只是看到了整个操作的局部过程)。toObservable(): 同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有 "订阅者" 的时候并不会发布事件,而是进行等待,直到有 "订阅者" 之后才发布事件,所以对于 Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程);
3. 若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存的结果会立即以 Observable 对象的形式 返回;
4. 检查断路器是否为打开状态:如果断路器是打开的,那么Hystrix不会执行命令,而是转接到 fallback 处理逻辑(第 8 步);如果断路器是关闭的,检查是否有可用资源来执行命令(第 5 步);
5. 线程池/请求队列/信号量是否占满。如果命令依赖服务的专有线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满, 那么 Hystrix 也不会执行命令, 而是转接到 fallback 处理逻辑(第8步);
6. Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。HystrixCommand.run() :返回一个单一的结果,或者抛出异常。HystrixObservableCommand.construct(): 返回一个Observable 对象来发射多个结果,或通过 onError 发送错误通知;
7. Hystrix会将 "成功"、"失败"、"拒绝"、"超时" 等信息报告给断路器, 而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行 "熔断/短路";
8. 当命令执行失败的时候, Hystrix 会进入 fallback 尝试回退处理, 我们通常也称该操作为 "服务降级"。而能够引起服务降级处理的情况有下面几种:第4步: 当前命令处于"熔断/短路"状态,断路器是打开的时候。第5步: 当前命令的线程池、 请求队列或 者信号量被占满的时候。第6步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候;
9. 当Hystrix命令执行成功之后, 它会将处理结果直接返回或是以Observable 的形式返回。tips:如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常, Hystrix 依然会返回一个 Observable 对象, 但是它不会发射任何结果数据, 而是通过 onError 方法通知命令立即中断请求,并通过onError()方法将引起命令失败的异常发送给调用者。
#### 8.2.8)服务监控hystrixDashboard
##### 8.2.8.1)概述
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
##### 8.2.8.2)仪表盘9001工程构建
1. 建立Module模块:参照上述消费者模块,新建工程 cloud-consumer-hystrix-dashboard9001;
2. 修改POM文件:修改Module模块下的POM文件,引入hystrix所需要的包;
```xml
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
org.springframework.boot
spring-boot-starter-actuator
```
3. 新建YML文件 :新建 application.yml文件,配置端口信息;
4. 新建启动文件:新建主启动类,作为9001模块启动的入口,添加注解 @EnableHystrixDashboard;
5. 测试
浏览器访问:http://127.0.0.1:9001/hystrix
浏览器返回:出现服务监控hystrix仪表盘页面
启动服务提供者工程:cloud-provider-hystrix-payment8001
然后不断刷新错误请求:http://127.0.0.1:8001/payment/circuit/-4,再次观察仪表盘:【服务熔断】

然后不断刷新正确请求:http://127.0.0.1:8001/payment/circuit/4,再次观察仪表盘:【服务恢复正常】

## 九、Gateway新一代网关
### 9.1)Gateway概述简介
官网:上一代zuul 1.X —— https://github.com/Netflix/zuul/wiki
当前gateway —— https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
网关:Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是SpringCloud Gateway一句话:gateway是原zuul1.x版的替代 。
Gateway:Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot2 和 Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等;
SpringCloud Gateway 是 Spring Cloud 的一个全新项目,基于 Spring 5.0+Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
一句话概括:SpringCloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。
#### 9.1.1)Gateway用途
- 反向代理
- 服务鉴权
- 流量控制
- 服务熔断
- 日志监控
微服务架构中网关如下图所示:

#### 9.1.2)Gateway特性
**Spring Cloud Gateway 具有如下特性:**
- 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
- 动态路由:能够匹配任何请求属性;
- 可以对路由指定 Predicate(断言)和 Filter(过滤器);
- 集成Hystrix的断路器功能;
- 集成 Spring Cloud 服务发现功能;
- 易于编写的 Predicate(断言)和 Filter(过滤器);
- 请求限流功能;支持路径重写。
**SpringCloud Gateway 与 Zuul的区别:**
在SpringCloud Finchley 正式版之前,Spring Cloud 推荐的网关是 Netflix 提供的Zuul:
1、Zuul 1.x,是一个基于阻塞 I/ O 的 API Gateway;
2、Zuul 1.x **基于Servlet 2. 5使用阻塞架构**【目前多使用响应式异步非阻塞框架】它不支持任何长连接(如 WebSocket) Zuul 的设计模式和Nginx较像,每次 I/ O 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx 用C++ 实现,Zuul 用 Java 实现,而 JVM 本身会有第一次加载较慢的情况,使得Zuul 的性能相对较差;
3、Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。 Zuul 2.x的性能较 Zuul 1.x 有较大提升。在性能方面,根据官方提供的基准测试, Spring Cloud Gateway 的 RPS(每秒请求数)是Zuul 的 1. 6 倍;
4、Spring Cloud Gateway 建立 在 Spring Framework 5、 Project Reactor 和 Spring Boot 2 之上, 使用非阻塞 API;
5、Spring Cloud Gateway 还 支持 WebSocket, 并且与Spring紧密集成拥有更好的开发体验
**Zuul1.x模型:**

Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型。
Servlet的生命周期:servlet由servlet container进行生命周期管理。container启动时构造servlet对象并调用servlet init()进行初始化;container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service()。container关闭时调用servlet destory()销毁servlet;
上述模式的缺点:servlet是一个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发(比如抽风用jemeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet(DispatcherServlet)并由该servlet阻塞式处理处理。所以Springcloud Zuul无法摆脱servlet模型的弊端。
**GateWay模型:**
传统的Web框架,比如说:struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的。但是在Servlet3.1之后有了异步非阻塞的支持。
而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8) Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。
WebFlux官网 :https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-new-framework
#### 9.1.3)Gateway三大核心概念
Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由;
Predicate(断言):参考的是Java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由;
Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
#### 9.1.4)Gateway工作流程
工作流程图如下:

客户端向 Spring Cloud Gateway 发出请求;然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler; Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。 Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
**核心逻辑:路由转发+执行过滤器链**
9.1)Gateway概述简介
### 9.2)Gateway工程构建
1. 建立Module模块:参照上述支付模块,新建工程 cloud-gateway-gateway9527;
2. 修改POM文件:修改Module模块下的POM文件,引入gateway所需要的包;
```xml
org.springframework.cloud
spring-cloud-starter-gateway
```
3. 新建YML文件 :新建 application.yml文件,编辑eureka和Gateway相关配置信息;
```yml
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
```
4. 新建启动文件:新建主启动类,作为9527模块启动的入口;
5. 测试
添加网关前,浏览器访问:http://127.0.0.1:8001/payment/get/4
返回:{"code":200,"message":"查询成功,serverPort: 8001","data":{"id":4,"serial":"100003"}}
添加网关后,浏览器访问:http://127.0.0.1:9527/payment/get/4
返回:{"code":200,"message":"查询成功,serverPort: 8001","data":{"id":4,"serial":"100003"}}
说明:对外隐藏服务的真实IP和端口。通过访问网关的地址也可以正常访问服务
访问说明:

#### 9.2.1)Gateway网关路由硬编码配置
添加配置Bean,如下:实现路由转发
```java
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
// 配置了一个id为 route-name 的路由规则,当访问地址 http://127.0.0.1:9527/guonei 时
// 会自动转发到地址 http://news.baidu.com/guonei
routes.route("path_route_study",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
```
测试:浏览器访问——http://127.0.0.1:9527/guonei
返回:页面显示 http://news.baidu.com/guonei 的内容
#### 9.2.2)GateWay配置动态路由
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
网关工程9527中配置eureka:
```xml
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
```
修改配置文件application.yml:
【需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能,lb://serviceName是spring cloud gateway在微服务中自动创建的负载均衡uri】
```yml
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
```
启动工程:eureka7001/7002 + 两个服务提供者8001/8002
测试:浏览器访问 :http://localhost:9527/payment/lb
测试结果:8001/8002两个端口切换
#### 9.2.3)GateWay中Predicated断言的使用
启动网关工程9527的过程中,可以看到启动日志如下:

##### 9.2.3.1)Route Predicate Factories 概述
Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合。
Spring Cloud Gateway 创建 Route 对象时, 使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。 Spring Cloud Gateway 包含许多内置的Route Predicate Factories。
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。
##### 9.2.3.2)常用的Route Predicate
- After Route Predicate
配置 After 断言,只有时间在所配置时间之后的请求才允许访问网关
```yml
predicates:
# 添加该断言,只有时间在所配置之后的请求才允许访问网关
- After=2022-04-09T11:13:57.476+08:00[Asia/Shanghai]
```
- Before Route Predicate
配置 Before 断言,只有时间在所配置时间之前的请求才允许访问网关
```yml
predicates:
# 添加该断言,只有时间在所配置之前的请求才允许访问网关
- Before=2022-04-09T11:13:57.476+08:00[Asia/Shanghai]
```
- Between Route Predicate
配置 Between 断言,只有时间在所配置时间之间的请求才允许访问网关
```yml
predicates:
# 添加该断言,只有时间在所配置之间的请求才允许访问网关
- Between=2022-04-09T11:13:57.476+08:00[Asia/Shanghai], 2023-04-09T11:13:57.476+08:00[Asia/Shanghai]
```
- Cookie Route Predicate
Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行 。
配置 Cookie 断言,只有请求中所带的Cookie 信息与配置的Cookie 键值对配置时请求才允许访问网关
```yml
predicates:
# 添加该断言,只有请求中所带的Cookie信息与配置的Cookie 键值对配置时请求才允许访问网关
- Cookie=username,jianghaok
```
- Header Route Predicate
```yml
predicates:
# 添加该断言,请求头要有X-Request-Id属性并且值为整数的正则表达式
- Header=X-Request-Id, \d+
```
- Host Route Predicate
```yml
predicates:
# 添加该断言,匹配域名列表为 .study.com 结尾的才允许进行路由
- Host=**.study.com
```
- Method Route Predicate
```yml
predicates:
# 添加该断言,请求方法为get才允许进行路由
- Method=GET
```
- Path Route Predicate
```yml
predicates:
# 断言,路径相匹配的进行路由
- Path=/payment/lb/**
```
- Query Route Predicate
```yml
predicates:
# 配置该断言,要有参数名username并且值还要是整数的才能路由
- Query =username, \d+
```
总结:说白了,Predicate【断言】就是为了实现一组匹配规则,让请求过来找到对应的Route【路由】进行处理。
#### 9.2.4)GateWay中Filter过滤器的使用
##### 9.2.4.1) 路由过滤器Filter概述
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用;
Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生 。
##### 9.2.4.2) Spring Cloud Gateway的Filter
生命周期 :1)pre 2)post
种类:
1)GatewayFilter : https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/ #the-addrequestparameter-gatewayfilter-factory31种之多。。。。。
2)GlobalFilter 种类如下图:

过滤器的使用
修改配置文件application.yml,添加 filters 的相关使用
```yml
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
filters:
#过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024
- AddRequestParameter=X-Request-Id,1024
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
```
##### 9.2.4.3)自定义网关过滤器
两个主要接口介绍:implements GlobalFilter, Ordered
编写自定义网关过滤器 MyLogGateWayFilter.java
```java
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("***********come in MyLogGateWayFilter: " + new Date());
// 获取请求参数中的 uname
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null) {
log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
// 设置Http状态码
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
// 去下一个过滤链进行验证
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 定义加载过滤器的顺序,数字越小代表优先级越高
return 0;
}
}
```
测试:
浏览器访问:http://127.0.0.1:9527/payment/lb?uname=test
浏览器输出:8001/8002两个端口切换
浏览器访问:http://127.0.0.1:9527/payment/lb 或 http://127.0.0.1:9527/payment/lb?uname1=test
浏览器输出:如下图:

日志输出:

## 十、SpringCloud Config 分布式配置中心
### 10.1)概述
#### 10.1.1)分布式系统面临的配置问题
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。
SpringCloud提供了ConfigServer来解决这个问题,每一个微服务自己带着一个application.yml,上百个配置文件的管理非常麻烦,需要一次修改、处处生效。
#### 10.1.2)SpringCloud Config 分布式配置中心简介
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RELEASE/reference/html/
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。

SpringCloud Config分为服务端和客户端两部分;
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口;
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
与GitHub整合配置:由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式
#### 10.1.3)SpringCloud Config 分布式配置中心用途
- 集中管理配置文件;
- 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release;
- 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息;
- 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置;
- 将配置信息以REST接口的形式暴露【post、curl访问刷新均可】
### 10.2)Config服务端配置与测试
#### 10.2.1)Config服务端配置
用自己的账号在Git上新建一个名为 SpringCloudStudy-Config的新Repository
由上一步可获得仓库地址:https://gitee.com/jianghaok/SpringCloudStudy-Config.git
由上述地址,将该仓库克隆到本地
在本地工程中新建表示多个环境的相关配置文件并推送到 Gitee上
#### 10.2.2)Config工程构建
1. 建立Module模块:新建工程cloud-config-center-3344,即为Cloud的配置中心模块cloudConfig Center;
2. 修改POM文件:修改Module模块下的POM文件,引入SpringCloud Config所需要的包;
```xml
org.springframework.cloud
spring-cloud-config-server
```
3. 新建YML文件 :新建 application.yml文件,编辑相关配置信息;
```yml
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
#GitHub上面的git仓库名字
uri: git@gitee.com:jianghaok/SpringCloudStudy-Config.git
####搜索目录
search-paths:
- SpringCloudStudy-Config
####读取分支
label: master
```
4. 新建启动文件:新建主启动类,作为3344模块启动的入口,添加 @EnableConfigServer;
5. 修改修改映射配置文件
找到C:\Windows\System32\drivers\etc路径下的hosts文件,增加映射内容:
```
127.0.0.1 config-3344.com
```
6. 测试通过Config微服务是否可以从Gitee上获取配置内容
启动微服务3344
浏览器访问:http://config-3344.com:3344/master/config-dev.yml
返回:配置文件信息【成功实现了用SpringCloud Config通过Gitee获取配置信息】
```
config:
info: master branch,SpringCloudStudy-Config/config-dev.yml version=1
```
### 10.3)Config客户端配置与测试
#### 10.3.1)Config客户端工程构建
1. 建立Module模块:新建工程cloud-config-client-3355,即为Cloud的配置客户端模块cloudConfig Client;
2. 修改POM文件:修改Module模块下的POM文件,引入SpringCloud Config所需要的包;
```xml
org.springframework.cloud
spring-cloud-starter-config
```
3. 新建YML文件 :新建 bootstrap.yml文件,编辑相关配置信息;
```yml
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址
```
bootstrap配置文件说明:
applicaiton.yml是用户级的资源配置项,bootstrap.yml是系统级的,优先级更加高;
Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的`Application Context`的父上下文。初始化的时候,`Bootstrap Context`负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的`Environment`。
`Bootstrap`属性有高优先级,默认情况下,它们不会被本地配置覆盖。 `Bootstrap context`和`Application Context`有着不同的约定,所以新增了一个`bootstrap.yml`文件,保证`Bootstrap Context`和`Application Context`配置的分离。
要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,因为bootstrap.yml是比application.yml先加载的,**bootstrap.yml优先级高于application.yml**。
4. 新建启动文件:新建主启动类,作为3355模块启动的入口;
5. 编写业务代码:编写其他业务代码,与模块构建无关
controller:服务主逻辑,获取配置中心的配置信息;
测试:
启动Config配置中心3344微服务并自测
浏览器访问:http://config-3344.com:3344/master/config-dev.yml
浏览器输出:
```
config:
info: master branch,SpringCloudStudy-Config/config-dev.yml version=1
```
启动3355作为Client准备访问:http://localhost:3355/configInfo
浏览器输出:【成功实现了客户端3355访问SpringCloud Config3344通过Gitee获取配置信息】
```
master branch,SpringCloudStudy-Config/config-dev.yml version=1
```
#### 10.3.2)分布式配置的动态刷新问题
修改Gitee上的配置文件,对内容做调整,修改 verison 为 2;
刷新3344,发现ConfigServer配置中心立刻响应,输出如下:
```
config:
info: master branch,SpringCloudStudy-Config/config-dev.yml version=2
```
刷新3355,发现ConfigClient客户端没有任何响应,输出如下:
```
master branch,SpringCloudStudy-Config/config-dev.yml version=1
```
3355没有变化除非自己重启或者重新加载,输出如下:
```
master branch,SpringCloudStudy-Config/config-dev.yml version=2
```
每次运维修改配置文件,客户端都需要重启,该问题需要解决
### 10.4)Config客户端之动态刷新
为避免每次更新配置都要重启客户端微服务3355,需要实现动态刷新
#### 10.4.1)动态刷新实现步骤
1. POM文件中引入actuator监控;
```xml
org.springframework.boot
spring-boot-starter-actuator
```
2. 修改YML文件 :修改 bootstrap.yml文件,暴露监控端口;
```yml
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
```
3.修改业务类Controller:业务类Controller中添加 @RefreshScope;
测试:启动3344和3355工程,修改Gitee上的配置文件,将verison修改为3
浏览器访问:http://config-3344.com:3344/master/config-dev.yml
浏览器输出:
```
config:
info: master branch,SpringCloudStudy-Config/config-dev.yml version=3
```
浏览器访问:http://localhost:3355/configInfo
浏览器输出:
```
master branch,SpringCloudStudy-Config/config-dev.yml version=2
```
发现上面所加的相关动态刷新并没有生效,在控制端发送POST请求
```
curl -X POST "http://localhost:3355/actuator/refresh"
```
如下图:

再次浏览器访问:http://localhost:3355/configInfo 【不要重启3355工程】
浏览器输出:【动态刷新此时生效,所以每一次修改配置文件后都要发送上述POST请求,但无需重启工程】
```
master branch,SpringCloudStudy-Config/config-dev.yml version=3
```
成功实现了客户端3355刷新到最新配置内容,避免了服务重启,但仍然存在问题:
假如有多个微服务客户端3355/3366/3377...每个微服务都要执行一次post请求,手动刷新?
思考:可否广播,一次通知,处处生效,实现大范围的自动刷新 --》引入消息总线
## 十一、SpringCloudBus消息总线
### 11.1)SpringCloud Bus概述
Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新,从而实现分布式自动刷新配置功能。
Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。Spring Clud Bus目前支持RabbitMQ和Kafka。
处理流程图如下:

#### 11.1.1)SpringCloud Bus 用途
Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个**分布式执行器**,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。

#### 11.1.2)为何被称为总线

### 11.2)RabbitMQ环境配置
安装Erlang,下载地址:http://erlang.org/download/otp_win64_21.3.exe
安装RabbitMQ,下载地址:https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.7.14
进入RabbitMQ安装目录下的sbin目录,输入以下命令启动管理功能:
```
rabbitmq-plugins enable rabbitmq_management
```
如下图所示:

访问地址查看是否安装成功:http://localhost:15672/ 输入默认账号/密码测试(guest/guest),如下图所示:说明 RabbitMQ 安装成功

### 11.3)SpringCloud Bus动态刷新全局广播
演示广播效果,增加复杂度,再以3355为模板再制作一个3366
1. 建立Module模块:新建工程cloud-config-client-3366,即为Cloud的配置客户端模块cloudConfig Client;
2. 修改POM文件:修改Module模块下的POM文件,引入SpringCloud Config所需要的包;
3. 新建YML文件 :新建 bootstrap.yml文件,编辑相关配置信息;
4. 新建启动文件:新建主启动类,作为3366模块启动的入口;
5. 编写业务代码:编写其他业务代码,与模块构建无关
#### 11.3.1)设计方案和选型
1)利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
2)利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
方案2更合理,因为方案1不适合的原因如下:
- 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责;
- 破坏了微服务各节点的对等性。
- 有一定的局限性;例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改
#### 11.3.2)设计方案实现
##### 11.3.2.1)给cloud-config-center-3344配置中心服务端添加消息总线支持
修改POM文件,新增:
```xml
org.springframework.cloud
spring-cloud-starter-bus-amqp
```
修改YML文件application.yml,新增:
```yml
#rabbitmq相关配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
##rabbitmq相关配置,暴露bus刷新配置的端点
management:
endpoints: #暴露bus刷新配置的端点
web:
exposure:
include: 'bus-refresh'
```
##### 11.3.2.2)给cloud-config-client-3355客户端添加消息总线支持
修改POM文件,新增:
```xml
org.springframework.cloud
spring-cloud-starter-bus-amqp
```
修改YML文件bootstrap.yml,新增:
```yml
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
```
##### 11.3.2.3)给cloud-config-client-3366客户端添加消息总线支持
同上 3355
##### 11.3.2.4)测试
启动工程 3344、3355、3366
浏览器访问:http://config-3344.com:3344/config-dev.yml
浏览器输出:
```
config:
info: master branch,SpringCloudStudy-Config/config-dev.yml version=3
```
浏览器访问:http://localhost:3355/configInfo http://localhost:3366/configInfo
```
master branch,SpringCloudStudy-Config/config-dev.yml version=3
```
修改Gitee上配置文件增加版本号
浏览器访问:http://config-3344.com:3344/config-dev.yml
浏览器输出:
```
config:
info: master branch,SpringCloudStudy-Config/config-dev.yml version=4
```
浏览器访问:http://localhost:3355/configInfo http://localhost:3366/configInfo
浏览器输出:
```
master branch,SpringCloudStudy-Config/config-dev.yml version=3
```
控制台发送POST请求:
```
curl -X POST "http://localhost:3344/actuator/bus-refresh"
```
再次浏览器访问:http://localhost:3355/configInfo http://localhost:3366/configInfo
浏览器输出:【获取配置信息,发现都已经刷新了,体现了 一次发送,处处生效】
```
master branch,SpringCloudStudy-Config/config-dev.yml version4
```
### 11.4)SpringCloud Bus动态刷新定点通知
不想全部通知,只想定点通知,如只通知3355,不通知 3366,即指定具体某一个实例生效而不是全部
公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
/bus/refresh请求不再发送到具体的服务实例上,而是发给config server并通过destination参数类指定需要更新配置的服务或实例
案例:这里以刷新运行在3355端口上的config-client为例,只通知3355,不通知 3366
更新修改Gitee上配置文件增加版本号
浏览器访问:http://config-3344.com:3344/config-dev.yml
浏览器输出:
```
config:
info: master branch,SpringCloudStudy-Config/config-dev.yml version=5
```
浏览器访问:http://localhost:3355/configInfo http://localhost:3366/configInfo
```
master branch,SpringCloudStudy-Config/config-dev.yml version4
```
控制台发送POST请求:【只通知3355】
```
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
```
再次浏览器访问:http://localhost:3355/configInfo
```
master branch,SpringCloudStudy-Config/config-dev.yml version=5
```
再次浏览器访问:http://localhost:3366/configInfo
```
master branch,SpringCloudStudy-Config/config-dev.yml version4
```
发现上述只有3355已修改读取的配置文件,3366未修改
## 十二、SpringCloud Stream 消息驱动
### 12.1)消息驱动概述
#### 12.1.1)为何引入

#### 12.1.2)官方定义
Spring Cloud Stream 是一个构建消息驱动微服务的框架。
应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream中binder对象交互。通过配置来binding(绑定) ,而 Spring Cloud Stream 的 binder对象负责与消息中间件交互。所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。
通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
目前仅支持RabbitMQ、Kafka。
一句话:**屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型**
官网:https://spring.io/projects/spring-cloud-stream#overview
Spring Cloud Stream是用于构建与共享消息传递系统连接的高度可伸缩的事件驱动微服务框架,该框架提供了一个灵活的编程模型,它建立在已经建立和熟悉的Spring熟语和最佳实践上,包括支持持久化的发布/订阅、消费组以及消息分区这三个核心概念
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/3.0.1.RELEASE/reference/html/
Spring Cloud Stream中文指导手册:https://m.wang1314.com/doc/webapp/topic/20971999.html
#### 12.1.3)设计思想
##### 12.1.3.1)标准MQ
- 生产者/消费者之间靠**消息**媒介传递信息内容 —— Message
- 消息必须走特定的**通道** —— 消息通道MessageChannel
- 消息通道里的消息如何被消费呢,谁负责收发**处理** ——消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅
##### 12.1.3.2)使用Cloud Stream凭什么可以统一底层差异?
在没有绑定器这个概念的情况下,SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性;
- 通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离;
- 通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现;
##### 12.1.3.3)绑定器Binder
【通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离】
Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型:**INPUT和OUTPUT**,INPUT对应于消费者,OUTPUT对应于生产者。

##### 12.1.3.4)Stream中的消息通信方式遵循了发布-订阅模式
Topic主题进行广播,在RabbitMQ就是Exchange;在Kakfa中就是Topic
#### 12.1.4)Spring Cloud Stream标准流程套路
流程如下图:

Binder:很方便的连接中间件,屏蔽差异;
Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置;
Source和Sink:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。
#### 12.1.5)编码API和常用注解
常用注解如下图:

### 12.2)消息驱动概述案例说明
RabbitMQ环境上面已经搭建完毕,新建三个子模块:
- cloud-stream-rabbitmq-provider8801, 作为生产者进行发消息模块
- cloud-stream-rabbitmq-consumer8802,作为消息接收模块
- cloud-stream-rabbitmq-consumer8803 作为消息接收模块
#### 12.2.1)消息驱动之生产者
1. 建立Module模块:新建工程cloud-stream-rabbitmq-provider8801,即作为生产者进行发消息模块;
2. 修改POM文件:修改Module模块下的POM文件,引入SpringCloud Config所需要的包;
```xml
org.springframework.cloud
spring-cloud-starter-stream-rabbit
```
3. 新建YML文件 :新建 application.yml文件,编辑rabbitmq相关配置信息;
```yml
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
```
4. 新建启动文件:新建主启动类,作为8801模块启动的入口;
5. 新建服务相关代码,定义消息发送的相关接口和实现类:
```java
@EnableBinding(Source.class)
public class MessageProviderImpl implements IMessageProvider {
// 消息发送管道
@Resource
private MessageChannel output;
@Override
public String send() {
// 生成流水号
String serial = UUID.randomUUID().toString();
// 消息构建器向消息中间件发送信息
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("*****serial: " + serial);
return null;
}
}
```
6. 测试
启动7001eureka
启动rabbitmq
启动微服务8801
启动微服务8801
浏览器访问:http://localhost:8801/sendMessage
返回:

#### 12.2.2)消息驱动之消费者
1. 建立Module模块:新建工程cloud-stream-rabbitmq-consumer8802,即作为消费者进行消息接收;
2. 修改POM文件:修改Module模块下的POM文件,引入SpringCloud Config所需要的包;
```xml
org.springframework.cloud
spring-cloud-starter-stream-rabbit
```
3. 新建YML文件 :新建 application.yml文件,编辑rabbitmq相关配置信息;
```yml
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
```
4. 新建启动文件:新建主启动类,作为8802模块启动的入口;
5. 新建服务相关代码,定义消息发送的相关接口和实现类:
```java
@Component
//定义消息的接收管道
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message message) {
System.out.println("消费者1号,----->接受到的消息: " + message.getPayload() + "\t port: " + serverPort);
}
}
```
6. 测试
启动7001eureka
启动rabbitmq
启动生产者微服务8801
启动消费者微服务8802
浏览器访问:http://localhost:8801/sendMessage
返回:【在消费者8802成功接收到生产者8801发送的消息】

#### 12.2.3)分组消费与持久化
依照8802,clone出来一份运行cloud-stream-rabbitmq-consumer8803,过程省略....
依次启动上述工程,同时启动8803 ,运行后有两个问题 :
- 有重复消费问题
- 消息持久化问题
测试结果如下:

目前是8802/8803同时都收到了,存在重复消费问题,这个问题必须要解决

##### 12.2.3.1)分组改造
原理:微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次;**不同的组是可以都消费的,同一个组内会发生竞争关系,只有其中一个可以消费**。
修改:在8802和8803的配置文件application.yml文件中均添加 group: study01,如下:
```yml
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: study01
```
说明:
8802/8803实现了轮询分组,每次只有一个消费者,8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。
测试:
浏览器访问 :http://localhost:8801/sendMessage
控制台返回:【同一个组的多个微服务实例,每次只会有一个拿到】

##### 12.2.3.2)持久化
通过上述,解决了重复消费问题,再看看持久化
停止8802/8803并去除掉8802的分组group: study01
8801先发送4条消息到rabbitmq
先启动8802,无分组属性配置,后台没有打出来消息
再启动8803,有分组属性配置,后台打出来了MQ上的消息
测试:http://localhost:8801/sendMessage
输出:

## 十三、SpringCloud Sleuth 分布式请求链路跟踪
### 13.1)SpringCloud Sleuth概述
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的**分布式服务调用链路**,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。
Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin,官网:https://github.com/spring-cloud/spring-cloud-sleuth
简单说:就是将微服务之间相互的调用关系记录下来,以网页的形式展现,如下图:

### 13.2)SpringCloud Sleuth搭建链路监控步骤
#### 13.2.1)zipkin的安装过程
下载:https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec
内网下载:https://search.maven.org/remotecontent?filepath=io/zipkin/zipkin-server/2.23.16/zipkin-server-2.23.16-exec.jar
运行jar:
```
java -jar zipkin-server-2.23.16-exec.jar
```
运行控制台
本地访问:http://localhost:9411/zipkin/
完整的调用链图:

一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来,简化如下图:

Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识;
span:表示调用链路来源,通俗的理解span就是一次请求信息
整个链路的依赖关系如下图:

#### 13.2.2)服务提供者
对之前的服务提供者 cloud-provider-payment8001 进行修改:
POM文件 中引入相应依赖:
```xml
org.springframework.cloud
spring-cloud-starter-zipkin
```
配置文件application.yml 中添加:
```yml
spring:
application:
name: cloud-payment-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
#采样率值介于 0 到 1 之间,1 则表示全部采集
probability: 1
```
服务接口开发:PaymentController中新增
```java
@GetMapping("/payment/zipkin")
public String paymentZipkin() {
return "hi ,i'am paymentzipkin server fall back,welcome to study,O(∩_∩)O哈哈~";
}
```
#### 13.2.3)服务消费者(调用方)
对之前的服务提供者 cloud-consumer-order80 进行修改:
POM文件 中引入相应依赖:
```xml
org.springframework.cloud
spring-cloud-starter-zipkin
```
配置文件application.yml 中添加:
```yml
spring:
application:
name: cloud-order-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1
```
服务接口开发:OrderController 中新增
```java
// ====================> zipkin+sleuth
@GetMapping("/consumer/payment/zipkin")
public String paymentZipkin()
{
String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class);
return result;
}
```
测试:依次启动eureka7001/8001/80 ,使用80调用8001几次测试下,
浏览器朝重复访问:http://127.0.0.1/consumer/payment/zipkin
返回结果:hi ,i'am paymentzipkin server fall back,welcome to study,O(∩_∩)O哈哈~
浏览器访问:http://localhost:9411
返回结果:可以看到所发送请求的调用关系

## 十四、SpringCloud Alibaba 入门简介
### 14.1)为什么会出现SpringCloud alibaba
Spring Cloud Netflix项目进入维护模式,官网:https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now
Netflix:包含五大神兽 ——Eureka \ Zuul \ Ribbon \ Feign \ Config
进入维护模式意味着什么呢?
进入维护模式意味着Spring Cloud Netflix 将不再开发新的组件,我们都知道Spring Cloud 版本迭代算是比较快的,因而出现了很多重大ISSUE都还来不及Fix就又推另一个Release了。进入维护模式意思就是目前一直以后一段时间Spring Cloud Netflix提供的服务和功能就这么多了,不在开发新的组件和功能了。以后将以维护和Merge分支Full Request为主新组件功能将以其他替代平代替的方式实现。
### 14.2)SpringCloud alibaba带来了什么
官网:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
诞生:2018.10.31,Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器,并在 Maven 中央库发布了第一个版本。
包含功能:
- 服务限流降级:默认支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
- 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
- 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
- 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
- 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
包含组件:

### 14.3)SpringCloud alibaba学习资料获取
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。 依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
官网:https://spring.io/projects/spring-cloud-alibaba#overview
英文:https://github.com/alibaba/spring-cloud-alibaba
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
中文:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
## 十五、SpringCloud Alibaba Nacos服务注册和配置中心
### 15.1)Nacos简介
#### 5.1.1)概述
为什么叫Nacos:【Dynamic Naming and Configuration Service】前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service;
Nacos是什么:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台;
Nacos就是注册中心 + 配置中心的组合 ——》 Nacos = Eureka+Config +Bus
Nacos能干嘛:替代Eureka做服务注册中心 / 替代Config做服务配置中心
Nacos下载地址:https://github.com/alibaba/Nacos
Nacos官网文档:https://nacos.io/zh-cn/docs/what-is-nacos.html
#### 15.1.2)安装并运行Nacos
本地Java8+Maven环境已经OK
下载地址:https://github.com/alibaba/nacos/releases/tag/1.4.3
解压安装包,直接运行bin目录下的startup.cmd
```
startup.cmd -m standalone
```
命令运行成功后直接访问:http://localhost:8848/nacos
默认账号密码都是nacos,登录成功如下图:

#### 15.1.3)各种注册中心比较
比较如下:

Nacos全景图所示

Nacos和CAP

Nacos 支持AP和CP模式的切换
**C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。**
何时选择使用何种模式?
一般来说,如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
### 15.2)Nacos作为服务注册中心演示
官网文档:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_config
#### 15.2.1)基于Nacos的服务提供者
1. 建立Module模块:新建工程cloudalibaba-provider-payment9001,即作为服务提供者模块;
2. 修改POM文件:修改Module模块下的POM文件,引入nacos所需要的包;
```xml
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
```
3. 新建YML文件 :新建 application.yml文件,编辑nacos相关配置信息;
```yml
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
```
4. 新建启动文件:新建主启动类,作为9001模块启动的入口,添加 @EnableDiscoveryClient;
5. 新建服务相关代码,定义消息发送的相关接口和实现类:
```java
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id) {
return "nacos registry, serverPort: " + serverPort + "\t id" + id;
}
}
```
6. 测试
启动nacos
启动9001工程
浏览器访问:http://localhost:8848/nacos
nacos自带负载均衡,参照9001新建cloudalibaba-provider-payment9002,不再赘述
登录后看到服务列表如下图,则已经9001模块和9002模块已注册到 nacos 上

#### 15.2.2)基于Nacos的服务消费者
1. 建立Module模块:新建工程cloudalibaba-consumer-nacos-order83,即作为服务消费者模块;
2. 修改POM文件:修改Module模块下的POM文件,引入nacos所需要的包,nacos已经集成了ribbon;
```xml
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
```
3. 新建YML文件 :新建 application.yml文件,编辑nacos相关配置信息;
```yml
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
```
4. 新建启动文件:新建主启动类,作为83模块启动的入口,添加 @EnableDiscoveryClient;
5. 新建服务相关代码,定义消息发送的相关接口和实现类:
```java
public class OrderNacosController {
@Resource
private RestTemplate restTemplate;
// 从配置文件中读取 service-url:nacos-user-service: http://nacos-payment-provider
@Value("${service-url.nacos-user-service}")
private String serverURL;
@GetMapping(value = "/consumer/payment/nacos/{id}")
public String paymentInfo(@PathVariable("id") Long id) {
return restTemplate.getForObject(serverURL + "/payment/nacos/" + id, String.class);
}
}
```
6. 新建负载均衡配置类:
```java
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
```
7. 测试
启动nacos ——》启动9001工程 ——》启动9002工程——》启动83工程
浏览器访问:http://localhost:8848/nacos
同时访问:http://localhost:83/consumer/payment/nacos/13
可以发现在 nacos 上已经注册了3个服务,同时通过83访问服务提供者也是9001和9002轮询出现,说明nacos 自带负载均衡【nacos已经集成了ribbon】

### 15.3)Nacos作为服务配置中心演示
#### 15.3.1)Nacos作为配置中心-基础配置
1. 建立Module模块:新建工程cloudalibaba-config-nacos-client3377,即作为服务配置客户模块;
2. 修改POM文件:修改Module模块下的POM文件,引入nacos所需要的包,nacos已经集成了ribbon;
```xml
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
```
3. 新建YML文件 :新建 application.yml文件,编辑nacos相关配置信息;
```yml
spring:
profiles:
active: dev # 表示开发环境
```
新建bootstrap.yml文件,编辑nacos相关配置信息;
```yml
# nacos配置
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式的配置
group: DEV_GROUP
namespace: 7d8f0f5a-6a53-4785-9686-dd460158e5d4
```
配置两个文件: Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application
4. 新建启动文件:新建主启动类,作为83模块启动的入口,添加 @EnableDiscoveryClient;
5. 新建服务相关代码,定义消息发送的相关接口类:
```java
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@Value("${server.port}")
private String serverPort;
@GetMapping("/config/info")
public String getConfigInfo() {
return "serverPort: " + serverPort + "configInfo: " + configInfo;
}
}
```
通过 Spring Cloud 原生注解@RefreshScope 实现配置自动更新
#### 15.3.2)在Nacos中添加配置信息
理论:Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则;
官网: https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html
要配置的DataId:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} , 即:nacos-config-client-dev.yaml
prefix 默认为 spring.application.name 的值;
spring.profile.active 即为当前环境对应的 profile,可以通过配置项 spring.profile.active 来配置;
file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置
配置总结如下图:

配置如下图:

历史配置:Nacos会记录配置文件的历史版本默认保留30天,此外还有一键回滚功能,回滚操作将会触发配置更新
测试:
启动前需要在nacos客户端-配置管理-配置管理栏目下有对应的yaml配置文件
启动nacos ——》nacos客户端下有对应的yaml配置文件 ——》启动3377工程
浏览器访问:http://localhost:3377/config/info
输出:serverPort: 3377configInfo: from nacos config center, nacos-config-center-dev.yaml, version = 1
修改配置管理栏目下有对应的yaml配置文件 :version = 2
再次浏览器访问:http://localhost:3377/config/info
输出:serverPort: 3377configInfo: from nacos config center, nacos-config-center-dev.yaml, version = 2
【自带动态刷新——再次调用查看配置的接口,就会发现配置已经刷新】