本博客参照了韩顺平老师的 Sping Cloud 课程讲义!

1 Spring Cloud 基本介绍

1.1 官方文档

1.1.1

地址**: https://spring.io/projects/spring-cloud**

1.2 微服务引出

1.没有微服务技术**,是不是程序员就不能开发大型项目?**

是可以的, 对大型项目进行模块划分,对各个模块进行实现, 模块之间更多的是以API调用完成,耦合度较高, 不利于扩展和维护。

2.标准的微服务解决方案(springcloud 和cloud alibaba)出现原因和价值是什么?

(1) **微服务可以根据业务不同,将一个大项目 , 分解成不同的服务(微服务,比如搜 索服务/网关服务/配置服务/存储服务/发现服务等等) **

(2)各个服务通过分布式方式进行工 作,从而可以高效,快速,稳定的完成复杂的功能

(3) 如果小伙伴还不理解, 你也可以理解成就 将原来大项目的某些模块->抽出形成微服务->配合分布式工作方式->从而高效,快速,稳定的完成复杂业务功能。

3. 一图胜千言

image-20230519160836110

1.3 系统架构的演变过程

1.3.1单体架构:

image-20230519161225897

仅适合少量用户的情况

如果用户量大,server无法响应大量请求;大量操作数据库的语句冲垮DB

1.3.2动静分离架构:

静态缓存+文件存储

动静分离:

1.对静态资源、动态资源的请求进行分离过滤,对于静态资源的请求,直接找资源服务器响应;对于动态服务器的请求才找到应用服务器进行响应。

2.对请求的数据进行缓存,使用redis等,降低数据库等负荷。

image-20230519162426761

1.3.3分布式架构:

业务拆分+负载均衡

image-20230519163644982

1.3.4微服务架构:

使用Spring Cloud

点击查看图片来源

  • “微服务” 一词源于 Martin Fowler 的名为Microservices 的博文,简单地说, 微服 务是系统架构上的一种设计风格,它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于HTTP 的 RESTful API 进行通信协作。

  • 被拆分成的每一个小型服务都国绕着系统中的某一项或一些耦合度较高的业务功能进行构建, 并且每个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。 由于有轻量级的通信协作基础,所以这些微服务可以使用不同的语言来编写,这里我们使用 java。

1.4 Spring Colud全面说明

1.Springcloud 来源于 Spring,是更高层次的、 架构视角的综合性大型项目, 目标旨在构建一套标准化的微服务解决方案,让架构师在使用微服务理念构建系统的时, 面对各环节的问题都可以找到相应的组件来处理

2.Spring Cloud 是Spring 社区为微服务架构提供的一个 “全家桶” 套餐。套餐中各个组件之间的配合,可以减少在组件的选型和整合上花费的精力,可以快速构建起基础的微服务架构系统,是微服务架构的最佳落地方案

3.Spirng Cloud 天然支持 spring Boot(有版本对应要求),使用门槛较低

4.解决与分布式系统相关的复杂性 一网络问题,延迟开销,带宽向题,安全问题

5.处理服务发现的能力 一 服务发现允许集群中的进程和服务找到彼此并进行通信

6.解決冗余问题 一 元余问题经常发生在分布式系统中

7.解决负载均衡 一 改进跨多个计算资源(例如计算机集群,网络链接,中央处理单元)的工作负载分布

1.5 Spring Cloud 核心组件图

1.5.1 文档 : https://spring.io/projects/spring-cloud

1.5.2一图胜千言

image-20230519184405128

1.6 Spring Cloud 分布式示意图

1.6.1 文档 : https://spring.io/microservices

1.6.2 一图胜千言

image-20230519185237018

  1. Spring cloud 是微服务的落地

  2. Spring cloud 体现了微服务的弹性设计

  3. 微服务的工作方式一般是基于分布式的。

  4. Spring Cloud 仍然是 Spring 家族一员,可以解决微服务的分布式工作方式带来的各种问题

  5. Spring Cloud 提供很多组件,比如 服务发现,负载均街,链路中断,分布式追踪和监控,甚至提供 API gateway 功能.

1.6.3 SpringCloudSpringBoot版本对应关系

image-20230519190708371

1.7 Spring Cloud 组件选型

image-20230519190901580

2 Spring Cloud Alibaba 基本介绍

2.1 官方文档

2.1.1 英文地址**: https://github.com/alibaba/spring-cloud-alibaba**

2.1.2英 文 地 址 : 中 文 文 档 : https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

2.2 SpringCloudAlibaba是什么

image-20230519192136898

2.3 主要功能一览

image-20230519192150299

2.4 Spring Cloud Alibaba 核心组件

image-20230519192202912

2.5: 分布式微服务技术选型

•Spring Cloud 原生组件的几大痛点

1.Spring Cloud 部分组件停止维护和更新,给开发带来不便

2.Spring cioud 部分环境搭建复杂,没有完善的可视化界面,我们需要大量的二次开发和定制

3.Spring Cloud 配置复杂,难以上手

•Spring Cloud Alibaba 的优势

  1. 阿里使用过的组件经历了考验(高并发,高性能,高可用,性能强悍,设计合理,现在开源

出来供大家使用。

  1. 搭配完善的可视化界面,给开发运维带来极大的便利搭建简单,学习曲线低。
  • 分布式微服务技术选型建议

以Spring Cloud Alibaba为主,以Spring Cloud 原生组件为辅。

3 微服务基础环境搭建

3.1 创建父工程 ,用于聚合其它微服务模块

3.1.1 需求说明**/**图解

3.1.2 实现步骤

3.1.2.1 创建父项目, 作为聚合其它微服务模块

###我们先创建一个父项目**,** 该父项目会去管理多个微服务模块(module)

Maven创建项目 使用webapp Maven工件

3.1.2.2 项目设置

image-20230519194056058

image-20230519194231324

3.父项目本身不写代码,是用来进行管理的

image-20230519194329079

3.1.2.4 配置父工程 pom.xml, 作为聚合其它模块

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.study.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 表明是一个父工程,用于聚合管理其他模块-->
<packaging>pom</packaging>
<name>e-commerce-center Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<!--
老师说明: 使用最新版本的 log4j , 防止安全漏洞
-->
<log4j.version>2.17.2</log4j.version>
<lombok.version>1.18.20</lombok.version>
<mysql.version>8.0.31</mysql.version>
<druid.version>1.1.17</druid.version>
<mybatis.spring.boot.version>2.2.0</mybatis.spring.boot.version>

</properties>

<!--
1. dependencyManagement 作用: 子模块继承后, 锁定版本,
子模块不用再写 version -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<!--
老师解读:
1. type: pom 和 scope import 配合使用
2. 表示 父项目的子模块, 在引入 springboot 相关依赖时 锁定版本为
3. 通过 pom + import 解决 maven 单继承机制
-->
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

3.1.3 注意事项和细节

3.1.3.1 Maven的dependencyManagement说明

1、Maven 使用 dependencyManagement 元素来提供了一种管理依赖版本号的方式。通常在项目

packaging 为 POM,中使用dependencvManadement 元素。

2、使用pom.xml 中的dependencyManagement 元素能让所有在子项目中引用一个依赖, Maven 会沿着父子层次向上走,直到找到一个拥有dependencyManagement 元素的项目,然后它就会使用这个dependencyManagement 元素中指定的版本号。

3、好处:如果有多个子项目都号1用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,当开级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要分别在子项目的修改;另外如果某个子项目需要另外的一个版本,只需要声明version 就可。

4、dependency Management 里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。

5、如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version 和 scope 都读取自父 pom

image-20230519202427190

6、如果子项目中指定了版本号,那么会使用子项目中指定的 jar 版本

3.2 创建会员中心微服务模块 -service provider

3.2.1 需求说明

1、通过浏览器可以获取会员信息**(通过会员中心微服务模块)**

3.2.2 思路分析

1、创建 Moduel & 完成配置

2、创建数据库/表

3、创建 entity-dao/Mapper.xml-service-controller

4、完成测试

3.2.3 实现步骤

​ application.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 10000
spring:
application:
name: member-service-provider-10000 #名称,可以自己指定
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/booking?characterEncoding=utf-8&useSSL=false
username: root
password: QWEASDzxc123
mybatis:
mapper-locations: classpath:mapper/*.xml #后面 mapper 文件的位置
type-aliases-package: com.study.springcloud.entity #实体类的包路径

MemberMapper.xml

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
61
62
63
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.study.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-service-provider-10000</artifactId>
<!-- <packaging>war</packaging>-->
<name>member-service-provider-10000 Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<!-- 使用版本仲裁,从父项目中获取版本-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springboot程序的监控系统,实现系统健康检测
可以通过 http://localhost:10000/actuator 看到相关连接
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

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

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

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


</dependencies>
<build>
<finalName>member-service-provider-10000</finalName>
</build>
</project>

3.2.4 🌟注意事项和细节

传输javabean时

1、我们的前端如果是以 json 格式来发送添加信息Member,那么我们需要使用@RequestBody,

才能将数据封裝到对应的 bean,同时保证 http 的请求头的 content-type 是对应的

2、如果前端是以 表单 或者 parameters 形式提交, 则一定不能使用@RequestBody, 才

会进行对象参数封装,同时保证http 的请求头的 content-type 是对应

3、在进行 SpringBoot 应用程序测试时,引入的 jUnit 是ore.junit.jupiter.api.Test

4、 在运行程序时,一定要确保你的 XxxxMapper.xml 文件被自动放到的 target 目录的

classes 指定目录。

3.3 创建使用会员微服务模块 -service consumer

3.3.1 需求说明**/**图解

image-20230520114632421

-浏览器: http://localhost/member/consumer/get/1

-测试添加会员 : http://localhost/member/consumer/save

3.3.2 思路分析**/**图解

1、创建 Moduel(member-service-consumer-80) & 完成配罝

2、创建 controller

3、完成测试

3.3.3 实现步骤

3.3.3.1 创建 Moduel & 完成配置

创建Moduel:

在创建Moduel后,其父工程的 pom.xml 会做相应变化,在标签中增加管理member-service-consumer-80微服务模块

完成配置:

pom.xml / application.yaml 可参考之前写的,取自己需要的即可。

注入 RestTemplate:
RestTemplate基本介绍:

​ 1、RestTemplate 是Spring 提供的用于访问 Rest 服务的模板类

​ 2、RestTerplate 提供了多种便捷访问远程 Http 服务的方法

​ 3、说明:可以这样理解,通过 RestTemplate,我们可以发出 http 请求(支持Restful 风格),去调用Controller 提供的 API 接口,就像我们使用浏览器发出http 请求调用该 API接口一样。

​ 4、使用简单便捷

官网地址:

https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

创建配置类: com/hspedu/springcloud/config/CustomizationBean.java
1
2
3
4
5
6
7
8
@Configuration
public class CustomizationBean {
//配置注入RestTemplate bean 对象
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
创建:com/hspedu/springcloud/controller/MemberConsumerController.java
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
@RestController
@Slf4j
public class MemberConsumerController {
//url由(ip:端口)+(请求名称)两部分组成
//定义member_service_provider_url
public static final String MEMBER_SERVICE_PROVIDER_URL =
"http://localhost:10000";
//方法/接口,添加member对象到数据库/表

//装配RestController
@Resource
private RestTemplate restTemplate;

@PostMapping("member/consumer/save")
public Result<Member> save(Member member){
log.info("service-consumer member={}",member);
return restTemplate.postForObject(
MEMBER_SERVICE_PROVIDER_URL+"/member/save",member, Result.class);
//url,要save的对象,返回值的类型
}

//根据id 调用服务接口,返回member对象信息
@GetMapping("/member/consumer/get/{id}")
public Result<Member> getMemberById(@PathVariable("id") Long id){
return restTemplate.getForObject(
MEMBER_SERVICE_PROVIDER_URL+"/member/get/" + id,Result.class);
}
}

3.3.3.2 注意事项和使用细节

添加会员数据库中为 null 的解决方案
image-20230520171355467 image-20230520171418620
开启 Run DashBoard

​ 当springcloud 的服务有多个时,管理多个服务的启动使用run会不好管理,这样我们就可以使用

RunDashboard.

找到 你的项目/.idea/workspace.xml 文件在其中添加下面的代码即可

添加在:原本第二行的下一行即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<component name="RunDashboard"> 
<option name="configurationTypes">
<set>
<option value="SpringBootApplicationConfigurationType" />
</set>
</option>
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>

重启项目

视图-工具窗口-服务 即可方便管理各个主程序!

3.4 创建共用模块-供其它模块使用

3.4.1 需求说明**/**图解

image-20230520185154382

3.4.2 思路分析**/**图解

1、创建 Moduel & 完成配置

2、创建entity,把共用的实体类放到对应的包下

3、完成测试

3.4.3 实现步骤

3.4.3.1 创建 Moduel & 完成配置

3.4.3.1.1 创建 e_commerce_center-common-api
在pom.xml中提取公共依赖
1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--老师解读<optional>true</optional>:
1. true 表示两个项目之间依赖不传递
2. 小伙伴可以这里理解<optional>true</optional>: 防止将该依赖传递到其他模块中
说的再具体一点,比如 member-service-consumer-80 模块依赖了本项目,
那么本项目不会把 lombok 传递给 member-service-consumer-80
3. 不设置 optional 或者 optional 是 false,表示传递依赖
-->
<optional>true</optional>
</dependency>
抽取共用 API/类

​ 写入公共的entity

image-20230520191145475
使用 Maven 打包成 jar
image-20230520192050410

会在test目录中生成对应jar包

工程重构

​ 1.在 member-service-consumer-80 引入 e_commerce_center-common-api-1.0-SNAPSHOT.jar

​ 删除原来的 entity

​ 修改 pom.xml

1
2
3
4
5
6
<!-- 引入 e_commerce_center-common-api --> 
<dependency>
<groupId>com.hspedu.springcloud</groupId>
<artifactId>e_commerce_center-common-api</artifactId>
<version>${project.version}</version>
</dependency>

这样就完成了!

4 SpringCloud Eureka 服务注册中心

4.1 Eureka介绍

4.1.1Eureka 前的说明

1、Spring Cloud 组件选型图

![image-20230519190901580](/Users/donn/Library/Application Support/typora-user-images/image-20230519190901580.png)

2、从上图可以看出,目前主流的服务注册&发现的组件是 Nacos, 但是 Eureka 作为一个老牌经典的 服务注册&发现技术还是有必要学习一下,原因:

(1) 一些早期的分布式 微服务项目使用的是 Eureka, 小伙伴在工作中,完全有可能遇到这种情况。

(2) 后期的服务注册&发现组件/技术,都参考了 Eureka 设计和理念,学习了Eureka 后,我们上手Nacos容易很多,而且理解的更深刻。

4.1.2 当前项目架构问题分析 **-**引出 Eureka

一图胜千言

image-20230521192330770

1.在企业级项目中,服务消费访问请求会存在高并发 (一个服务提供模块可能无法承受)

2.如果只有一个会员中心-提供服务模块,可用性差 (如果只有一个服务提供模块,如果故障,系统将无法使用,可用性差)

3.所以,会员中心提供服务往往是一个集群,也就是说会有多个会员中心提供服务微服务模块 ( 高可用 )

4.那么这个时候,就存在一个问题:服务消费方,怎么去发现可以使用的服务

5,当服务消费方,发现了可以使用的服务后(可能是多个,又存在一个问题:到底调用 A服务,还是B服务? 这就引出了服务注册和负载均衡)

6.Eureka 就可以解决上述问题

4.1.3 引入 Eureka 项目架构

一图胜千言

image-20230521193144050

  1. 会员中心-是提供服务的模块,在项目中,会做成集群,提供高可用

  2. Eureka Server 有必要的话,也可以做成集群

  3. Eureka 包含两个组件:Eureka Server和 Eureka Client

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

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

4.1.4 服务治理介绍

4.1.4.1 Eureka实现服务治理

4.1.4.2 在传统的 rpc 远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理困难,所以 需要治理服务之间依赖关系

4.1.4.3 服务治理实现服务调用、负载均衡、容错等,实现服务发现与注册。

4.1.4.4 二说分布式开发: https://jingyan.baidu.com/article/46650658def479f549e5f83e.html

4.1.5 服务注册和发现

Eureka采用了CS [client-server-java基础我们讲过一个多人聊天项目] 的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。

系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接,通过 Eureka Server 来监控系统中各个微服务是否正常运行。

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

服务消费者或者服务提供者,以服务别名的方式去注册中心上获取到实际的服务提供者通讯地址,然后通过RPC调用服务。

4.2 创建单机 Eureka Server-注册中心

4.2.1 需求说明**/**图解

image-20230522104534270

4.2.2 实现步骤

4.2.2.1 创建 Moduel & 完成配置

4.2.2.1.1 创建 e-commerce-eureka-server-9001 微服务模块**[**作为注册中心]
创建 module
4.2.2.1.2 修改 e-commerce-eureka-server-9001pom.xml , 加入依赖
4.2.2.1.3 创建 resources/application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 9001
# 配置eureka-server
eureka:
instance:
hostname: localhost #服务实例名
client:
#不向注册中心注册自己
register-with-eureka: false
#表示自己就是注册中心,作用是维护注册服务实例,不需要去检索服务
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
4.2.2.1.4 创建主启动类 com/hspedu/springcloud/EurekaApplication.java

记得添加@EnableEurekaServer,表示该程序作为EurekaServer

4.2.3member-service-provider-10000 作 为 EurekaClient 注 册 到 e-commerce-eureka-server-9001 成为服务提供者

4.2.3.1 架构示意图

image-20230522124755649

4.2.3.2 修改 member-service-provider-10000 的 pom.xml

1
2
3
4
5
<!--       引入eureka-client场景启动器starter-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

4.2.3.3 修改 member-service-provider-10000 的 resources/application.yml

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

spring:
application:
name: member-service-provider-10000
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/e_commerce_center_db?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
username: root
password: QWEASDzxc123
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.study.springcloud.entity
#配置eureka-client
eureka:
client:
register-with-eureka: true #将自己注册到Eureka-server
#表示从Eureka-server抓取注册信息
#如果是单节点,可以不配置;
#但是provider是一个集群,则必须将此项配置为true,这样才能配合Ribbon使用负载均衡功能
fetch-registry: true
service-url:
# 表示将自己注册到哪个eureka-server
defaultZone: http://localhost:9001/eureka

4.2.3.4 修改 member-service-provider-10000 的 com/hspedu/springcloud/MemberApplication.java

添加注解:

1
2
//@EnableEurekaClient,将该程序标识为Eureka-client
@EnableEurekaClient

4.2.3.5 完成测试

4.2.3.5.1 启动 **e-commerce-eureka-server-9001 **
4.2.3.5.2 启动 **member-service-provider-10000 **
4.2.3.5.3 浏览器**: http://localhost:9001**
4.2.3.5.4 微服务注册名配置说明

image-20230522130616526

4.2.4 配 置 member-service-consumer-80 作 为 EurekaClient, 可 以 拉取 / 获 取e-commerce-eureka-server-9001 提供的服务信息

4.2.4.1 架构示意图

image-20230522202407897

4.2.4.2 修改 pom.xml

1
2
3
4
5
<!--       引入eureka-client场景启动器starter-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

4.2.4.3 修改 application.yml

1
2
3
4
5
6
7
8
9
10
11
12
在原本的配置在增加:
#配置eureka-client
eureka:
client:
register-with-eureka: true #将自己注册到Eureka-server
#表示从Eureka-server抓取注册信息
#如果是单节点,可以不配置;
#但是provider是一个集群,则必须将此项配置为true,这样才能配合Ribbon使用负载均衡功能
fetch-registry: true
service-url:
# 表示将自己注册到哪个eureka-server
defaultZone: http://localhost:9001/eureka

4.2.4.4 修改 MemberConsumerApplication.java

添加注解:@EnableEurekaClient 将该程序标识为Eureka-client

4.2.4.5 完成测试

4.2.4.5.1 启动 **e-commerce-eureka-server-9001 **
4.2.4.5.2 启动 **member-service-consumer-80 **
4.2.4.5.3 浏览器**: http://localhost:9001**

4.2.5 Service ConsumerService ProviderEurekaServer 的维护机制

EurekaServer中维护了服务信息——一个键值对:key是服务名,value是 调用服务的地址

image-20230526170804321

4.2.6 Eureka自我保护模式

4.2.6.1 自我保护模式理论

  1. 在默认情况下, Eureka 启动了自我保护模式

  2. 自我保证机制/模式说明

    • 默认情況下Eurekaclient定时向EurekaServer端发送心跳包。

    • 如果Eureka在server端在一定时间内(默认90秒)没有收到Eurekaclient发送心跳包,便会直接从服务注册列表中剔除该服务。

    • 如果Eureka 开启了自我保护模式/机制,那么在短时间 (90秒中)内丢失了大量的服务实例心跳,这时候EurekaServer会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通或者阻鑫) 因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的。

  3. 自我保护是属于 CAP 里面的 AP 分支, 保证高可用和分区容错性。(cap解读:https://blog.csdn.net/wangliangluang/article/details/120626014)

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

  5. 测试:

    ……

4.3 搭建 EurekaServer 集群- 实现负载均衡&故障容错

4.3.1 为什么需要集群 Eureka Server

image-20230528115636389
  1. 微服务 RPC 远程服务调用最核心的是实现高可用

  2. 如果注册中心只有1个,它出故障,会导致整个服务环境不可用

  3. 解决办法:搭建 Eureka 注册中心集群实现负载均衡+故障容错

4.3.2需求分析**/**图解

image-20230528120007909

4.3.3 搭建 Eureka Server 集群

4.3.3.1 创建 e-commerce-eureka-server-9002 微服务模块[作为注册中心]

4.3.3.1.1 创建步骤参考 e-commerce-eureka-server-9001
4.3.3.1.2 修改 pom.xml , 加入依赖
4.3.3.1.3 创建 resources/application.yml
4.3.3.1.4 创建主启动类 EurekaApplication9002.java

4.3.3.2 修改 e-commerce-eureka-server-9001 微服务模块

4.3.3.2.1 修改 resources/application.yml
4.3.3.2.2 修改主启动类名为 EurekaApplication9001.java

4.3.3.3 修改 hosts 文件

​ 域名转ip地址:先找本机的host文件,hosts文件中没有才去DNS服务器寻找,

​ 现在我们需要访问eureka9001.com这样的形式,就需要自行在本机的hosts文件添加键值对实现域名->ip

/private/etc/hosts 复制出来,修改,覆盖原来的文件即可完成修改

​ 在hosts文件中添加内容:

#eureka 主机名和 ip 映射

​ **127.0.0.1 eureka9001.com **

127.0.0.1 eureka9002.com

4.3.3.4 完成测试

4.3.3.4.1 启动 **e-commerce-eureka-server-9001
4.3.3.4.2** 启动 **e-commerce-eureka-server-9002
4.3.3.4.3** 浏览器**: http://eureka9001.com:9001** 浏览器**: http://eureka9002.com:9002**

4.3.3.5 将 member-service-provider-10000 注册到 EurekaServer 集群(目前 2 台)

4.3.3.5.1 修改 resources/application.yml

‘’’

defaultZone: http://eureka9001.com:9001/eureka, http://eureka9002.com:9002/eureka

*#*表示将自己注册到哪个 eurekaServer

将本微服务注册到多个 eurekaServer, 使用逗号隔开

‘’’

4.3.3.5.2 完成测试

1. 启动 e-commerce-eureka-server-9001 e-commerce-eureka-server-9002

2. 启动 member-service-provider-10000
3.
观察 member-service-provider-10000 是否注册到 Eureka 集群2 )

4.3.3.6 将 member-service-consumer-80 注册到 EurekaServer 集群(目前 2 台)

4.3.3.6.1 修改 resources/application.yml
1
defaultZone: http://eureka9001.com:9001/eureka,http://eureka9002.com:9002/eureka
4.3.3.6.2 完成测试

1. 启动 e-commerce-eureka-server-9001 e-commerce-eureka-server-9002
2. 启动 member-service-consumer-80
3.
观察 member-service-consumer-80 是否注册到 Eureka 集群(目前 2 )

4.3.4 搭建会员中心服务提供方**-**集群

4.3.4.1 架构示意图

image-20230528140357541

4.3.4.2 创建member-service-provider-10002

1.参考member-service-provider-10000 来创建 member-service-provider-10002 即可

2.创建好后,使用memberservice-provider-10000 的源 码和配置替換member-service-provider-10002 生成的代码(不要到磁盘整体拷贝,会出现关联到 member-service-provider-10000 的问题,很麻烦,可以创建好新项目的包,然后再拷贝对应包下的文件,就不会出向题)

3.提醒,拷贝时不要忘记拷贝 resources/mapper/Mem berMapper:xml 这些xaos.xml 文件

4.3.4.3 创建 resources/application.yml

1. 创建好 application.yml

2.member-service-provider-10000 拷贝 application.yml 的内容

3. 修改端口号即可

4.3.4.4 修改主启动类名

1. 修改 member-service-provider-10000 的主启动类为 MemberProviderApplication10000

2. 修改 member-service-provider-10002 的主启动类为 MemberProviderApplication10002

4.3.4.5 完成测试

……

4.3.4.6 注意事项和细节

  1. 因为 member-service-provider-10000 和 mem ber-service-provider-10002 作为一个集群提供服务,因此需要將 spring.application.name 进行统一。
  2. 这样消费方通过统一的别名进行负载均衡调用。
image-20230528142117680

将相同服务的提供方对外暴露的名称设置为相同的,这样就方便管理了。

4.3.5 配置服务消费端 member-service-consumer-80 使用会员中心服务集群

4.3.5.1 架构图

image-20230528143413873

4.3.5.2 修改 MemberConsumerController.java

1
2
public static final String MEMBER_SERVICE_PROVIDER_URL = "http://member-service-provider";
//member-service-provider为服务提供集群对外暴露的统一名称

4.3.5.3 修改 CustomizationBean.java

​ 给 RestTemplate添加注解:**@LoadBalanced**

​ 目的是:赋予 RestTemplate 负载均衡的能力

4.3.5.5 完成测试

4.3.5.5.1 启动 eureka server 集群**(目前 2)**
4.3.5.5.2 启动 member-service-provider-10000
4.3.5.5.3 启动 member-service-provider-10002
4.3.5.5.4不急,先测试 : http://localhost:10000/member/get/1http://localhost:10002/member/get/1

先看看直接使用服务提供方时有没有问题,没问题再进行下一步测试(方便排错!)

4.3.5.5.5 启动 member-service-consumer-80
4.3.5.5.6 浏览器访问**: http://localhost/member/consumer/get/1**

4.3.5.6 交替访问member服务说明:

  1. 注解@LoadBalanced 底层是Ribbon支持算法
  2. 2.Ribbon和Eureka 整合后consumer 直接调用服务而不用再关心地址和端口号,且该服务还有负载功能

4.3.6 获取 Eureka Server 服务注册信息 -DiscoveryClient

4.3.6.1 需求分析/图解

1. 需求分析示意图

image-20230528150507261

2. 这里我们以服务消费方去获取 Eureka Server 的服务注册信息为例讲解

3. 当然也可以在服务提供方获取 Eureka Server 的服务注册信息

4.3.6.2 代码实现

  1. 所在模块 member-service-consumer-80
  2. 修改 com/hspedu/springcloud/controller/MemberConsumerController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//装配DiscoveryClient
@Resource
private DiscoveryClient discoveryClient;

@GetMapping("/member/consumer/discovery")
public Object discovery(){
List<String> services = discoveryClient.getServices();
for (String service : services){
log.info("服务名={}",service);
List<ServiceInstance> instances = discoveryClient.getInstances(service);
for (ServiceInstance instance : instances){
log.info("id={},host={},port={},uri={}",instance.getServiceId(),instance.getHost(),instance.getPort(),instance.getUri());
}
}
return discoveryClient;
}

3. 这里修改主启动类 com/hspedu/springcloud/MemberConsumerApplication.java

添加注解:@EnableDiscoveryClient

4.3.6.3 测试

1. 重启 member-service-consumer-80

2. 浏览器输出 http://localhost/member/consumer/discovery

4.3.6.4 注意事项和细节说明

  1. 在引入 DiscoveryClient 时,不要引入错误的包:

    正确的包**:** import org.springframework.cloud.client.discovery.DiscoveryClient;

    错误的包**:** import com.netflix.discovery.DiscoveryClient;

4.4 Eureka后续说明

1. Eureka 停更说明**: https://github.com/Netflix/eureka/wiki**

2. 对于一些早期使用 Eureka 项目,掌握老师讲解技术基本可以应付了(这也是老师为什么还 要讲 Eureka 的原因)

3.目前替代 Eureka 做服务注册和发现,均衡负载的 最佳组件是 spring Cloud Alibaba Nacos,后面老师会重点讲解。

4.虽然Eureka 停更,目前用的不多,但是它的服务注册和发现,均衡负载的思想是优先的,有了Eureka 的基础,我们学习 Spring Cloud Alibaba Nacos 会轻松很多

5 SpringCloudRibbon服务负载均衡

5.1 Ribbon介绍

5.1.1 Ribbon是什么

  1. Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套**客户端负载均衡**的工具。

  2. Ribbon 主要功能是提供客户端负载均衡算法和服务调用

  3. Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等。

  4. Ribbon 会基于某种规则(如简单轮询,随机连接等)去连接指定服务

  5. 程序员很容易使用 Ribbon 的负载均衡算法实现负载均衡

  6. 一句话:Ribbon: 负载均衡+RestTemplate调用

5.1.2 官网

5.1.2.1 https://github.com/Netflix/ribbon

5.1.3 Ribbon进入维护状态

Ribbon 目前进入维护模式, 未来替换方案 是 Spring Cloud LoadBalancer

5.1.4 LB(Load Balance)

5.1.4.1 LB分类

集中式 LB
  • 即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访向请求通过某种策略转发至服务的提供方:

  • LB(Load Balance 负载均衡)

进程内 LB
  • 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些服务地址可用,然后再从这些地址中选择出一个合适的服务地址。

  • Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

5.1.4.2 实例-前面 member-consumer 轮询负载访问 10000/10002 底层就是 Ribbon 默认的轮询负载算法

5.2 Ribbon原理

5.2.1 Ribbon架构图**&**机制

image-20230529203201726Ribbon 机制

  1. 先选择 EurekaServer,它优先选择在同一个区域内负载较少的 server
  2. 再根据用户指定的策略,在从 server取到的服务注册列表中选择一个地址
  3. Ribbon 提供了多种策略:比如轮询、随机和根据响应时间加权。

5.2.2 Ribbon常见负载算法

image-20230529203353777

5.3 替换负载均衡算法-应用实例

5.3.1 需求分析**/**图解

  1. 需求:将默认的轮询算法改成随机算法 RandomRule
  2. 浏览器输入:htp://ocalhost/member/consuimer/get/1
  3. 要求 彷问的 10000/10002 端口的服务是随机的

5.3.2代码实现

  1. 创建 member-service-consumer-80com/hspedu/springcloud/config/RibbonRule.java

5.3.3 测试

1. 浏览器输入 **: http://localhost/member/consumer/get/1 **

2. 观察访问的 10000/10002 端口的服务是随机的

6 SpringCloud Open Feign服务调用

6.1 OpenFeign介绍

6.1.1 OpenFeign是什么

  1. 0penFeign 是个声明式 Webservice 客户端,使用 OpenFeign 让编写 web Service 客户端更简单

  2. 它的使用方法是定义一个服务接口然后在上面添加注解

  3. 0penFeign 也支持可拔插式的编码器和解码器。

  4. Spring cloud 对 OpenFeign 进行了封装使其支持了 Spring Mvc 标准注解和HttpMessageConverters

  5. 0penFeign 可以与Eureka 和 Ribbon 组合使用以支持负载均衡

6.1.2 官网

6.1.2.1 https://github.com/spring-cloud/spring-cloud-openfeign

6.1.3 FeignOpenFeign区别

​ Feign(不好用,没人用了):

  • Feign是Spring Cioud组件中的一个轻量级RESTful的HTTP服务客户端

  • Feign内置了Ribbon,用来做客户端免载均衡, 去调用服务注册中心的服务。

  • Feign的使用方式是:使用Feign的注解定义接口,调用服务注册中心的服务

  • Feign支持的注解和用法请参考官方文档:https://githulb.com/OpenFeign/feign

  • Feign本身不支持Spring MvC的注解,它有一套自己的注解

​ OpenFeign:

  • openFeign是Spring Cloud **在Feign的基础上支持了Spring MvC的注解**,如@Requesapping等等。

  • OpenFeign的@FeignClient可以解析SpringvC的@RequestMapping注解下的接口

  • OpenFeign通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务

精简一句话 : OpenFeign 就是在 Feign 基础上做了加强 , 有些程序员为了方便,说 Feign 就是指的 OpenFeign

6.2 OpenFeign-应用实例

6.2.1 需求分析**/**图解

image-20230529210545959

(Eureka实现获取服务、Ribbon实现自定义负载均衡算法、RestTemplate实现远程调用)

(Eureka实现获取服务、OpenFeign实现负载均衡+远程调用)

6.2.2 创建服务消费模块 **-**通过 OpenFeigen 实现远程调用

1. 参考 member-service-consumer-80 创建 **member-service-consumer-openfeign-80(**具体步骤参考以前)

2. 修改 pom.xml

1
2
3
4
5
<!--    引入openfeign-start,即场景启动器    -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

**3.创建 application.yml 内容如下:**、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 80
spring:
application:
name: e-commerce-consumer-openfeign
#配置eureka-client
eureka:
client:
register-with-eureka: true #将自己注册到Eureka-server
#表示从Eureka-server抓取注册信息
#如果是单节点,可以不配置;
#但是provider是一个集群,则必须将此项配置为true,这样才能配合Ribbon使用负载均衡功能
fetch-registry: true
service-url:
# 表示将自己注册到哪个eureka-server
defaultZone: http://eureka9001.com:9001/eureka,http://eureka9002.com:9002/eureka

4.创建主启动类

1
2
3
4
5
6
7
8
@EnableFeignClients //启动openFeignClient
@SpringBootApplication
@EnableEurekaClient
public class MemberConsumerOpenfeignApplication {
public static void main(String[] args) {
SpringApplication.run(MemberConsumerOpenfeignApplication.class,args);
}
}
  1. 创建 com/study/springcloud/service/MemberFeignService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@FeignClient(value = "MEMBER-SERVICE-PROVIDER")
public interface MemberFeignService {

//在这里定义方法——远程调用服务提供方给的接口
//原来是使用RestController实现功能的

//1.远程调用的方式是get
//2.远程调用的url http:MEMBER-SERVICE-PROVIDER/member/get/{id}
//3.MEMBER-SERVICE-PROVIDER是服务提供方在Eureka Server注册的服务
//4.openfeign会根据负载均衡来决定调用哪个服务-默认是轮询
//5.优点:openfeign支持了springMVC注解 + 接口 -->解耦

@GetMapping("/member/get/{id}")
public Result getMemberById(@PathVariable("id")Long id);

}
  1. 创建 com/study/springcloud/controller/MemberConsumerFeignController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController //结果以json格式返回
public class MemberConsumerFeignController {

//装配MemberFeignService
@Resource
private MemberFeignService memberFeignService;

@GetMapping(value = "/member/consumer/openfeign/get/{id}")
public Result getMemberById(@PathVariable("id")Long id){
return memberFeignService.getMemberById(id);

}
}

6.2.3 测试

​ 浏览器输入 : http://localhost/member/consumer/openfeign/get/1

6.2.4 注意事项和细节

  • 配Openfeign的使用特点是 微服务调用接口+@Feignclient,使用接口进行解耦

  • @FeignClient(value = “MEMBER-SERVICE-PROVIDER”),这里MEMBER-SERVICE-PROVIDER就是Eureka Server服务提供方注册的名称,不要写错了

  • 接口方法上: value是不能乱写,远程调用的url 为http://MEMBER-SERVICE-PROVIDER/member/get/{id}

​ @GetMapping(“/member/get/{id}”)
​ public Result getMemberById(@PathVariable(“id”)Long id);

6.3 日志配置

6.3.1 基本介绍

1.说明:Feign 提供了日志打印功能,可以通过配罝来调整日志级别,从而对 Feign 接口的调用情况进行监控和输出

2.日志级别

  • NONE :默认的,不显示任何日志

  • BASIC:仅记录请求方法、URL、响应状态码及执行时间;

  • HEADERS : 除了 BASIC中定义的信息之外,还有请求和响应的头信息;

  • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

6.3.2 配置日志**-**应用实例

1.member-service-consumer-openfeign-80 创 建

com/hspedu/springcloud/config/OpenFeignConfig.java

1
2
3
4
5
6
7
8
9
@Configuration
public class OpenFeignConfig {

@Bean
public Logger.Level loggerLevel(){
return Logger.Level.FULL;
}

}

2.member-service-consumer-openfeign-80 修改 application.yml

1
2
3
4
#对MemberFeignService接口调用过程打印信息-debug
logging:
level:
com.study.springcloud.service.MemberFeignService: debug
image-20230530125632781

6.3.3 测试

……

6.4 OpenFeign超时

若服务提供方(可能由于各种原因,比如操作数据库时间过长)响应超过OpenFeign的等待时间(默认1秒),会返回超时错误(time out)

……

6.4.3 设置OpenFeign超时时间

在服务消费方的配置文件中进行响应配置:

1
2
3
4
5
6
7
8
9
# 设置feign客户端超时时间
# read-timeout 建立连接:从服务提供方获取可用资源的全部时间
# connect-timeout 两端连接所用时间
feign:
client:
config:
default:
connect-timeout: 5000
read-timeout: 5000

6.4.4 测试

……

7 🌟SpringCloudGateway 服务网关

7.1 Gateway介绍

7.1.1 看一个需求,引出网关服务

1、有一个前后端分离项目**,** 分析如图

image-20230530144204835

2、使用网关服务, 重构项目架构

image-20230530144645171

7.1.2 🌟Gateway 网络拓扑图

image-20230530151559362

7.1.3 Gateway是什么

  1. Gateway 是在 Spring 生态系统之上构建的 API 网关服务,基于 spring , Spring Boot 和 Project Reactor 等技术。

  2. Gateway 旨在提供一种简单而有效的方式来对 API 进行路由,以及提供一些强大的过滤器功能,例如 :熔断、限流、重试等。

7.1.4 官网

7.1.4.1 https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

7.1.5 Gateway 核心功能

7.1.5.1 鉴权

7.1.5.2 流量控制

7.1.5.3 熔断

7.1.5.4 日志监控

7.1.5.5 反向代理

7.1.6 Gateway vs Zuul

7.1.6.1 Gateway 和 Zuul 区别

  1. SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul

  2. SpringCloud Gateway 是基于 Spring WebFlux框架实现的

  3. Spring WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty,提升了网关性能

7.1.6.2 Gateway 特性

​ Spring Cloud Gateway 基 于 Spring Framework(支 特 Spring WebFlux), project Reactor 和 spring Boot 进行构建,具有如下特性:

  • 动态路由
  • 可以对路由指定 predicate(断言)和Filter(过滤器)
  • 集成Hystrix的断路器功能
  • 集成 Spring Cloud 服务发现功能
  • 请求限流功能
  • 支持路径重写

7.2 Gateway基本原理

7.2.1 Gateway核心组件

7.2.1.1 一张图

image-20230530152704594
  1. web 请求通过一些匹配条件,定位到真正的服务节点/微服务模块,在这个转发过程的前后,进行一些精细化控制。

  2. predicate:就是匹配条件(请求是否能被接受使用)

  3. filter:可以理解为是网关的过滤机制。

    有了predicate 和filter,再加上目标 URL,就可以实现一个具体的路由

7.2.1.2 Route(路由)

一句话:路由是构建网关的基本模块,它由 ID目标 URL一系列的断言和过滤器组成,如果断言为 true 则匹配该路由。

7.2.1.3 Predicate(断言)

  1. 一句话:对 HTTP 请求中的所有内容(例如请求头或请求参数)进行匹配,如果请求与断言相匹配则进行路由

  2. 简单举例,比如配置路径,- Path=/member/get/** 断言,路经相匹配的进行路由转发,如果Http 请求的路径不匹配,则不进行路由转发。

7.2.1.4 Filter(过滤)

1、一句话:使用过滤器,可以在请求被路由前或者之后对请求进行处理

2、你可以理解成,在对 Http 请求断言匹配成功后,可以通过网关的过滤机制,对Http 请求处理

7.2.2 HowItWorks工作机制

7.2.2.1 一图胜千言

image-20230530163559159
  1. 客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,將其发送到 Gateway Web Handler。

  2. Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

  3. 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(”pre”)或之后(“post”)执行业务逻辑。

  4. Filter 在”pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,

  5. 在”post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

一句话说:路由转发+执行过滤器链

7.3 搭建 Gateway 微服务

7.3.1 搭建 **Gateway-**应用实例

7.3.1.1 需求分析/图解

– 引入 Gateway 项目架构

image-20230531095247037

1. 通过网关暴露的接口,实现调用真正的服务

2. 网关本身也是一个微服务模块

7.3.1.2 代码实现

1. 参考 member-service-consumer-80 创建 **e-commerce-gateway-20000(**具体步骤参考以前)
2. 修改 pom.xml, 部分内容可以从 member-service-consumer-80pom.xml 拷贝

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.study.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<artifactId>e-commerce-gateway-20000</artifactId>

<dependencies>

<!-- 引入gateway start, 网关场景启动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!-- 引入eureka-client场景启动器starter-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>


<!--老师特别说明:
不要引入:spring-boot-starter-web、spring-boot-starter-actuator
否则会出现冲突
因为gateway是一个服务网关,不需要web
-->

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 引入 e_commerce_center-common-api -->
<dependency>
<groupId>com.study.springcloud</groupId>
<artifactId>e_commerce_center-common-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

</project>

3.创建 application.yml(重点核心) 内容如下:

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
server:
port: 20000
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 #路由的id,程序员自行配置
#gateway 最终访问的url 是 uri + path
#匹配后提供服务的路由地址:也可以是外网 如www.baidu.com
#比如:客户端/浏览器请求url:http:localhost:20000/member/get/1
#网关对请求url中的path进行匹配,如果匹配失败,返回404
#如果匹配成功,最终访问的url=http://localhost:10000/member/get/1
# 即 uri + path
#这里⬇️的uri是固定不变的,在当前这种情况下是可以不使用Eureka Server 的
#后面会使用灵活uri配置,会用到Eureka Server
uri: http://localhost:10000
predicates: # 断言,可以有多种形式
- Path=/member/get/**
# 配置Eureka Client
eureka:
instance:
hostname: e-commerce-service
client:
register-with-eureka: true #将自己注册到Eureka-server
#表示从Eureka-server抓取注册信息
#如果是单节点,可以不配置;
#但是provider是一个集群,则必须将此项配置为true,这样才能配合Ribbon使用负载均衡功能
fetch-registry: true
service-url:
# 表示将自己注册到哪个eureka-server
#这里为了方便,就使用9001端口一个server
defaultZone: http://eureka9001.com:9001/eureka

**4.**创建主启动类 com/hspedu/springcloud/GateWayApplication20000.java

1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaClient
public class GateWayApplication20000 {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication20000.class,args);
}
}

7.3.1.3 测试

……

7.3.1.4 注意事项和细节

因为我们的 member 的 controller 的方法参数使用了@RequestBody ,所以在使用 postman时,需要使用 json 格式发送数据, 否则会报400错误。

7.3.2 二说 Gateway 路由配置

7.3.2.1 方式 1: application.yml 中配置-前面讲过

7.3.2.2 方式 2: 编写配置类注入【了解】

1. 先注销 application.yml 对网关路由部分注销

2. 重启 e-commerce-gateway-20000, 再次测试,网关路由失效

3. 参考官方文档:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#spring-cloud-circuitbreaker-filter-factory , 创 建com/hspedu/springcloud/config/GateWayRoutesConfig.java

7.3.2.3 测试

……

7.3.3 动态路由

7.3.3.1 需求分析/图

image-20230531150741007

7.3.3.2 代码实现

1. 修改 e-commerce-gateway-20000application.yml

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
server:
port: 20000
spring:
application:
name: e-commerce-gateway
cloud:
gateway:
discovery:
locator:
# 启用 DiscoveryClient 服务发现
enabled: true
routes: #配置路由,可以配置多个 List<RouteDefinition> routes
- id: member_route01 #路由的id,程序员自行配置
# 1. lb:负载均衡协议名称
# member-service-provider:注册到eureka server的服务名(小写)
# 2. 默认情况下,负载均衡算法为轮询
uri: lb://member-service-provider
predicates:
- Path=/member/get/**
- id: member_route02
uri: lb://member-service-provider
predicates:
- Path=/member/save
- id: member_route03
uri: lb://member-service-provider
predicates:
- Path=/
# 配置Eureka Client
eureka:
instance:
hostname: e-commerce-service
client:
register-with-eureka: true #将自己注册到Eureka-server
#表示从Eureka-server抓取注册信息
#如果是单节点,可以不配置;
#但是provider是一个集群,则必须将此项配置为true,这样才能配合Ribbon使用负载均衡功能
fetch-registry: true
service-url:
# 表示将自己注册到哪个eureka-server
#这里为了方便,就使用9001端口一个server
defaultZone: http://eureka9001.com:9001/eureka

7.3.3.3 测试

7.3.3.3.1 启动 e-commerce-eureka-server-9001
7.3.3.3.2 启动 member-service-provider-10000
7.3.3.3.3 启动 member-service-provider-10002
7.3.3.3.4 启动 e-commerce-gateway-20000
7.3.3.3.5 浏览器**:(通过网关访问) http://localhost:20000/member/get/1**

​ 浏览器输入**: http://localhost:20000/member/get/1**

7.3.3.4 注意事项和细节

7.3.3.4.1 配置好动态路由后 Gateway 会根据注册中心上微服务名,为请求创建动态路由,实现 动态路由 功能
7.3.3.4.2 使用的 lb 协议支持负载均衡**-**轮询算法
7.3.3.4.3 配置自己的负载均衡算法, 测试完毕恢复成原来的轮询算法

7.4 Predicate/断言

7.4.1 基本介绍

7.4.1.1 一句话: Predicate 就是一组匹配规则,当请求匹配成功,就执行对应Route, 匹配失败,放弃 处理/转发

7.4.1.2 RoutePredicateFactories

文档地址: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories

  • Spring Cloud Gateway包括许多内置的Route Predicate工厂,所有这些Predicate都与HTTP请求的不同属性匹配,可以组合使用.
  • Spring Cloud Gateway 创建 Route 对象时,使用RoutePredicateFactory 创建Predicate对象,Predicate 对象可以赋值给Route。
  • 所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合

其实就是利用工厂中的api进行断言

7.4.2 RoutePredicate实例(根据上述文档进行操作演示)

7.4.2.1 AfterRoutePredicate

7.4.2.1.1 需求分析**/**图解

1. 需求分析**/**图解

​ 需求**:** 只有 2022-11-18 12:35:50 之后的请求才进行匹配**/转发,** 不满足该条件的,不处理

7.4.2.1.2 代码实现
1. 参考文档 : https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories

2. 修改 e-commerce-gateway-20000 application.yml

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]

7.4.2.2 BeforeRoutePredicate

1. 需求分析**/**图解

需求**:** 只有 2022-11-18 12:35:50 之前的请求才进行匹配**/转发,** 不满足该条件的,不处理

7.4.2.2.2 代码实现

1. 参考文档 : https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories

2. 修改 e-commerce-gateway-20000 application.yml

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://example.org
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]

7.4.2.3 BetweenRoutePredicate

1. 需求分析**/**图解

需求**:** 只有 2020-11-18 12:35:502022-11-18 12:35:50 之间 的请求才进行匹配**/转 发,** 不满足该条件的,不处理

7.4.2.3.2 代码实现

1. 参考文档 : https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories

2. 修改 e-commerce-gateway-20000 application.yml

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

7.4.2.4 CookieRoutePredicate

7.4.2.4.1 需求分析**/**图解

​ 需求**:** 请求带有 cookie 键**: user** 值**: hsp** 才匹配**/**断言成功

1.4.2.4.2 代码实现

1. 参考文档 : https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories

2. 修改 e-commerce-gateway-20000 application.yml

1
2
3
4
5
6
7
8
9
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
# chocolate 是 cookie 名字 ch.p 是 cookie 的值,是按照正则表达式来匹配的

7.4.2.5 HeaderRoutePredicate

7.4.2.5.1 需求分析**/**图解

1. 需求分析**/**图解

需求: 请求头 Header X-Request-Id, 并且值为xxxxx 才匹配/断言成功

1
2
3
4
5
6
7
8
9
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
# X-Request-Id 是 header 的名称, \d+ 是一个正则表达式

7.4.2.6 HostRoutePredicate

7.4.2.6.1 需求分析**/**图解

1. 需求分析**/**图解

需求: 请求 Host *.hspedu.* 才匹配/断言成功 , 比如 Host: www.hspedu.com

1
2
3
4
5
6
7
8
9
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
#Host 可以有多个, 使用逗号间隔

7.4.2.7 MethodRoutePredicate

1.4.2.7.1 需求分析**/**图解

需求: 请求是 XXXXXX 方式才匹配/断言成功

1
2
3
4
5
6
7
8
9
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST
# 请求方式可以有多个, 使用逗号间隔

7.4.2.8 PathRoutePredicate

1
2
3
4
5
6
7
8
9
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
# Path 可以有多个, 使用逗号间隔

7.4.2.9 QueryRoutePredicate

1.4.2.9.1 需求分析**/**图解

需求**:** 请求有参数 email ,并且满足电子邮件的基本格式, 才能匹配**/**断言成功

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=red, gree.
#red 是参数名 gree. 是值, 支持正则表达式.

#对应邮箱:
# - Query=email, [\w-]+@([a-zA-Z]+\.)+[a-zA-Z]+

7.4.2.10 RemoteAddr Route Predicate

7.4.2.10.1 需求分析**/**图解

需求: 请求的 IP 127.0.0.1, 才能匹配/断言成功

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24

7.5 Filter/过滤器

7.5.1 基本介绍

1. 文档地址 : https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应

Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生

7.5.2 类型

7.5.2.1 GatewayFilter

7.5.2.2 GlobalFilter

一般很少使用官方的filter,都是自定义满足需求,因此只介绍一个官方filter

7.5.3 GatewayFilter使用

7.5.3.1 开发直接使用 GatewayFilter 较少,一般是自定义过滤器

​ 演示:The AddRequestParameter GatewayFilter Factory

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
filters:
- AddRequestParameter=color, blue

7.5.4 自定义 GlobalFilter

7.5.4.1 需求分析/图解

1. 自定义全局 GlobalFilter 过滤器
2. 如果请求参数 user=hspedu , pwd=123456 则放行**,** 否则不能通过验证

7.5.4.2 代码实现

1.e-commerce-gateway-20000 创建com/hspedu/springcloud/filter/CustomGateWayFilter.java

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
package com.study.springcloud.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
* @author 浦原
* 2023/6/1
* 11:14
* @version 1.0
*/
@Component
public class CustomGateWayFilter implements GlobalFilter, Ordered {

//filter是核心的方法,将我们的过滤业务写在该方法中
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//先获取到对应的参数
String user =
exchange.getRequest().getQueryParams().getFirst("user");
String pwd =
exchange.getRequest().getQueryParams().getFirst("pwd");
if(!("hspedu".equals(user) && "123456".equals(pwd))){
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}

//表示过滤器执行的顺序,数字越小,优先级越高
@Override
public int getOrder() {
return 0;
}
}

7.5.4.3 测试

7.5.4.3.1 启动 e-commerce-eureka-server-9001
7.5.4.3.2 启动 member-service-provider-10000/10002
7.5.4.3.3 启动 e-commerce-gateway-20000
7.5.4.3.4 浏览器**:(通过网关访问) http://localhost:20000/member/get/1?user=hspedu&pwd=123456** 输入**: http://localhost:20000/member/get/1?user=hspedu&pwd=123456**

7.5.4.4 测试完毕,记得代码恢复到测试前

8 SpringCloud Sleuth+Zipkin 服务跟踪

8.1 Sleuth/ZipKin基础

8.1.1 官网

8.1.1.1 https://github.com/spring-cloud/spring-cloud-sleuth

8.1.2 Sleuth/Zipkin是什么?

8.1.2.1 概述(两张图)

  1. 在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用**,** 来协同产生最后的请求结果,每一个请求都会形成一条复杂的分布式服务调用链路

  2. 链路中的任何一环出现高延时或错误都会引起整个请求最后的失败,因此对整个服务的调用进行链路追踪和分析就非常的重要

  3. Sleuth 和 Zipkin简单关系图

image-20230601125136474

8.1.2.2 一句话: Sleuth 提供了一套完整的服务跟踪的解决方案 并兼容 Zipkin

8.1.2.3 梳理: 🌟Sleuth 做链路追踪 , Zipkin 做数据搜集/存储/可视化

8.1.3 Sleuth工作原理

1. SpanTrace 在一个系统中使用 Zipkin 的过程**-**图形化

image-20230603152155135

  • 表示一请求链路,一条链路通过Trace id唯一标识,span标识发起的请求信息,各span通过parent id关联起来

  • Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识 (一条请求链路只有唯一一个Trace id)

  • Span:基本工作单元,表示调用链路来源,通俗的理解span就是一次请求信息

2. spans parent/child 关系图形化

image-20230603152459615

  • 小伙伴注意看老师标识的红线,后一个span节点的parentid 指向/记录 了上一个Span结点

  • span就是一次请求信息

  • 多个Span集合就构成一条调用链路

  • 在span=C 这个节点存在分支

8.2 Sleuth/ZipKin-搭建链路监控实例

8.2.1 需求说明**/**图解

要求: 通过 Sleuth Zipkin 可以对服务调用链路进行监控, 并在 Zipkin 进行显示

image-20230603152942262

8.2.2 安装**/**使用 Zipkin

8.2.2.1 下载

  1. 下载地址:https://repo1.maven.org/maven2/io/zipkin/zipkin-server/

  2. 得到 zipkin-server-2.14.1-exec.jar

8.2.2.2 运行

  1. 进入 zipkin-server-2.14.1-exec.jar 所在目录的终端
  2. 执行命令 java -jar zipkin-server-2.14.1-exec.jar 开启zipkin服务

8.2.2.3 访问

​ 浏览器输入**:http://localhost:9411/zipkin/**

image-20230603225430611

8.2.3 服务提供方集成 Sleuth/Zipkin

1. 修改 member-service-provider-10000pom.xml , 增加引入 sleuth+zipkin

1
2
3
4
5
6
<!--    引入sleuth与zipkin相关依赖    -->
<!--包含了 sleuth+zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
image-20230603230040079

2. 修改 member-service-provider-10000 appliaction.xml , 指定 Zipkin

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

spring:
application:
name: member-service-provider
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/e_commerce_center_db?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
username: root
password: QWEASDzxc123


#配置sleuth和zipkin
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
# 采样率 在0~1之间,1表示全部采集
probability: 1


mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.study.springcloud.entity
#配置eureka-client
eureka:
client:
register-with-eureka: true #将自己注册到Eureka-server
#表示从Eureka-server抓取注册信息
#如果是单节点,可以不配置;
#但是provider是一个集群,则必须将此项配置为true,这样才能配合Ribbon使用负载均衡功能
fetch-registry: true
service-url:
# 表示将自己注册到哪个eureka-
# 将本服务注册到多个eureka-server,使用逗号间隔即可
defaultZone: http://eureka9001.com:9001/eureka,http://eureka9002.com:9002/eureka
# instance:
#1秒发送一次心跳(默认是90秒
# lease-renewal-interval-in-seconds: 1
#服务端收到最后一次心跳后的等待时间上限
# lease-expiration-duration-in-seconds: 2

8.2.4 服务消费方集成 Sleuth/Zipkin

1. 修改 member-service-consumer-80pom.xml , 增加引入 sleuth+zipkin

2. 修改 member-service-consumer-80appliaction.xml , 指定 Zipkin

8.2.5 测试

8.2.5.1 启动 e-commerce-eureka-server-9001

8.2.5.2 启动 member-service-provider-10000

8.2.5.3 启动 member-service-consumer-80

8.2.5.4 浏览器: 浏览器输入: http://localhost/member/consumer/get/1

8.2.6 查看监控**&**分析结果

8.2.6.1 查看监控&分析结果

1. 查看 **Zipkin : http://localhost:9411/zipkin/ **

2. 选择某个服务,看结果

image-20230604102426067 image-20230604102442418

3. 查看一次调用链路的深度,以及该链路包含请求,各个请求耗时,**找到请求瓶颈,为优化提供依据**(重要)

image-20230604102455803

4. 查看服务调用的依赖关系

image-20230604102509066

9 🌟SpringCloudAlibabaNacos 服务注册中心+服务配置

9.1 Nacos基础

9.1.1 官网

9.1.1.1 https://github.com/alibaba/Nacos

9.1.2 Nacos是什么?

9.1.2.1 一句话: Nacos 就是注册中心[替代 Eureka]+配置中心[替代 Config]

9.1.2.2 Nacos:Dynamic Naming and Configuration Service

9.1.2.3 Nacos:架构理论基础: CAP 理论 (支持 AP 和 CP, 可以切换)

CAP理论:

​ 一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。

  • 一致性指 “all nodes see the same data at the same time”,即所有节点在同一时间的数据完全一致。即多副本(Replications)问题中的数据一致性
  • 可用性指“Reads and writes always succeed”,即服务在正常响应时间内一直可用。好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
  • 分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。

比如对于两个结点如果它们之间的网络断开了,我们假设需要满足分区容错性,那么一定要在一致性和可用性之间进行取舍(分区容错性满足的情况下,如果满足一致性那就必须等待网络恢复后数据进行同步后才能提供服务——放弃了可用性;反之,如果选择了可用性,那么两个结点提供的数据一定不能保证一致性——牺牲了一致性)

9.1.3 Nacos下载&运行

9.1.3.1 下载: https://github.com/alibaba/nacos/releases/tag/1.2.1

9.1.3.2 环境要求: Java8/Maven 3.2.x+

9.1.3.3 解压

在我的mac中执行:

1
2
3
cd /Applications/nacos/bin						//因为我把nacos放在了/Applications目录下
sh startup.sh -m standalone //在nacos/bin目录下执行该语句即可成功启动nacos服务
sh shutdown.sh //关闭nacos

9.1.3.4 浏览器 http://localhost:8848/nacos

9.1.3.5 用户名/密码 为 nacos

9.2 创建 Nacos 服务提供者

9.2.1 需求说明/图解

image-20230604111207532

9.2.2 创建 member-service-nacos-provider-10004 并注册到 NacosServer8848

9.2.2.1 创建 member-service-nacos-provider-10004

  1. 参考member-service-provider-10000 来创建 member-service-nacos-provider-10004 即可

  2. 创建好后,使用member-service-provider-10000 的源码和配置替换member-service-nacos-provider-10004 生成的代码

  3. 提醒,拷贝时不要忘记拷贝 resources/mapper/MernberrVapper.xml 这些xeox.xrl 文件

9.2.2.2 修改父项目 pom.xml

1. 参考官方文档 https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_discovery

1
2
3
4
5
6
7
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

9.2.2.3 修改本模块 pom.xml

1.member-service-provider-10000pom.xml

拷贝过来,修改即可

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.study.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<artifactId>member-service-nacos-provider-10004</artifactId>

<dependencies>
<!--引入nacos-start nacos场景启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- 使用版本仲裁,从父项目中获取版本-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springboot程序的监控系统,实现系统健康检测
可以通过 http://localhost:10000/actuator 看到相关连接
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>

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

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

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

<!-- 引入 e_commerce_center-common-api -->
<dependency>
<groupId>com.study.springcloud</groupId>
<artifactId>e_commerce_center-common-api</artifactId>
<version>${project.version}</version>
</dependency>



</dependencies>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

</project>

9.2.2.4 创建 application.yml

1.member-service-provider-10000application.xml 拷贝过来,修改即可

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

spring:
application:
name: member-service-nacos-provider
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/e_commerce_center_db?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
username: root
password: QWEASDzxc123
# 配置nacos
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置nacos server的地址

mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.study.springcloud.entity
#配置暴露所有的监控点
management:
endpoints:
web:
exposure:
include: '*'

9.2.2.5 创建主启动类

1. 创建主启动类 MemberNacosProviderApplication10004

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.study.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

//引入nacos发现的注解
@EnableDiscoveryClient
@SpringBootApplication
public class MemberNacosProviderApplication10004 {
public static void main(String[] args) {
SpringApplication.run(MemberNacosProviderApplication10004.class,args);
}
}

9.2.2.6 为看到更好提示,修改 Controller

……

9.2.2.7 测试

9.2.2.7.1 启动 Nacos Server 8848
9.2.2.7.2 启动 member-service-nacos-provider-10004
9.2.2.7.3 观察 nacos 服务是否注册成功

image-20230604130757230

9.2.3 创建 member-service-nacos-provider-10006 并注册到 NacosServer8848

9.2.3.1 创建 member-service-nacos-provider-10006

  1. 参考member-service-nacos-provider-10004 来创建 member-service-nacos-provider-10006 即可

  2. 创建好后,使用member-service-nacos-provider-10004 的源码和配罝替换member-service-nacos-provider-10006 生成的代码

  3. 提醒,拷贝时不要忘记拷贝 resources/mapper/MemberMapper.xml 这些xoxx.xml文件

9.2.3.2 修改本模块 pom.xml

1.member-service-provider-10000pom.xml

拷贝过来,修改即可

9.2.3.3 创建 application.yml

1.member-service-nacos-provider-10004application.xml 拷贝过来,修改端口即可

9.2.3.4 创建主启动类

1. 创建主启动类 MemberNacosProviderApplication10006 (也复制过来,改个名称即可)

9.2.3.5 为看到更好提示,修改 Controller

9.2.3.6 测试

9.2.3.6.1 保证 Nacos Server 8848 是启动的
9.2.3.6.2 启动 **member-service-nacos-provider-10006 **
9.2.3.6.3 观察 nacos 服务是否注册成功

image-20230605235515301

9.2.3.6.4 浏览器**: http://localhost:10006/member/get/1**

9.3 创建 Nacos 的服务消费者

9.3.1 需求说明**/**图解

示意图

image-20230619155537025

9.3.2 创建 member-service-nacos-consumer-80 并注册到 NacosServer8848

9.3.2.1 创建 member-service-nacos-consumer-80

  1. 参考 member-service-consumer-80 来创建 member-service-nacos-consumer-80 即可

9.3.2.2 修改 pom.xml

member-service-consumer-80pom.xml 拷 贝过来,修改即可

9.3.2.3 创建 application.yml

1
2
3
4
5
6
7
8
9
10
11
server:
port: 80
spring:
application:
name: member-service-consumer-80
#配置nacos
cloud:
nacos:
discovery:
#启动nacos服务的ip以及端口
server-addr: localhost:8848

9.3.2.4 创建主启动类

……

9.3.2.5 业务类

nacos 本身就集成了 Ribbon, 直接支持 Ribbon(负载均衡)+RestTemplate(远程调用) 调用

1. 创建配置类 com/hspedu/springcloud/config/CustomizationBean.java

2. 创建 com/hspedu/springcloud/controller/MemberNacosConsumerController.java

9.3.2.6 测试

9.3.2.6.1 启动 Nacos Server 8848
9.3.2.6.2 启动 member-service-nacos-provider-10004/10006 9.3.2.6.3 启动 member-service-nacos- consumer-80
9.3.2.6.4 浏览器**: http://localhost/member/nacos/consumer/get/1**

9.3.2.7 配置自己的负载均衡算法, 测试完毕恢复成原来的轮询算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.study.springcloud.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author 浦原
* 2023/6/19
* 16:28
* @version 1.0
* RibbonRule配置类,配置自己的负载均衡算法
*/
@Configuration
public class RibbonRule {
@Bean
public IRule myRibbonRule(){
return new RandomRule();
}
}

9.4 NacosAP和CP切换-理论

9.4.1 各种注册中心对比

image-20230619224742402

9.4.2 选择 AP 还是 CP?

1CP: 服务可以不能用,但必须要保证数据的一致性。
2AP: 数据可以短暂不一致,但最终是需要一致的,无论如何都要保证服务的可用。
3、取舍:只能在 CPAP 选择一个平衡点**,** 大多数都是选择 AP 模式

9.4.3 APCP切换

Nacos 集群默认支持的是CAP原则中的AP原则,但是也可切换为CP原则**(一般不切换)**

9.4.3.2 参考: https://www.jianshu.com/p/c56e22c222bb

9.5 Nacos 配置中心实例

9.5.1 需求分析**/**图解

微服务各个模块中有许多配置是相同的,在配置中心进行统一的配置不仅配置更方便,也便于日后修改。

示意图

image-20230619225914409

9.5.2Nacos Server 加入配置

  1. 进入到 Nacos Server
  2. 加入配置, 老韩特别提醒: 文件后缀.yaml 别忘了
image-20230619230758715

Data ID: e-commerce-nacos-config-client-dev.yaml

image-20230619230855754 image-20230619230930267

9.5.3 创建 Nacos 配置客户端模块 e-commerce-nacos-config-client5000

9.5.3.1 创建 Module

先创建 e-commerce-nacos-config-client5000 模块,参考以前的方法

9.5.3.2 修改 pom.xml

1. 修改 pom.xml

1
2
3
4
5
6
在之前的消费方xml文件的基础上添加:
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

9.5.3.3 创建 application.xml

创建 application.yml

1
2
3
spring:
profiles:
active: dev #指定环境,常见的环境有 dev/test/prod 开发,测试,生产

参考*: https://blog.csdn.net/zsl131557/article/details/80886114*

9.5.3.4 创建 bootstrap.yml

创建 bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
port: 5000
spring:
application:
# 这里的name 需要参考nacos配置中心的DataId
name: e-commerce-nacos-config-client
#配置nacos
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服务注册中心地址
config:
server-addr: localhost:8848 #nacos配置中心地址
file-extension: yaml #指定读取配置中心的yaml后缀的配置

#老韩解读[重要]
#nacos 配置客户端,会根据配置,找到资源获取配置文件
#(1 config-server-addr) localhost:8848
#(2 spring.application.name/对应 DataId) e-commerce-nacos-config-client
#(3 spring.profiles.active) dev
#(4 spring.cloud.nacos.config.file-extension 文件扩展名) .yaml
#也就到 localhost:8848 下的 e-commerce-nacos-config-client-dev.yaml 获取配置信息
#规则总结: ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}

9.5.3.5 主启动类

……

9.5.3.6 业务类

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
package com.study.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author 浦原
* 2023/6/20
* 15:32
* @version 1.0
*/
@RestController
@Slf4j
public class NacosConfigClientController {
/**
* 解读 @Value("${config.ip}")
* 1. client 会拉取 Nacos Server 的 e-commerce-nacos-config-client-dev.yaml * config:
* ip: "122.11.11.11"
* name: "韩顺平教育"
* 2. @Value("${config.ip}") 会将 config.ip 赋给 configIp
*/


@Value("${config.ip}")
private String configIp;
@Value("${config.name}")
private String configName;

@GetMapping("/nacos/config/ip") public String getConfigIp() {
return configIp; }
@GetMapping("/nacos/config/name") public String getConfigName() {
return configName; }


}

9.5.3.7 测试

9.5.3.7.1 启动 **Nacos Server
9.5.3.7.2 启动 **e-commerce-nacos-config-client5000 **
9.5.3.7.3 浏览器**: http://localhost:5000/nacos/config/ip**

​ 浏览器输入**: http://localhost:5000/nacos/config/ip**

9.5.3.8 注意事项和细节

0. src\main\java\com\study\springcloud\controller\NacosConfigClientController.java 的 @Value(“${config.ip}”), 是 import org.springframework.beans.factory.annotation.Value; 而不是 lombok 包下的.

1. 配置文件 application.yml bootstrap.yml 结合会得到配置文件/资源的地址

2. 参考文档: https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html

3. 注意在 Nacos Server 的配置文件的后缀是 .yaml , 而不是 .yml

4. 在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动, 也就是说如果项目不能正确的获取到 Nacos Server 的配置数据,项目是启动不了的.[演示]

5. springboot 中配置文件的加载是存在优先级顺序的,bootstrap.yml 优先级高于 application.yml

🌟6. @RefreshScope springcloud 原生注解,实现配置信息自动刷新, 如果在 Nacos Server 修改了配置数据,Client 端就会得到最新配置

9.6 Nacos 分类配置 (实现配置隔离)

9.6.1 DataID方案

9.6.1.1 需求分析/图解

示意图

image-20230620155605713

9.6.1.2 解决方案分析

9.6.1.2.1 使用 Data ID 方案解决

9.6.1.3 配置实现

1.nacos server 创建新的配置:e-commerce-nacos-config-client-test.yaml

9.6.1.4 修改 application.yml

1
2
3
spring:
profiles:
active: test #指定环境,常见的环境有 dev/test/prod 开发,测试,生产

9.6.1.5 测试

9.6.1.5.1 浏览器**: http://localhost:5000/nacos/config/ip**

**1.**浏览器输入: http://localhost:5000/nacos/config/ip

9.6.2 Group方案

9.6.2.1 需求分析/图解

示意图

image-20230620161153853

9.6.2.2 解决方案分析

9.6.2.2.1 使用 Group 方案解决

9.6.2.3 配置实现

1.nacos server 创建新的配置:order/e-commerce-nacos-config-client-dev.yaml

2. nacos server **创建新的配置:**seckill/e-commerce-nacos-config-client-dev.yaml

image-20230620161749751

9.6.2.4 修改 application.yml

1
2
3
spring:
profiles:
active: dev #指定环境,常见的环境有 dev/test/prod 开发,测试,生产

9.6.2.5 修改 bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 20000
spring:
application:
# 这里的name 需要参考nacos配置中心的DataId
name: e-commerce-nacos-config-client
#配置nacos
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服务注册中心地址
config:
server-addr: localhost:8848 #nacos配置中心地址
file-extension: yaml #指定读取配置中心的yaml后缀的配置
group: order #指定order组,默认为DEFAULT_GROUP

9.6.2.6 测试

9.6.2.6.1 浏览器**: http://localhost:5000/nacos/config/ip**

**1.**浏览器输入: http://localhost:5000/nacos/config/ip

9.6.3 Namespace方案

9.6.3.1 需求分析/图解

示意图

image-20230620162224978

9.6.3.2 解决方案分析

9.6.3.2.1 使用 Namespace 方案解决

9.6.3.3 配置实现

1.nacos server 创建新的 namespace , baidualibaba

image-20230620162934242 image-20230620163057931

2. nacos server 创建新的 group/dataid

9.6.3.4 修改 application.yml

1
2
3
spring:
profiles:
active: dev #指定环境,常见的环境有 dev/test/prod 开发,测试,生产

9.6.3.5 修改 bootstrap.yml

1. 增加 Namespace 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 20000
spring:
application:
# 这里的name 需要参考nacos配置中心的DataId
name: e-commerce-nacos-config-client
#配置nacos
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服务注册中心地址
config:
server-addr: localhost:8848 #nacos配置中心地址
file-extension: yaml #指定读取配置中心的yaml后缀的配置
group: seckill #指定order组,默认为DEFAULT_GROUP
namespace: 8b005a7a-89b9-40ba-a5da-aac3e6c7a564

9.6.3.6 测试

9.6.3.6.1 浏览器**: http://localhost:5000/nacos/config/ip**

**1.**浏览器输入: http://localhost:5000/nacos/config/ip

9.6.4 Namespace/Group/Data ID 关系

9.6.4.1 一图胜千言

image-20230620164046357

9.6.4.2 详解介绍

1. namespace / group / data id 的关系

2.梳理:

  • Nacos默认的命名空间是public,Namespace主要用来实现配置隔离,隔离范围大

  • Group默认是DEFAULT GROUP, Group可以把不同的微服务划分到同一个分组里面去

  • Service就是微服务,相同的Service可以是一个Cluster(簇/集群), Instance就是微服务的实例

10 SpringCloud Alibaba Sentinel——分布式系统的流量哨兵

10.1 Sentinel基础

10.1.1 官网

10.1.1.1 Github: https://github.com/alibaba/Sentinel

10.1.1.2 快速开始: https://sentinelguard.io/zh-cn/docs/quick-start.html

10.1.1.3 中文: https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

10.1.1.4 使 用 手 册 https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel

10.1.2 Sentinel是什么?

10.1.2.1 Sentinel 概述

  1. Sentinel 是什么?

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

  1. Sentinel 的主要特性

image-20230620201315601

梳理: sentinel **可以完成的功能 **: 绿色方框列出的部分

  1. Sentinel 的开源生态

image-20230620201431927

10.1.2.2 一句话: Sentinel: 分布式系统的流量防卫兵, 保护你的微服务

10.1.3 Sentinel核心功能

10.1.3.1 流量控制

10.1.3.2 熔断降级

- 在调用系统的时候,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积,如下图:

image-20230620201814295

解读

熔断降级可以解决这个问题,所谓的熔断降级就是当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障

10.1.3.3 系统负载保护

​ 根据系统能够处理的请求,和允许进来的请求,来做平衡,追求的目标是在系统不被拖垮的情况下**,** 提高系统的吞吐率

10.1.3.4 消息削峰填谷

​ 某瞬时来了大流量的请求**,** 而如果此时要处理所有请求,很可能会导致系统负载过高,影响稳定性。

​ 但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没有充分利用系统处理消息的能力

- Sentinel 的Rate Limiter模式能在某一段时间间隔内以匀速方式处理这样的请求, 充分利用系统的处理能力, 也就是削峰填谷, 保证资源的稳定性

10.1.4 Sentinel两个组成部分

10.1.4.1 核心库:(Java 客户端)不依赖任何框架/库,能够运行在所有 Java运行时环境,对 Spring Cloud有较好的支持

10.1.4.2 控制台:(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器(就跟nacos、eureka那种控制台那样)

10.2 Sentinel控制台

10.2.1 需求分析**/**图解

1. 需求**:** 搭建 Sentinel 控制台,用于显示各个微服务的使用情况

10.2.2 下载

10.2.2.1 https://github.com/alibaba/Sentinel/releases/tag/v1.8.0

image-20230620203427041

10.2.3 运行

10.2.3.1 指令:

我把下载的jar包放在 /Applications/Java/jar包资源 此处了

因此在终端运行以下指令即可启动 Sentinel

1
2
3
4
cd /Applications/Java/jar包资源
java -jar sentinel-dashboard-1.8.0.jar //启动
java -jar sentinel-dashboard-1.8.0.jar --server.port=9999 //也可以自定义端口运行该进程
Ctrl+C即可关闭服务

10.2.3.2 注意: Sentinel 控制台 默认端口是 8080

10.2.4 访问

10.2.4.1 浏览器: http://localhost:8080

10.2.4.2 控制台页面

1. 浏览器输入**: http://localhost:8080 ,** 用户**/**密码都是 sentinel

image-20230620203842208 image-20230620204626829

2. 登录成功后的页面, 目前是空的,因为 sentinel 还没有进行流量监控

10.3 Sentinel监控微服务

10.3.1 需求分析**/**图解

  1. 需求**:** 使用 Sentinel 控制台对 member-service-nacos-provider-10004 微服务 进行实时监控

示意图

image-20230620204744222
  1. 当调用了member-service-nacos-provider-10004微服务时,可以监控到请求的url/QPS(每秒查询率)/响应时间/流量
image-20230620205650814

10.3.2 代码**/**配置实现

1. 修改 member-service-nacos-provider-10004pom.xml, 引入 alibaba-sentinel

1
2
3
4
5
<!--    引入alibaba-sentinel starter    -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2. 修改 member-service-nacos-provider-10004 application.yml

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

spring:
application:
name: member-service-nacos-provider
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/e_commerce_center_db?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
username: root
password: QWEASDzxc123
# 配置nacos
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置nacos server的地址
sentinel:
transport:
dashboard: localhost:8080 #这个是 sentinel 控制台(sentinel dashboard)的地址
#解读 spring.cloud.sentinel.transport.port:
#1. spring.cloud.sentinel.transport.port 端口配置会在被监控的微服务
# 对应的机器上启动一个 Http Server
#2. 该 Server 会与 Sentinel 控制台做交互
#3. 比如 Sentinel 控制台添加了 1 个限流规则,会把规则数据 push 给这个
# Http Server 接收,Http Server 再将规则注册到 微服务 中
#4. 简单的说明: spring.cloud.sentinel.transport.port:指定被监控的微服务应用与
# Sentinel 控制台交互的端口,微服务应用本地会起一个该端口占用的 Http Server
port: 8719 #默认 8719,假如被占用了, 会自动从 8719 开始依次+1 扫描。
#直至找到未被占用的端口

mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.study.springcloud.entity
#配置暴露所有的监控点
management:
endpoints:
web:
exposure:
include: '*'

3. 成功启动后, netstat -anb 可以查看到该端口.

10.3.3 测试

10.3.3.1 启动 Nacos Server 8848

10.3.3.2 启动 Sentinel8080 控制台 / Sentinel dashboard

10.3.3.3 启动 member-service-nacos-provider-10004

10.3.3.4 浏览器: localhost:10004/member/get/1

10.3.3.5 Sentinel 控制台监控页面

1. 浏览器输入**: http://localhost:10004/member/get/1**

2. 进入到 Sentinel 查看实时监控效果, http://localhost:8080/#/dashboard

image-20230620211122568

10.3.4 注意事项和细节

10.3.4.1 QPS: Queries Per Second(每秒查询率),是服务器每秒响应的查询次数

10.3.4.2 Sentinel采用的是懒加载,只有调用了某个接口/服务,才能看到监控数据

10.4 Sentinel流量控制

10.4.1 规则

1. 先看一张图

image-20230620211426555
  1. 对上图的解读
  • 资源名:唯一名称,默认请求路径

  • 针对来源:Sentine可以针对调用者进行限流,填写微服务名,默认default(不区分来源)

  • 阈值类型/单机阈值:

    • QPS(每秒钟的请求数量):当调用该 api 的 QPS 达到阈值的时候,进行限流
    • 线程数:当调用该 api 的线程数达到阈值的时候,进行限流

解读: QPS和线程数的区别,注意听,比如 QPS和线程我们都设置阈值为1

(1)对QPS而言,如果在1秒内,客户端发出了2 次请求,就达到阈值,从而限流

(2)对线程数而言,如果在1秒内,客户端发出了2 次请求,不一定达到线程限制的國值,为什么呢?假设我们1 次请求后台会创建一个线程,但是这个请求完成时间是 0.1 秒(可以视为该请求对应的线程存活 0.1 秒),所以当客户端第2 次请求时(比如客户端是在0.3秒发出的),这时第 1个请求的线程就己经结束了,因此就没有达到线程的闻值,也不会限流。

(3)小伙伴可以这样理解,如果1个请求对应的线程平均执行时间为 0.1 那么,就相当于 QPS 为 10

  • 是否集群:不需要集群

  • 流控模式:

    • 直接:api 达到限流条件时,直接限流
    • 关联:当关联的资源达到闻值时,就限流自己
    • 链路:当从某个接口过来的资源达到限流条件时,开启限流
  • 流控效果:

    • 快速失败:直接失败,抛异常
    • Warm Up:根据 code Factor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的 QPS 阈值
    • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为 QPS, 否则无效

10.4.2 流量控制实例**-QPS**

10.4.2.1 需求分析/图解

1. 需求**:** 通过 Sentinel 实现 流量控制
2. 当调用 member-service-nacos-provider-10004 /member/get/ **API **时,限制 1秒内最多访问 1 次,否则直接失败,抛异常

10.4.2.2 配置实现步骤

1. 为**/member/get/1** 增加流控规则

image-20230622221155249

image-20230622221223336

2. 在流控规则菜单,可以看到新增的流控规则

image-20230622221447805

10.4.2.3 测试

10.4.2.3.1 启动 Nacos Server 8848
10.4.2.3.2 启动 Sentinel8080 控制台**/Sentinel dashboard 10.4.2.3.3** 启动 **member-service-nacos-provider-10004 **
10.4.2.3.4 浏览器**: localhost:10004/member/get/1 **
10.4.2.3.5 Sentinel 控制台监控页面

1. 浏览器输入**: http://localhost:10004/member/get/1 , 1** 秒钟内访问次数不超过 1 次**,** 页 面显示正常

2. 浏览器输入:http://localhost:10004/member/get/1 ,1秒钟内访问次数超过1次, 页面出现错误提示

image-20230622221636917

10.4.2.4 注意事项和细节

1. 流量规则改动,实时生效,不需重启微服务 , Sentinel 控制台

​ 为什么?因为sentinel中规则一发生改动,就会将信息push到微服务主机监听sentinel服务的进程

2. 在 sentinel 配置流量规则时,如何配置通配符问题, 比如 /member/get/1 /member/get/2 统一使用一个规则
  • 方案1: 在sentinel中 /member/get?id=1 和 /member/get?id=2 被统一认为是**/member/get** 所以只要对/member/get 限流就OK了. (将要携带的参数放在载荷而不是路径中)
1
2
3
4
5
6
7
8
9
@GetMapping(value = "/member/get", params = "id")
public Result getgetMemberById(Long id){
Member member = memberService.queryMemberById(id);
if(member != null){
return Result.success("查询成功 member-service-nacos-provider-10004",member);
}else {
return Result.error("402","ID = "+ id + "不存在");
}
}
image-20230622223803411

访问:http://localhost:10004/member/get?id=3

  • 方案2: URL资源清洗
    可以通过 UrlCleaner 接口来实现资源清洗,也就是对于/member/get/{id}这个 URL,我们可以统一归集到/member/get/*资源下,具体配置代码如下,实现 UrlCleaner 接口,并重写 clean 方法即可
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
//添加代码:

package com.study.springcloud.controller;

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

/**
* @author 浦原
* 2023/6/22
* 22:41
* @version 1.0
*/
@Component
public class CustomUrlCleaner implements UrlCleaner {
@Override
public String clean(String originURL) {
//资源清洗
if(StringUtils.isBlank(originURL)){
return originURL;
}
if (originURL.startsWith("/member/get")){
// 1. 如果请求的是接口 /member/get 开头的, 比如/member/get/1
// 2. 给 sentinel 的返回的资源名就是 /member/get/*
// 3. 在 sentinel 对 /member/get/* 添加流控规则即可
return "/member/get/*";
}
//否则 返回收到的URL即可
return originURL;
}
}
3. 如果 sentinel 流控规则没有持久化,当我们重启调用 API 所在微服务模块后,规则会丢失,需要重新加入

10.4.3 流量控制实例**-**线程数

10.4.3.1 需求分析/图解

1. 需求**:** 通过 Sentinel 实现 流量控制
2. 当调用 member-service-nacos-provider-10004/member/get/* API 时,限制只有一个工作线程,否则直接失败,抛异常**.**

10.4.3.2 配置实现步骤

1. 为**/member/get/*** 增加流控规则

image-20230623143357430

2. 在流控规则菜单,可以看到新增的流控规则

10.4.3.3 测试

10.4.3.3.1 启动 Nacos Server 8848
10.4.3.3.2 启动 Sentinel8080 控制台**/Sentinel dashboard **
10.4.3.3.3 启动 **member-service-nacos-provider-10004 **
10.4.3.3.4 浏览器**: localhost:10004/member/get/1 **
10.4.3.3.5 结果页面

1. 浏览器输入**: http://localhost:10004/member/get/1 ,** 快速刷新**,** 页面显示正常**(原因是服务执行时间很短,刷新下一次的时候,启动的工作线程,已经完成)**

2. 为了看到效果,我们修改下com/hspedu/springcloud/controller/MemberController.java

1
2
3
4
5
6
//让它睡眠一下
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

3. 重启 member-service-nacos-provider-10004 , 注意需要重新加入流控规则.

4. 浏览器输入: http://localhost:10004/member/get/1 , 快速刷新, 页面出现异常

10.4.3.4 注意事项和细节

1. 当我们请求一次微服务的 API 接口时,后台会启动一个线程

image-20230623144551449

2.阈值类型 QPS 和 线程数的区别讨论

  • 如果一个线程平均执行时间为0.05秒,就说明在1秒钟,可以执行20次(相当于 QPS为20)
  • 如果一个线程平均执行时间为1秒,说明1秒钟,可以执行1次(相当于 QPS为1)
  • 如果一个线程平均执行时间为2秒,说明2秒钟内,才能执行1次请求

10.4.4 流量控制实例**-**关联

10.4.4.1 关联的含义

10.4.4.1.1 当关联的资源达到阈值时,就限流自己

10.4.4.2 需求分析/图解

1. 需求**:** 通过 Sentinel 实现 流量控制

2. 当调用 member-service-nacos-provider-10004/t2 API 接口时,如果 QPS 超过 1,这时调用 /t1 API 接口 直接失败,抛异常**.**

​ 梳理: /t2 是关联的资源 , 限流的资源是**/t1**

1
2
3
4
5
6
7
8
9
@GetMapping("/t1")
public Result t1(){
return Result.success("t1执行...");
}

@GetMapping("/t2")
public Result t2(){
return Result.success("t2执行...");
}

10.4.4.3 配置实现步骤

1. 为**/t1** 增加流控规则

image-20230623150310128

2. 在流控规则菜单,可以看到新增的流控规则

10.4.4.4 测试

10.4.4.4.1 启动 Nacos Server 8848
10.4.4.4.2 启动 Sentinel8080 控制台**/Sentinel dashboard**
10.4.4.4.3 启动 **member-service-nacos-provider-10004 **
10.4.4.4.4 「Postman模拟高并发访问**/t2**」

1. 创建新的 http request

2. 保存 request 到 一个新的 collection

3. 设置 run collection 参数, 并运行

4. 浏览器访问: http://localhost:10004/t1

10.4.4.4.5 浏览器: localhost:10004/t1

1. 浏览器访问 http://localhost:10004/t1 的结果页面

10.4.4.5 注意事项和细节

10.4.4.5.1postman 执行 高并发访问 /t2 没有结束时, 去访问 /t1 才能看到流控异常出现

10.4.5 流量控制实例**-Warm up**

10.4.5.1 Warm up 介绍

1. 概述

  • 当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长 一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的 增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动, 预热)模式就是为了实现这个目的的。
  • 这个场景主要用于启动需要额外开销的场景,例如建立数据库连接等

2. 一张图

image-20230623215536876
  • 通常冷启动的过程系统允许通过的 QPS 曲线图(上图)
  • 默认 coldFactor 3,即请求 QPS threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值
  • 这里的threshold 就是最终要达到的QPS阈值.
10.4.5.1.1 文 档 :

https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8

10.4.5.1.2 默认 coldFactor3,即请求 QPSthreshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值
10.4.5.1.3 Warm up 称为 冷启动**/**预热
10.4.5.1.4 应用场景**:** 秒杀在开启瞬间,大流量很容易造成冲垮系统,Warmup 可慢慢的把流量放入,最终将阀值增长到设置阀值

10.4.5.2 需求分析/图解

1. 需求**:** 通过 Sentinel 实现流量控制**,**演示 Warm up

2. 调用 member-service-nacos-provider-10004/t2 API 接口,将 QPS 设置为 9, 设置 Warm up 值为 3

4. 含义为 请求 /t2QPSthreshold / 3( 9 /3 = 3) 开始,经预热时长**(3** 秒**)逐渐升至 设定的 QPS 阈值(9)**

5. 为什么是 9 / 3, 这个是 3 就是默认冷启动启动加载因子 coldFactor=3

6. 测试预期效果**:** 在前 3 秒,如果访问 /t2QPS 超过 3, 会直接报错,在 3 秒后 访问 /t2QPS 超过 3, 小于等于 9, 是正常访问

10.4.5.3 配置实现步骤

1. 为**/t2** 增加流控规则

image-20230623220623200

2. 在流控规则菜单,可以看到新增的流控规则

10.4.5.4 测试

10.4.5.4.1 启动 Nacos Server 8848
10.4.5.4.2 启动 Sentinel8080 控制台**/Sentinel dashboard **
10.4.5.4.3 启动 **member-service-nacos-provider-10004 **
10.4.5.4.4 浏览器**: localhost:10004/t2**

1. 浏览器访问 http://localhost:10004/t2 快速刷新页面,在前 3 秒,会出现流控异常, 后 3 秒就正常了**(如果你刷新非常快 QPS>9 , 仍然会出现流控异常)**

10.4.5.5 注意事项和细节

10.4.5.5.1 测试 Warm up 效果不是很好测,如果出不来可以尝试,调整流控规则**:** 比如 QPS 为 **11, Warm up 预热时间 **6 秒

10.4.5.5.2 如果请求停止**(:** 一段时间没有达到阈值**), Warm up** 过程将重复**,** 小伙伴可以理解是一个弹性过程

10.4.6 流量控制实例**-**排队

10.4.6.1 排队 介绍

1. 排队方式:这种方式严格控制了请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法

2. 一张图

image-20230623221119829

3. 这种方式主要用于处理间隔性突发的流量,例如消息队列。比如这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。-类似前面说的削峰填谷

4. 匀速排队,阈值必须设置为QPS

10.4.6.2 需求分析/图解

1. 需求**:** 通过 Sentinel 实现 流量控制**-排队
2. 调用 member-service-nacos-provider-10004/t2 API 接口,将 QPS 设置为 1 **
3. 当调用 /t2QPS 超过 1 时,不拒绝请求,而是排队等待
,
依次执行
4.当等待时间超过 10 秒,则为等待超时——等待超时就不要了

10.4.6.3 修改业务类

1. 为了测试看到效果,修改 com/hspedu/springcloud/controller/MemberController.java

1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/t2")
public Result t2(){
//模拟线程执行时间为1秒
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("执行 t2() 线程 id= " + Thread.currentThread().getId());
return Result.success("t2执行...");
}

10.4.6.4 配置实现步骤

1. 为**/t2** 增加流控规则

image-20230623222712777

2. 在流控规则菜单,可以看到新增的流控规则

10.4.6.5 测试

10.4.6.5.1 启动 Nacos Server 8848
10.4.6.5.2 启动 Sentinel8080 控制台**/Sentinel dashboard **
10.4.6.5.3 启动 **member-service-nacos-provider-10004 **
10.4.6.5.4 浏览器**: localhost:10004/t2**

1. 浏览器访问 http://localhost:10004/t2 快速刷新页面 9 次,观察前台**/**后台输出的情况

输出结果分析:没有报错误,后台请求排队执行,每隔1s匀速执行

2. 浏览器访问 http://localhost:10004/t2 快速刷新页面20次,当请求等待时间超过10S, 仍然出现流控异常

10.5 Sentinel熔断降级

10.5.1 线程堆积引出熔断降级

1. 一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。

2. 例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需 要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现 了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用

3. 这时,我们对不稳定的服务进行熔断降级,让其快速返回结果,不要造成线程堆积

10.5.2 文档地址**: https://sentinelguard.io/zh-cn/docs/circuit-breaking.html**

10.5.3 基本介绍

1. 一张图

image-20230623223928543

2. 解读上图:

  • 现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。
  • 链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。
  • 因此需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩

3. 熔断,降级,限流三者的关系

  • 熔断:强调的是服务之间的调用能实现自我恢复的状态

  • 限流:是从系统的流量入口考虑, 从进入的流量上进行限制**, **达到保护系统的作用

  • 降级:是从系统业务的维度考虑,流量大了或者频繁异常, 可以牺牲一些非核心业务,保护核心流程正常使用

-熔断是降级方式的一种

-降级又是限流的一种方式

-三者都是为了通过一定的方式在流量过大或者出现异常时,保护系统的手段

10.5.4 熔断策略

10.5.4.1 慢调用比例

1、慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用

2、当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断

3、熔断时长后,熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT 则会再次被熔断

4、配置参考

image-20230623225930567

10.5.4.2 异常比例

1、异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断

2、经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN状态)

3、若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断

4、异常比率的阈值范围是[0.0, 1.0],代表0% - 100%

5、配置参考

image-20230623230152696

6、工作示意图

image-20230623230238823

10.5.4.3 异常数

1、异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断

2、经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态)

3、若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断

4、配置参考

image-20230623230424805

10.5.5 熔断降级实例**-**慢调用比例

10.5.5.1 需求分析/图解

1. 需求**:** 通过 Sentinel 实现 熔断降级控制**-**慢调用比例

2. 当调用 member-service-nacos-provider-10004 /t3 API 接口时,如果在 1s 内持续进入了 5 个请求,并且请求的平均响应时间超过 200ms, 那么就在未来 10 秒钟内,断路器打开, /t3 API 接口微服务不可用

3. 后面对/t3 API 接口访问降到 1S 1 个请求,降低访问量了,断路器关闭,微服务恢复

10.5.5.2 修改业务类

1. 修改 com/hspedu/springcloud/controller/MemberController.java 增加方法 t3()

1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/t3")
public Result t3(){
//模拟线程执行时间为1秒
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("熔断降级测试 执行 t3() 线程 id= " + Thread.currentThread().getId());
return Result.success("t3执行...");
}

10.5.5.3 配置实现步骤

1. 为**/t3** 增加降级规则

image-20230623231431243

2. 在流控规则菜单,可以看到新增的降级规则

10.5.5.4 测试

10.5.5.4.1 启动 Nacos Server 8848
10.5.5.4.2 启动 Sentinel8080 控制台**/Sentinel dashboard **
10.5.5.4.3 启动 member-service-nacos-provider-10004
10.5.5.4.4 Postman测试

1. 先创建 collection , 也可以在已经存在的 collection 进行修改

…………

10.5.5.5 注意事项和细节

10.5.5.5.1 平均响应时间超出阈值且在 1s 内通过的请求**>=5**, 两个条件同时满足后触发降级
10.5.5.5.2 熔断时间过后,关闭断路器,访问恢复正常

10.5.6 熔断降级实例-异常比例

10.5.6.1 需求分析/图解

1. 需求**:** 通过 Sentinel 实现 熔断降级控制

2. 当调用 member-service-nacos-provider-10004/t4 API 接口时,当资源的每秒请求量**>=5,并且每秒异常总数占通过量的比值超过20%(即异常比例到20%),** 断路器打开**(:** 进入降级状态**),** 让 /t4 API 接口微服务不可用

3. 当对**/t4 API** 接口 访问降到 1S1 个请求,降低访问量了,断路器关闭,5 秒后微服务恢复

10.5.6.2 修改业务类

1. 修改 com/hspedu/springcloud/controller/MemberController.java 增加方法 t4()

1
2
3
4
5
6
7
8
9
private static int num = 0; 
@GetMapping(value = "/t4")
public Result t4() {
if ((++num) % 2 == 0) {
//异常比例 50% System.out.println(3 / 0);
}
log.info("熔断降级测试【异常比例】 执行 t4() 线程 id= " + Thread.currentThread().getId());
return Result.success("t4()执行");
}

10.5.6.3 配置实现步骤

1. 为**/t4** 增加降级规则

image-20230624094738411

2. 在流控规则菜单,可以看到新增的降级规则

10.5.6.4 测试

10.5.6.4.1 启动 Nacos Server 8848
10.5.6.4.2 启动 Sentinel8080 控制台/Sentinel dashboard
10.5.6.4.3 启动 member-service-nacos-provider-10004
10.5.6.4.4 Postman测试

1. 先创建给 collection , 也可以在已经存在的 collection 进行修改**,** 一定确保更新成功**.**

2. 点击 Run sentinel

3. 浏览器访问: http://localhost:10004/t4 出现 Blocked by Sentinel (flow limiting)

4. 停止 Postman

5. 浏览器访问: http://localhost:10004/t4 , 结果正常了(一次返回异常,一次返回正确结 果)

10.5.7.5注意事项和细节

10.5.7.5.1 资源在 1 分钟的异常数目超过阈值之后会进行熔断降级

10.5.7.5.2 异常数统计是分钟级别的,若 设置的时间窗口 小于 60s,则结束熔断状态后仍可能再进入熔断状态**,** 测试时,最好将时间窗口设置超过 60S

10.6 Sentinel 热点规则

10.6.1 一个问题引出热点 key 限流

1. 热点**:** 热点即经常访问的数据。很多时候我们希望统计热点数据中**,** 访问频次最高的 Top K 数据,并对其访问进行限制。

2. 比如某条新闻上热搜 ,在某段时间内高频访问, 为了防止系统雪崩**,** 可以对该条新闻进行热点限流

10.6.2文档地址 :

https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81

10.6.3 基本介绍

1. 一张图image-20230624102921499

2. 解读上图:

  • 热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。

  • 热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效

  • Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控 https://blog.csdn.net/qq_34416331/article/details/106668747

  • 热点参数限流支持集群模式

10.6.4 热点 Key 限流**-**实例

10.6.4.1 需求分析/图解

1. 需求**:** 通过 Sentinel 实现热点 Key 限流
2.member-service-nacos-provider-10004/news?id=x&type=x API 接口进行热点限流
3. 假定 id=10 这一条新闻是当前的热点新闻, 当查询新闻时,对通常的 id(非热点新闻)请求 QPS 限定为 2, 如果 id=10 QPS 限定为 100
4.
如果访问超出了规定的 QPS, 触发热点限流机制
,
调用自定义的方法,给出提示信息**. **

5. 当对 /news?id=x&type=x API 接口降低访问量,QPS 达到规定范围**,** 服务恢复

10.6.4.2 修改业务类

1. 修 改 com/hspedu/springcloud/controller/MemberController.java 增 加 方 法 queryNews()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 注意 @GetMapping 是 url 带有 / , @SentinelResource 的 value 是没有 /的
* * @param id
* * @param type
* * @return
*/
@GetMapping("/news")
@SentinelResource(value = "news",blockHandler = "newsBlockHandler")
public Result queryNews(@RequestParam(value = "id",required = false) String id,
@RequestParam(value = "type",required = false) String type)
{
log.info("到 DB 查询数据库.... ");
return Result.success("返回 id= " + id + " 新闻 fromDB"); }
//热点 key 限制异常处理方法
public Result newsBlockHandler(String id, String type, BlockException exception) {
return Result.success("查询 id= " + id
+ " 新闻 触发热点 key 限制保护.. sorry");
}

10.6.4.3 测试

10.6.4.3.1 启动 Nacos Server 8848
10.6.4.3.2 启动 Sentinel8080 控制台/Sentinel dashboard
10.6.4.3.3 启动 member-service-nacos-provider-10004
10.6.4.3.4 配置实现步骤

1. 为资源 news 增加热点规则**,** 注意不是 /news

image-20230624104344763

image-20230624104402958

2. 在热点参数限流规则菜单,可以看到新增规则

10.6.4.3.5 浏览器**: http://localhost:10004/news?id=1&type=%E6%95%99%E8%82%B2**

如果QPS没有超过2,则返回正确结果

如果QPS超过2,则返回热点key处理信息

10.6.4.3.6 独立设置热点 id=10 的 QPS 阈值(即添加例外)

1. 独立设置热点 id=10QPS 阈值**(即添加例外)**

image-20230624104744651
10.6.4.3.7 浏览器: http://localhost:10004/news?id=10&type=%E6%95%99%E8%82%B2

1. 浏览器输入: http://localhost:10004/news?id=10&type=%E6%95%99%E8%82%B2 , 如果QPS没有超过 100,则返回正确结果

2. 浏览器访问的 id 不是10 的,仍然遵守 QPS 不能超过 2 的热点限制

10.6.5 注意事项和细节

10.6.5.1 热点参数类型是(byte/int/long/float/double/char/String)

10.6.5.2 热点参数值,可以配置多个

10.6.5.3 热点规则只对指定的参数生效 (比如本实例对 id 生效, 对 type 不生效)

10.7 系统规则

10.7.1 一个问题引出系统规则

1.如我们系统最大性能能抗 100QPS,如何分配 /t1 /t2 的 QPS?

方案1: /t1 分配 QPS=50;/t2 分配 QPS=50,问题,如果/t1 当前 QPS 达到 50,而/t2 的 QPS 才 10,会造成没有充分利用服务器性能

方案2: /t1 分配 QPS=100 /t2 分配 QPS=100,问题,容易造成系统没有流量保护造成请求线程堆积,形成雪崩

有没有对各个资源请求的QPS弹性设置,只要总数不超过系统最大QPS的流量保护规则?=> 系统规则

10.7.2文 档 地 址 :

https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81

10.7.3 一句话**:** 系统规则作用**,** 在系统稳定的前提下,保持系统的吞吐量

10.7.4 基本介绍

1. 一张图image-20230624105741809

**2.**解读上图:

系统处理请求的过程想象为一个水管,到来的请求是往这个水管灌水,当系统处理顺畅的时候,请求不需要排队,直接从水管中穿过,这个请求的RT是最短的,反之,当请求堆积的时候,那么处理请求的时间则会变为:排队时间 + 最短处理时间

**3.**系统规则(参考以下五种数据来判断系统是否超负荷)

  • Load 自适应(仅对 Linux/Unix-like 机器生效): 系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores *2.5

  • CPU usage (1.5.0+ 版本): 当系统 CPU 使用率超过阈值即触发系统保护 (取值范围0.0-1.0),比较灵敏。

  • 平均 RT: 当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。

  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。

  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

10.7.5 实例

10.7.5.1 需求分析/图解

1.需求: 通过 Sentinel 实现 系统规则**-**入口 QPS

2.member-service-nacos-provider-10004 的 所有 API 接口进行流量保护,不管访问哪个 API 接口, 系统入口总的 QPS 不能大于 2, 大于 2,就进行限流控制

3. 提示**:** 上面的 QPS 是为了方便看效果**,** 设置的很小

10.7.5.2 配置实现步骤

1. 增加入口 QPS 系统规则

image-20230624110601568

10.7.5.3 测试

10.7.5.3.1 启动 Nacos Server 8848
10.7.5.3.2 启动 Sentinel8080 控制台/Sentinel dashboard
10.7.5.3.3 启动 member-service-nacos-provider-10004
10.7.5.3.4 浏览器: http://localhost:10004/t1

1. 浏览器输入**: http://localhost:10004/t1 ,** 如果 QPS 超过 2, 打开断路器,返回流控信息

2. 浏览器输入: http://localhost:10004/news?id=1&type=%E6%95%99%E8%82%B2 , 如果QPS 超过2, 打开断路器,返回流控信息(说明: 项目的 /t2 资源对应方法有休眠代码,所以使用 /news?id=x&type=x 测试)

10.8 @SentinelResource

10.8.1 自定义全局限流处理类

10.8.1.1 需求分析/图解

1. 先看前面的一段代码

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/news")
@SentinelResource(value = "news",blockHandler = "newsBlockHandler")
public Result queryNews(@RequestParam(value = "id",required = false) String id,
@RequestParam(value = "type",required = false) String type)
{
log.info("到 DB 查询数据库.... ");
return Result.success("返回 id= " + id + " 新闻 fromDB"); }
//热点 key 限制异常处理方法
public Result newsBlockHandler(String id, String type, BlockException exception) {
return Result.success("查询 id= " + id
+ " 新闻 触发热点 key 限制保护.. sorry");
}

说明: 当配置的资源名 news 触发限流机制时,会调用 newsBlockHandler 方法

2. 上面的处理方案存在一些问题

  • 每个@SentinelResource 对应一个异常处理方法,会造成方法很多

  • 异常处理方法和资源请求方法在一起,不利于业务逻辑的分离

  • 解决方案-> 自定义全局限流处理类

3. 需求: 请编写一个自定义全局限流处理类,完成对异常处理.

10.8.1.2 代码实现

1. 修改 com/hspedu/springcloud/controller/MemberController.java 增加方法 t6()

1
2
3
4
5
6
7
8
9
10
11
/**
* 解读:
* value = "t6": SentinelResource 资源名
* blockHandlerClass = CustomGlobalBlockHandler.class: 全局限流处理类 * blockHandler = "handlerMethod1": 全局限流处理类的哪个方法,可以指定. */
@GetMapping(value = "/t6")
@SentinelResource(
value = "t6",
blockHandlerClass = CustomGlobalBlockHandler.class, blockHandler = "handlerMethod1")
public Result t6() {
log.info("执行 t6() 线程 id= " + Thread.currentThread().getId()); return Result.success("200", "t6()执行成功");
}

2. 创建 com/hspedu/springcloud/handler/CustomGlobalBlockHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.study.springcloud.handler;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.study.springcloud.entity.Result;

/**
* @author 浦原
* 2023/6/24
* 13:54
* @version 1.0
* 创建的异常处理方法需要是 static
*/
public class CustomGlobalBlockHandler {
public static Result handlerMethod1 (BlockException exception){
return Result.error("400", "客户自定义异常处理 handlerm1()");
}
public static Result handlerMethod2 (BlockException exception){
return Result.error("401", "客户自定义异常处理 handlerm2()"); }
}

10.8.1.3 配置实现步骤

1. 为资源 /t6 增加流控规则**,**方便测试

image-20230624135913223

2. 在流控规则菜单,可以看到新增规则

10.8.1.4 测试

10.8.1.4.1 启动 Nacos Server 8848
10.8.1.4.2 启动 Sentinel8080控制台/Sentinel dashboard
10.8.1.4.3 启动 member-service-nacos-provider-10004
10.8.1.4.4 浏览器: http://localhost:10004/t6

1. 浏览器输入**: http://localhost:10004/t6 ,** 如果 QPS 没有超过 1, 返回正常结果

2. 浏览器输入: http://localhost:10004/t6 ,如果QPS 超过 1, 断路器打开,返回自定义限流处理方法信息

image-20230624140131429

10.8.2 fallback 自定义程序错误处理

10.8.2.1 看一段代码-引出 fallback

1. 修 改 member-service-nacos-provider-10004com/hspedu/springcloud/controller/MemberController.java 增加一段代码**.**

1
2
3
4
5
6
7
8
9
10
11
@GetMapping(value = "/t6")
@SentinelResource(
value = "t6",
blockHandlerClass = CustomGlobalBlockHandler.class, blockHandler = "handlerMethod1")
public Result t6() {
//假定: 当访问 t6 资源次数是 5 的倍数时,就出现了一个 java 的异常
if (++num % 5 == 0) {
throw new RuntimeException("num 的值异常 num= " + num); }
log.info("执行 t6() 线程 id= " + Thread.currentThread().getId());
return Result.success("200", "t6()执行成功");
}

2. 浏览器: http://localhost:10004/t6 , 看效果当num为 5的整数时,返回的是error页面,不友好.

3. 怎么解决=> 使用 fallback

10.8.2.2 基本介绍

10.8.2.2.1 blockHandler只负责sentinel控制台配置违规
10.8.2.2.2 fallback负责Java异常/业务异常

10.8.2.3 需求分析/图解

1. 需求**:** 请编写一个自定义全局 fallback 处理类**,** 处理 java 异常**/**业务异常

2. 也就是解决前面我们提出的问题

10.8.2.4 代码实现

1.member-service-nacos-provider-10004 创建com/hspedu/springcloud/handler/CustomGlobalFallbackHandler.java

1
2
3
4
5
6
7
8
public class CustomGlobalFallbackHandler {
public static Result fallBackHandlerMethod1(Throwable e) {
return Result.error("400", "java 异常信息= " + e.getMessage());
}
public static Result fallBackHandlerMethod2(Throwable e) {
return Result.error("401", "java 异常信息= " + e.getMessage());
}
}

2.在 member-service-nacos-provider-10004修改com/hspedu/springcloud/controller/MemberController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 解读
* value = "t6": SentinelResource 资源名
* blockHandlerClass = CustomGlobalBlockHandler.class: 全局限流处理类 * blockHandler = "handlerMethod1": 全局限流处理类的哪个方法,可以指定. */
@GetMapping(value = "/t6")
@SentinelResource(
value = "t6",
fallbackClass = CustomGlobalFallbackHandler.class,
fallback = "fallBackHandlerMethod1",
blockHandlerClass = CustomGlobalBlockHandler.class,
blockHandler = "handlerMethod1")
public Result t6() {
//假定: 当访问 t6 资源次数是 5 的倍数时,就出现了一个 java 的异常
if (++num % 5 == 0) {
throw new RuntimeException("num 的值异常 num= " + num); }
log.info("执行 t6() 线程 id= " + Thread.currentThread().getId());
return Result.success("200", "t6()执行成功");
}

10.8.2.5 测试

10.8.2.5.1 启动 Nacos Server 8848
10.8.2.5.2 启动 Sentinel8080 控制台/Sentinel dashboard
10.8.2.5.3 启动 member-service-nacos-provider-10004
10.8.2.5.4 浏览器: http://localhost:10004/t6

1. 浏览器输入**: http://localhost:10004/t6 ,** 访问次数不是 5 的倍数**,** 返回正常结果

2. 浏览器输入: http://localhost:10004/t6 , 访问次数是 5 的倍数, 返回 fallback指定方法信息

3. 为资源 /t6 增加流控规则,方便测试

image-20230624142029799

4. 在流控规则菜单,可以看到新增规则

5. 浏览器输入: http://localhost:10004/t6 , 如果访问 QPS 大于1 , 由blockHandler指定的方法处理,访问次数是 5 的倍数, 由fallback指定方法处理,其它情况返回正常的结果.

10.8.3 exceptionsToIgnore 希望忽略某个异常

10.8.3.1 如果希望忽略某个异常,可以使用 exceptionsToIgnore

10.8.3.2 应用实例

1. 如果希望忽略某个异常**(支持数组)**,可以使用 exceptionsToIgnore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@GetMapping(value = "/t6")
@SentinelResource(
value = "t6",
fallbackClass = CustomGlobalFallbackHandler.class,
fallback = "fallBackHandlerMethod1",
blockHandlerClass = CustomGlobalBlockHandler.class,
blockHandler = "handlerMethod1",
exceptionsToIgnore = {RuntimeException.class}
)
public Result t6() {
//假定: 当访问 t6 资源次数是 5 的倍数时,就出现了一个 java 的异常
if (++num % 5 == 0) {
throw new RuntimeException("num 的值异常 num= " + num);
}
log.info("执行 t6() 线程 id= " + Thread.currentThread().getId());
return Result.success("200", "t6()执行成功");
}

2. 浏览器输入: http://localhost:10004/t6 , 你会发现访问次数为 5 的倍数时,不再调用 fallback 指定方法处理

​ 而是使用RuntimeException.class中的方式(即默认方式)处理

10.8.4 接入 Sentinel 的方式

即(不进入sentinel控制台网页而使用sentinel)

10.8.4.1 代码方式(硬编码,侵入性强, 不推荐)

1. 文档地址**:** https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

2. 基本使用

image-20230629202935657

image-20230629203105770

image-20230629203336858

10.8.4.2 注解方式(低侵入性, 前面用过, 推荐)

1. 注解方式埋点不支持 private 方法

https://xue.baidu.com/okam/pages/strategy-tp/index?strategyId=136707206360879&source=natural

2. @SentinelResource 用于定义资源,并提供可选的异常处理和fallback配置项。

3. @SentinelResource 注解包含以下属性(我们再梳理一下)

  • value:资源名称,必需项(不能为空)

  • entryType:entry 类型,可选项(默认为EntryType.OUT)

  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项blockHandler函数访问范围需要是public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其 他类的函数,则可以指定blockHandlerClass为对应的类的Class对象,注意对应的函数必需为 static 函数,否则无法解析。

  • fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:

  1. 返回值类型必须与原函数返回值类型一致;
  2. 方法参数列表需要和原函数一致,或者可以额外多一个Throwable类型的参数用于接收对应的异常。
  3. fallback函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定fallbackClass为对应的类的Class对象,注意对应的函数必需为static函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback函数名称,可选项,通常用于通用的fallback逻辑(即可以用于很多服务或方法)。默认fallback函数可以针对所有类型 的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了fallback和defaultFallback,则只有fallback会生效。defaultFallback 函数签名要求:
  1. 返回值类型必须与原函数返回值类型一致;
  2. 方法参数列表需要为空,或者可以额外多一个Throwable类型的参数用于接收对应的异常。
  3. defaultFallback函数默认需要和原方法在同一个类中。若希望使用其他类的函数, 则可以指定fallbackClass为对应的类的Class对象,注意对应的函数必需为static函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会进入异常统计中,也不会进入fallback逻辑中,而是会原样抛出。

10.9 openFeign+sentinel 对远程调用熔断降级

10.9.1 当前微服务基础环境

10.9.1.1 当前微服务环境架构图

– 示意图

image-20230629205804092

10.9.1.2 测试

10.9.1.2.1 启动 Nacos Server 8848
10.9.1.2.2 启动 member-service-nacos-provider-10004/10006
10.9.1.2.3 启动 member-service-nacos-consumer-80
10.9.1.2.4 浏览器**: http://localhost/member/nacos/consumer/get/1**

1. 浏 览 器 输 入 : http://localhost/member/nacos/consumer/get/1 , 目 前 是 Ribbon+RestTemplate

10.9.2 服务消费者整合 Openfeign

1. 需求:在 member-service-nacos-consumer-80 整合 Openfeign 实现远程调用

image-20230629210226986

10.9.2.2 代码+配置实现步骤

1. 修改 member-service-nacos-consumer-80pom.xml 加入 openfeign 依赖

1
2
3
4
5
<!--    引入openfeign-start,即场景启动器    -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2. member-service-nacos-consumer-80 创 建com/study/springcloud/service/MemberOpenFeignService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.study.springcloud.service;

import com.study.springcloud.entity.Member;
import com.study.springcloud.entity.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
* @author 浦原
* 2023/6/29
* 21:07
* @version 1.0
*/
@Component
@FeignClient(value = "member-service-nacos-provider")
public interface MemberOpenFeignService {
/**
* 老韩解读
* 1. 远程调用的方式为 get
* 2. 远程调用的 url 为 http://member-service-nacos-provider/member/get/{id}
* 3. member-service-nacos-provider 是 nacos 注册中心服务 member-service-nacos-provider:10004/10006
* 4. 会根据 OpenFeign 的均衡算法来决定是调用 10004 还是 10006
*/
@GetMapping(value = "/member/get/{id}")
public Result<Member> getMembertById(@PathVariable("id") Long id);
}

3. member-service-nacos-consumer-80 修改 com/study/springcloud/controller/MemberNacosConsumerController.java 增加方法getMemberOpenfeignById()

1
2
3
4
5
6
@GetMapping("/member/openfeign/consumer/get/{id}")
public Result<Member> getMemberOpenfeignById(@PathVariable("id") Long id) {
//这里使用 Openfeign 接口方式远程调用服务
System.out.println("通过 openfeignd+负载均衡 调用服务");
return memberOpenFeignService.getMemberById(id);
}

4. 再在 member-service-nacos-consumer-80 的主启动类加入注解 com/hspedu/springcloud/MemberNacosConsumerApplication80.java

「@EnableFeignClients」

10.9.2.3 测试

10.9.2.3.1 启动 Nacos Server 8848
10.9.2.3.2 启动 **member-service-nacos-provider-10004/10006 **
10.9.2.3.3 启动 member-service-nacos-consumer-80
10.9.2.3.4 浏览器**: http://localhost/member/openfeign/consumer/get/1**

1. 浏览器输入**: http://localhost/member/openfeign/consumer/get/1 , 目前是 Openfeign 调用(负载均衡)**

10.9.3 服务消费者整合 Sentinel

10.9.3.1 需求分析/图解

1. 需求:在 member-service-nacos-consumer-80 整合 Sentinel 能被 Sentinel 监控

image-20230630165412038

10.9.3.2 代码+配置实现步骤

1. 修改 member-service-nacos-consumer-80pom.xml 加入 sentinel 依赖

1
2
3
4
5
<!-- 引入 alibaba-sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2. 修改 member-service-nacos-consumer-80application.yml 配置 sentinel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 80
spring:
application:
name: member-service-consumer-80
#配置nacos
cloud:
nacos:
discovery:
#启动nacos服务的ip以及端口
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 #这个是 sentinel 控制台(sentinel dashboard)的地址
port: 8719 #默认 8719,假如被占用了, 会自动从 8719 开始依次+1 扫描。直至找到未被占用的端口
management: #暴露所有监控点
endpoints:
web:
exposure:
include: '*'

10.9.3.3 测试

10.9.3.3.1 启动 Nacos Server 8848
10.9.3.3.2 启动 Sentinel8080 控制台/Sentinel dashboard
10.9.3.3.3 启动 **member-service-nacos-provider-10004/10006 **
10.9.3.3.4 启动 member-service-nacos-consumer-80
10.9.3.3.5 浏览器**: http://localhost/member/openfeign/consumer/get/1**

1. 浏览器输入**: http://localhost/member/openfeign/consumer/get/1 ,** 目前是 Openfeign 调用**(负载均衡)**

2. 登录 sentinel 控制台: 可以看到已经监控到 member-service-nacos-consumer

image-20230630170804677

10.9.4 openFeign+sentinel 对远程调用熔断降级

10.9.4.1 需求分析/图解

1. 需求**/如图:在 member-service-nacos-consumer-80 调用某个无效服务时,启动 Sentinel 的熔断降级机制 , 能够快速返回响应,而不是使用默认的超时机制(因为超时机制容易线程堆积,** 从而导致雪崩)

image-20230630170956206

2. 先测试一下,关闭 10004/10006, 这时 openfeign 去调用会怎么样? (返回 time out)

3. 还可以测试一下,让 10004 服务对应的 API 执行时间很长(比如休眠 2 ), 这 时 openfeign 去调用会怎么样?

image-20230630171235813

10.9.4.2 代码+配置实现步骤

1. 修 改 member-service-nacos-consumer-80

com/hspedu/springcloud/service/MemberOpenFeignService.java, 加入 fallback 的处理类

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
package com.study.springcloud.service;

import com.study.springcloud.entity.Member;
import com.study.springcloud.entity.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
* @author 浦原
* 2023/6/29
* 21:07
* @version 1.0
*/
@Component
@FeignClient(value = "member-service-nacos-provider")
public interface MemberOpenFeignService {
/**
* 老韩解读
* 1. 远程调用的方式为 get
* 2. 远程调用的 url 为 http://member-service-nacos-provider/member/get/{id}
* 3. member-service-nacos-provider 是
* nacos 注册中心服务 member-service-nacos-provider:10004/10006
* 4. 会根据 OpenFeign 的均衡算法来决定是调用 10004 还是 10006
*/
@GetMapping(value = "/member/get/{id}")
public Result<Member> getMemberById(@PathVariable("id") Long id);
}

2. 创建 com/hspedu/springcloud/service/MemberFeignFallbackService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.study.springcloud.service;

import com.study.springcloud.entity.Member;
import com.study.springcloud.entity.Result;
import org.springframework.stereotype.Component;

/**
* @author 浦原
* 2023/6/30
* 17:15
* @version 1.0
*/
@Component
public class MemberFeignFallbackService implements MemberOpenFeignService {
@Override
public Result<Member> getMemberById(Long id) {
return Result.error("500", "被调用服务异常, 熔断降级,快速返回结果, 防止线程堆 积");
}
}

3. 修改 member-service-nacos-consumer-80application.yml , 加入 openfeignsentinel 整合配置

1
2
3
4
5
末尾添加以下:
#openfeign 和 sentinel 整合,必须配
feign:
sentinel:
enabled: true

10.9.4.3 测试

10.9.4.3.1 启动 Nacos Server 8848
10.9.4.3.2 启动 Sentinel8080 控制台/Sentinel dashboard
10.9.4.3.3 关闭 **member-service-nacos-provider-10004/10006 **
10.9.4.3.4 启动 member-service-nacos-consumer-80
10.9.4.3.5 浏览器**: http://localhost/member/openfeign/consumer/get/1**

1. 浏览器输入**: http://localhost/member/openfeign/consumer/get/1 ,** 目前是 Openfeign 调用**(负载均衡)**

image-20230630172238879

10.9.4.4 注意事项和细节说明

1. 因为 member-service-nacos-consumer-80 已经被 sentinel 监控,所以我们可以加入相

关的流控规则**,** 比如为 /member/openfeign/consumer/get/1 加入流控规则 qps = 1

测试: 如果/member/openfeign/consumer/get/1 请求 QPS 超过 1, 会输出Blocked by Sentinel (flow limiting)

QPS 没有超过 1, 会被 fallback 处理

2. 如果远程服务恢复正常, 又会正常调用.

10.10 规则持久化

10.10.1 规则没有持久化的问题

10.10.1.1 如果 sentinel 流控规则没有持久化,当重启调用 API/接口 所在微服务后,规则就会丢失,需要 重新加入

10.10.1.2 解决方案:通过 Nacos 进行持久化

10.10.2 规则持久化方案

10.10.2.1 阿里云 Ahas[最方便/付费]

1. 官方文档 : https://help.aliyun.com/product/87450.html?spm=5176.cnahas.0.0.78034bb7ef0y86

image-20230630190919413

10.10.2.2 🌟在 Nacos Server 配置规则, 完成持久化 -官方推荐

10.10.2.3 将规则持久化到本地文件, 定时同步

10.10.2.4 其它…

10.10.3 Nacos Server 配置中心**-**规则持久化实例

10.10.3.1 工作原理示意图

其实就是靠把规则保存到nacos中,下次重启服务时再从nacos中获取先前设置的规则信息

image-20230630191350485

10.10.3.2 需求分析/图解

1. 需 求 :member-service-nacos-consumer-80 微服务的**/member/openfeign/consumer/get/1 API** 接口添加流控规则 **QPS=1/**快速失败 .

2. 要求将该流控规则加入到 nacos server 配置中心,实现持久化

10.10.3.3 代码+配置实现步骤

1.Nacos Server 配置中心增加 Sentinel 客户端**/**微服务模块的流控规则

image-20230630191715975
1
2
3
4
5
6
7
8
9
10
11
12
[
{

"resource":"/member/openfeign/consumer/get/1",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]

image-20230630192956899

2. Nacos Server 配置中心增加 Sentinel 客户端/微服务模块的流控规则参数说明

  • resource ∶ 资源名称;
  • limlitApp ∶ 来源应用;
  • **grade **∶ 阈值类型,0 表示线程数,1 表示QPS;
  • count ∶ 单机阈值;
  • **strategy **∶ 流控模式,0 表示直接,1 表示关联,2 表示链路;
  • **controlBehavior **∶ 流控效果,0 表示快速失败,1 表示Warm Up,2 表示排队等待;
  • **clusterMode **∶ 是否集群

3. 修改 member-service-nacos-consumer-80 pom.xml, 加入 sentinel nacos 持久化整合依赖

1
2
3
4
5
<!-- 加入 sentinel 和 nacos 持久化整合依赖 --> 
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

4. 修改 member-service-nacos-consumer-80的application.yml , 配置该微服务从Nacos Server获取流控规则

1
2
3
4
5
6
7
8
9
10
11
12
13
# 在原本的基础上添加以下代码:
spring:
cloud:
sentinel:
# 配置该微服务从 Nacos Server 获取流控规则
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: member-service-nacos-consumer
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow

10.10.3.4 测试

10.10.3.4.1 启动 Nacos Server 8848
10.10.3.4.2 启动 Sentinel8080 控制台/Sentinel dashboard
10.10.3.4.3 启动 **member-service-nacos-provider-10004/10006 **
10.10.3.4.4 启动 member-service-nacos-consumer-80
10.10.3.4.5 浏览器**: http://localhost/member/openfeign/consumer/get/1**

1. 浏览器输入**: http://localhost/member/openfeign/consumer/get/1 ,** 目前是 Openfeign 调用**(负载均衡),** 而且流控规则已经生效了.

2. 查看 Sentinel 控制台,发现已经同步了流控规则

10.10.3.5 注意事项和细节

1.nacos server 配置 sentinel 流控规则的 Data ID 也可以自己指定,比如写成 hsp-id, 只要在 **sentinel client/**微服务 的 applicaion.ymldatasource.ds1.nacos.dataId 的值保持一致即可

2. 如图所示

image-20230630194533516

11 SpringCloud Alibaba Seata全局事务

全局事务——保证一个牵扯修改多个数据库的操作由于意外情况导致数据库中数据不一致的情况

11.1 Seata基础

11.1.1 先看一个问题,引出 Seata

1. 单机单库**(多表)**处理事务示意图

image-20230630203651434

2. 分布式微服务架构下的数据库事务示意图(RPC,远程过程调用)

image-20230630204333902

3. 梳理上图

用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持∶

  • 仓储服务∶对给定的商品扣除仓库/商品数量
  • 订单服务;根据采购需求创建订单
  • 帐户服务∶从用户帐户中扣除余额

4. 问题分析

  • 单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源

  • 业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证

  • 但是全局的数据—致性问题没法保证

  • 简单的说: 一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题—–

    ——->seata

11.1.2 分布式事务问题**&**解决方案

11.1.2.1 分布式微服务架构下的全局数据一致性问题 [即: 分布式事务问题]

11.1.2.2 解决方案: Seata

11.1.3 官网

11.1.3.1 http://seata.io/zh-cn/

11.1.3.2 使用手册: https://seata.io/zh-cn/docs/overview/what-is-seata.html

11.1.4 Seata是什么?

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务

11.2 🌟Seata 工作机制/返讲

11.2.1 分布式事务过程分析

1. Seata 分布式事务处理过程-ID+三组件模型

2. 一图胜千言

image-20230703150121800

上图展示了一个分布式事务在Seata的处理过程

  • Transaction ID XID:全局唯一的事务ID
  • Transaction Coordinator(TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚
  • Transaction Manager(TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
  • Resource Manager(RM):控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支 (本地)事务的提交和回滚

执行过程

  • TM向 TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID
  • XID在微服务调用链路的上下文中传播
  • RM 向 TC注册分支事务,将其纳入 XID 对应全局事务的管辖
  • TM 向 TC 发起针对 XID 的全局提交或回滚决议。
  • TC 调度 XID下管辖的全部分支事务完成提交或回滚请求
image-20230703152243363

image-20230703152337277

11.2.2 Seata 事务模式

1. 地址: https://seata.io/zh-cn/docs/overview/what-is-seata.html

11.2.2.1 AT(默认模式)

11.2.2.2 TCC

11.2.2.3 SAGA

11.2.2.4 XA

11.2.3 AT无侵入模式

11.2.3.1 文档: https://seata.io/zh-cn/docs/overview/what-is-seata.html

11.2.3.2 一阶段加载

在一阶段,Seata 会拦截**”**业务 SQL”

image-20230703161643439

  1. 解析 SQL 语义,找到“业务SQL”要更新的业务数据,在业务数据被更新前,将其保存成”before image”(前罝镜像)

  2. 执行”业务 SQL”更新业务数据,在业务数据更新之后,其保存成”after image” /后置镜像

  3. 最后生成行锁

  4. 以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性

其中,前像与后像存于undo_log数据表中

11.2.3.3 二阶段提交

image-20230703162629147

1. 二阶段如果是顺利提交

2. 因为”业务 SQL”在一阶段已经提交至数据库,所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可

11.2.3.4 二阶段回滚

image-20230703162812449

  1. 二阶段如果是回滚的话,seata 就需要回滚一阶段己经执行的“业务 SQL”‘,还原业务数据

  2. 回滚方式便是用”before image”还原业务数据;但在还原前要首先要校验脏号,对比”数据库当前业务数据”和”after image 如果两份数据完全一致就说明没有脏写,可以还原业务数据

  3. 如果不一致就说明有脏写,出现脏写就需要转人工处理

11.2.4 AT事务模式Debug验证

image-20230703163149523

11.2.5 SEATA的分布式交易解决方案

image-20230703163341180

11.3 SeataServer安装

11.3.1 下载

11.3.1.1 https://github.com/seata/seata/releases/tag/v0.9.0

11.3.2 安装和配置

1.seata-server-0.9.0.zip 解压到 指定目录,比如 d:\program

2. 修改 conf\file.conf 文件:

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
#thread factory for netty
thread-factory {
boss-thread-prefix = "NettyBoss"
worker-thread-prefix = "NettyServerNIOWorker"
server-executor-thread-prefix = "NettyServerBizHandler"
share-boss-worker = false
client-selector-thread-prefix = "NettyClientSelector"
client-selector-thread-size = 1
client-worker-thread-prefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
boss-thread-size = 1
#auto default pin or 8
worker-thread-size = 8
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
#vgroup->rgroup
#vgroup_mapping.my_test_tx_group = "default"
vgroup_mapping.my_test_tx_group = "hspedu_order_tx_group"
#only support single node
default.grouplist = "127.0.0.1:8091"
#degrade current not support
enableDegrade = false
#disable
disable = false
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
}

client {
async.commit.buffer.limit = 10000
lock {
retry.internal = 10
retry.times = 30
}
report.retry.count = 5
tm.commit.retry.count = 1
tm.rollback.retry.count = 1
}

## transaction log store
store {
## store mode: file、db
##mode = "file"
mode = "db"

## file store
file {
dir = "sessionStore"

# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
max-branch-session-size = 16384
# globe session size , if exceeded throws exceptions
max-global-session-size = 512
# file buffer size , if exceeded allocate new buffer
file-write-buffer-cache-size = 16384
# when recover batch read size
session.reload.read_size = 100
# async, sync
flush-disk-mode = async
}

## database store
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
db-type = "mysql"
driver-class-name = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
##user = "mysql"
user = "root"
##password = "mysql"
password = "QWEASDzxc123"
min-conn = 1
max-conn = 3
global.table = "global_table"
branch.table = "branch_table"
lock-table = "lock_table"
query-limit = 100
}
}
lock {
## the lock store mode: local、remote
mode = "remote"

local {
## store locks in user's database
}

remote {
## store locks in the seata's server
}
}
recovery {
#schedule committing retry period in milliseconds
committing-retry-period = 1000
#schedule asyn committing retry period in milliseconds
asyn-committing-retry-period = 1000
#schedule rollbacking retry period in milliseconds
rollbacking-retry-period = 1000
#schedule timeout retry period in milliseconds
timeout-retry-period = 1000
}

transaction {
undo.data.validation = true
undo.log.serialization = "jackson"
undo.log.save.days = 7
#schedule delete expired undo_log in milliseconds
undo.log.delete.period = 86400000
undo.log.table = "undo_log"
}

## metrics settings
metrics {
enabled = false
registry-type = "compact"
# multi exporters use comma divided
exporter-list = "prometheus"
exporter-prometheus-port = 9898
}

support {
## spring
spring {
# auto proxy the DataSource bean
datasource.autoproxy = false
}
}

3. mysql5.7 创建 seata 数据库

4. 修改 seata \conf\registry.conf , 配置注册中心 nacos server

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
# type = "file"
type = "nacos"

nacos {
# serverAddr = "localhost"
serverAddr = "localhost:8848"
namespace = ""
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}

config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"

nacos {
serverAddr = "localhost"
namespace = ""
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}

11.3.3 启动

11.3.3.1 启动 Nacos Server 8848

11.3.3.2 双击 Seata 的\bin\seata-server.bat , 启动 Seata Server

1. 启动 seata-server.bat , 看到如下界面说明成功

1
2
cd /Applications/seata/bin
./seata-server.sh -h 127.0.0.1 -p 8848 -m db -n 1

11.3.3.3 登录 Nacos Server , 查看 Seata Server 是否注册成功

image-20230702162714540

11.4 Seata 分布式事务-应用实例

11.4.1 需求分析**/**图解

1. 需求:完成下订单功能,由三个微服务模块协同完成**,** 涉及到多数据库**,** 多张表

image-20230702164527518

11.4.2 创建数据库和表

1. 创建业务数据库和表

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
-- 订单微服务的数据库
CREATE DATABASE order_micro_service
USE order_micro_service

CREATE TABLE `order`(
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT DEFAULT NULL ,
product_id BIGINT DEFAULT NULL ,
nums INT DEFAULT NULL ,
money INT DEFAULT NULL,
`status` INT DEFAULT NULL COMMENT '0:创建中; 1:已完结'
);

SELECT * FROM `order`

-- 库存微服务的数据库`storage``order`
CREATE DATABASE storage_micro_service
USE storage_micro_service

CREATE TABLE `storage`(
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
product_id BIGINT DEFAULT NULL ,
amount INT DEFAULT NULL COMMENT '库存量'
);

-- 初始化库存表
INSERT INTO `storage` VALUES(NULL, 1, 10);
SELECT * FROM `storage`
-- 账号微服务的数据库
CREATE DATABASE account_micro_service
USE account_micro_service

CREATE TABLE `account`(
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
user_id BIGINT DEFAULT NULL ,
money INT DEFAULT NULL COMMENT '账户金额'
);

-- 初始化账户表
INSERT INTO `account` VALUES(NULL, 666, 10000);

2. 分别为 3 库创建对应的回滚日志表**,** 说明回滚日志表在 seata\conf\db_undo_log.sql

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
use order_micro_service 
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


use storage_micro_service
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


use account_micro_service
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

11.4.3 开发 seata_storage_micro_service-10010 微服务

1. 参考以前的方式,创建 seata_storage_micro_service-10010 微服务模块

2. 修改 pom.xml, 添加相关的 jar 依赖

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<dependencies>
<!-- 提示 application.yml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 引入 openfeign,因为可能需要用到远程调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 在微服务模块引入 nacos-discovery starter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!-- 排除自带的 seata-all -->
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入指定版本的 io.seata -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
<!-- 引入 e_commerce_center-common-api -->
<dependency>
<groupId>com.hspedu.springcloud</groupId>
<artifactId>e_commerce_center-common-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<!-- 这里我们重新指定一下 version -->
<version>1.1.13</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

3. 创建 application.yml, 进行相关的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server:
port: 10010
spring:
application:
name: seata_storage_micor_service
cloud:
alibaba:
seata:
#自定义事务组名称需要与 seata-server 中的对应,看 \conf\file.conf
tx-service-group: hspedu_order_tx_group
nacos:
discovery:
server-addr: localhost:8848 #配置 Nacos Server
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/storage_micro_service
username: root
password: QWEASDzxc123
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml

4. 创建 file.conf, 进行相关的配置, 说明:该文件从 seata \conf\file.conf 拷贝,进行修改即可

5. 创建 com/hspedu/springcloud/entity/Storage.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.study.springcloud.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @author 浦原
* 2023/7/2
* 20:33
* @version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Storage {
private Long id;
private Long productId;
private Integer amount;
}

6. 创建 com/hspedu/springcloud/dao/StorageDao.java

1
2
3
4
5
@Mapper
public interface StorageDao {
//扣减库存信息
void reduce(@Param("productId") Long productId, @Param("nums") Integer nums);
}

7. 创建 **resources/**mapper/StorageMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.hspedu.springcloud.dao.StorageDao">

<resultMap id="BaseResultMap" type="com.hspedu.springcloud.entity.Storage">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="product_id" property="productId" jdbcType="BIGINT"/>
<result column="amount" property="amount" jdbcType="INTEGER"/>
</resultMap>
<!-- 减少库存 -->
<update id="reduce">
UPDATE
storage
SET
amount = amount - #{nums}
WHERE
product_id = #{productId}
</update>
</mapper>

8. 创建 com/hspedu/springcloud/service/StorageService.java

1
2
3
4
public interface StorageService {
// 扣减库存
void reduce(Long productId, Integer nums);
}

9. 创建 com/hspedu/springcloud/service/impl/StorageServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@Service
public class StorageServiceImpl implements StorageService {

// private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
@Resource
private StorageDao storageDao;
@Override
public void reduce(Long productId, Integer nums) {
log.info("==========seata_storage_micro_service-10010 扣 减 库 存 start==========");
storageDao.reduce(productId, nums);
log.info("==========seata_storage_micro_service-10010 扣 减 库 存 end==========");
}
}

10. 创建 com/hspedu/springcloud/controller/StorageController.java

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class StorageController {
@Autowired
private StorageService storageService;
//扣减库存
@PostMapping("/storage/reduce")
public Result reduce(Long productId, Integer nums) {
storageService.reduce(productId, nums);
return Result.success("扣减库存成功 ok", null);
}
}

11. 创建 com/hspedu/springcloud/config/MyBatisConfig.java

1
2
3
4
@Configuration
@MapperScan({"com.hspedu.springcloud.dao"})
public class MyBatisConfig {
}

12. 创建com/hspedu/springcloud/config/DataSourceProxyConfig.java ,常规配置(拿来使用即可)

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
package com.study.springcloud.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
* @author 浦原
* 2023/7/3
* 11:28
* @version 1.0
*/
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapperLocations}")
private String mapperLocations;

@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}

@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}

@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy)
throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean =
new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return sqlSessionFactoryBean.getObject();
}


}

13. 创 建 主 启 动 类 com/hspedu/springcloud/SeataStorageMicroServiceApplication10010.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.study.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
* @author 浦原
* 2023/7/3
* 12:24
* @version 1.0
*/
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataStorageMicroServiceApplication10010 {
public static void main(String[] args) {
SpringApplication.run(SeataStorageMicroServiceApplication10010.class, args);
}
}

11.4.4 测试 seata_storage_micro_service-10010 微服务

11.4.4.1 启动 Nacos Server 8848

11.4.4.2 双击 Seata 的\bin\seata-server.bat , 启动 Seata Server

11.4.4.3 启动 seata_storage_micro_service-10010

11.4.4.4 登录 Nacos Server , 查看 10010 微服务是否注册成功

1. 登录 Nacos Server, 查看 10010 是否注册成功

image-20230703125536604

11.4.5 开发 seata_account_micro_service-10012 微服务

(account模块,参照上面storage模块的创建过程即可)

1. 参考以前的方式,创建 seata_account_micro_service-10012 微服务模块

2. 修改 pom.xml, 添加相关的 jar 依赖

3. 创建 application.yml, 进行相关的配置

4.1 创建 file.conf, 进行相关的配置, 说明:该文件从 seata \conf\file.conf 拷贝,进行修 改即可

4.2 创建 registry.conf, 进行相关的配置, 说明:该文件从 seata \conf\registry.conf 拷贝, 进行修改即可

5. 创建 com/hspedu/springcloud/entity/Account.java

6. 创建 com/hspedu/springcloud/dao/AccountDao.java

7. 创建 **resources/**mapper/AccountMapper.xml

8. 创建 com/hspedu/springcloud/service/AccountService.java

9. 创建 com/hspedu/springcloud/service/impl/AccountServiceImpl.java

10. 创建 com/hspedu/springcloud/controller/AccountController.java

11. 创建 com/hspedu/springcloud/config/MyBatisConfig.java

12. 创建 com/hspedu/springcloud/config/DataSourceProxyConfig.java , 常规配置(拿来使用即可)

13. 创 建 主 启 动 类 com/hspedu/springcloud/SeataAccountMicroServiceApplication10012.java

11.4.6 测试 seata_account_micro_service-10012 微服务

11.4.6.1 启动 Nacos Server 8848

11.4.6.2 双击 Seata 的\bin\seata-server.bat , 启动 Seata Server

11.4.6.3 启动 seata_account_micro_service-10012

11.4.6.4 登录 Nacos Server , 查看 10012 微服务是否注册成功

1. 登录 Nacos Server, 查看 10012 是否注册成功

image-20230703143135194

11.4.7 开发 seata-order-micro-service-10008 微服务

(Order模块,参照上面storage模块的创建过程即可)

1. 参考以前的方式,创建 seata-order-micro-service-10008 微服务模块

2. 修改 pom.xml, 添加相关的 jar 依赖

3. 创建 application.yml, 进行相关的配置

4.1 创建 file.conf, 进行相关的配置, 说明:该文件从 seata \conf\file.conf 拷贝,进行修 改即可

4.2 创建 registry.conf, 进行相关的配置, 说明:该文件从 seata \conf\registry.conf 拷贝, 进行修改即可

5. 创建 com/hspedu/springcloud/entity/Order.java

6. 创建 com/hspedu/springcloud/dao/OrderDao.java

7. 创建 **resources/**mapper/OrderMapper.xml

8. 创建 com/hspedu/springcloud/service/OrderService.java

9. 创建 com/hspedu/springcloud/service/AccountService.java

10. 创建 com/hspedu/springcloud/service/impl/OrderServiceImpl.java

11. 创建 com/hspedu/springcloud/controller/OrderController.java

12. 创建 com/hspedu/springcloud/config/MyBatisConfig.java

13. 创建 com/hspedu/springcloud/config/DataSourceProxyConfig.java , 常规配置(拿来使用即可)

14. 创 建 主 启 动 类 com/hspedu/springcloud/SeataOrderMicroServiceApplication10008.java

11.4.8 测试 seata-order-micro-service-10008 微服务

……

11.4.9 集成测试**(1)** 三个微服务协同完成**-**正常下单

11.4.9.1 启动 Nacos Server 8848

11.4.9.2 双击 Seata 的\bin\seata-server.bat , 启动 Seata Server

11.4.9.3 启动 seata-order-micro-service-10010 /10012/10008 三个微服务

11.4.9.4 浏览器 : http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

1. 浏 览 器 : http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

image-20230703143712667

2. 查看数据库/表的情况是否正常,结论:如果没有异常出现,正常下单,数据库三张表 数据一致性是OK的

11.4.9.5 注意事项和细节

1. MySQL 出现 too many connections(1040)错误解决方法:

my.ini 设置

max_connections=1000

2. 如果出现**: service id not legal hostname
** 报错 Service id not legal hostname 的原因是服务名称不能带有下划线,可以使用中划线**,**

springcloud 无法识别下划线,把下划线改成中划线就好

11.4.10 集成测试**(2)** 三个微服务协同完成**-**模拟异常

11.4.10.1 启动 Nacos Server 8848

11.4.10.2 双击 Seata 的\bin\seata-server.bat , 启动 Seata Server

11.4.10.3 启动 seata-order-micro-service-100010 /10012/10008 三个微服务

11.4.10.4 浏览器 : http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

1. 修 改 seata_account_micro_service-10012com/hspedu/springcloud/controller/AccountController.java, 模拟异常出现

1
2
3
4
5
6
7
8
@PostMapping("/account/reduce")
public Result result(@RequestParam("userId") Long userId, @RequestParam("money") Integer money){
//模拟异常, 超时
//openfeign 接口调用默认超时时间为 1s try {
TimeUnit.SECONDS.sleep(12); } catch (InterruptedException e) {
e.printStackTrace(); }
accountService.reduce(userId,money);
return Result.success("200", "扣减账户余额 OK"); }

2. 浏 览 器 : http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

image-20230703143951841

3. 查看数据库/表的情况是否正常

结论:这时数据库/表,出现数据不一致现象, 订单是未支付,但是库存减少了,账号钱也扣了(提示:等休眠时间完成后,再查看account表,会看到数据不一致)

11.4.11 集成测试**(3)** 三个微服务协同完成**-使用@GlobalTransactional** 完成分布式事务控制(出现异常,也能保证数据一致性)

11.4.11.1 启动 Nacos Server 8848

11.4.11.2 双击 Seata 的\bin\seata-server.bat , 启动 Seata Server

11.4.11.3 启动 seata-order-micro-service-10008 /10010/10012 三个微服务

11.4.11.4 浏览器 : http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

1. 修 改 seata_account_micro_service-10012com/hspedu/springcloud/controller/AccountController.java, 模拟异常出现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Resource
AccountService accountService;
/**
* 扣减账户余额
*/
@PostMapping("/account/reduce")
public Result result(@RequestParam("userId") Long userId, @RequestParam("money") Integer money){
//模拟异常, 超时,或者 int i = 9 / 0;
//openfeign 接口调用默认超时时间为 1s
//说明 1. 也可以使用其它方式模拟异常, 但在 Debug 看 Seata 分布式事务机制不方便, 不好看效果 , 所以这里我们使用超时异常
//说明 2. 因为是超时异常, 所以在 Debug 分析 Seata 机制时, 可能会发现某张表 被锁几条记录, 因为 seata 会做最终一致性操作(即尝试再提交上次超时的事务).
try {
TimeUnit.SECONDS.sleep(12);
} catch (InterruptedException e) {
e.printStackTrace();
}
accountService.reduce(userId,money);
return Result.success("200", "扣减账户余额 OK"); }
}

**2.**修 改 seata-order-micro-service-10008 com/study/springcloud/service/impl/OrderServicelmpl.java

使用@GlobalTransactional控制分布式事务,保证数据一致性

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
/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
* 解读
* 1. @GlobalTransactional: 分布式全局事务控制
* 2. name = "hspedu-save-order" 名称自己写,保证唯一即可
* 3. rollbackFor = Exception.class 指定发生什么异常就回滚,
* 这里指定只要发生异常就回滚
*/
@Override
//下面这句话是做全局事务控制的, 如果没有,则没有分布式全局事务控制
@GlobalTransactional(name = "hspedu-save-order", rollbackFor = Exception.class)
public void save(Order order) {
log.info("=========开始新建订单 start ==========");
//新建订单
orderDao.save(order);
System.out.println("order=" + order);
log.info("=========减库存 start ==========");
storageService.reduce(order.getProductId(), order.getNums());
log.info("=========减库存 end ==========");
log.info("=========减账户金额 start ==========");
accountService.reduce(order.getUserId(), order.getMoney());
log.info("=========减账户金额 end ==========");
log.info("=========修改订单状态 start ==========");
orderDao.update(order.getUserId(), 0);
log.info("=========修改订单状态 end ==========");
log.info("=========下订单 end==========");
}

3. 重启 seata-order-micro-service-10008

4. 浏 览 器 : http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

image-20230703143951841

5. 查看数据库/表的情况是否正常, 结论:这时数据库/表,数据不一致性得到保证.

11.4.12 注意事项和细节

11.4.12.1 如果数据库/表使用到关键字,需要使用反引号

  • 举例说明**:** 比如mapper/OrderMapper.xml , 这里的 order 就要使用**``,** 否则会报错
1
2
3
4
5
6
7
8
<insert id="save">
insert into `order` (id,user_id,product_id,nums,money,status)
values (null,#{userId},# {productId},#{nums},#{money},0);
</insert>
<update id="update">
update `order` set status = 1
where user_id=#{userId} and status = #{status};
</update>

11.4.12.2 openfeign在远程调用api接口时,默认超时时间为1s