image-20260106025947877

红色部分是Alibaba

1、从boot和cloud版本选型开始

最终选型

1
2
3
4
5
6
Java 17+
cloud 2023.0.0
boot 3.2.0
cloud alibaba 2022.0.0.0-RC2
Maven 3.9+
Mysql 8.0+

推演过程

为什么选版本?怎么选型呢?

SpringCloud 和 SpringCloud Alibaba 截然不同,根据工作需要可以直接学SpringCloudAlibaba

SpringBoot版本选择

​ git源码地址 https://github.com/spring-projects/spring-boot/releases/

​ 官网看Boot版本 3.2.0

也就是直接看官网,但是官网有延后,还要看 git源码上的版本说明,配合着看

那谁决定谁呢?

若同时使用Boot和cloud,由话事人cloud决定boot版本

Spring cloud Alibaba 毕业版本依赖关系

image-20260106031919849

2、关于Cloud各种组件的停更/升级/替换

微服务理论知识入门

什么是微服务

形象一点来说:微服务就像搭积木,每个微服务都是一个零件一个继母,并使用这些继母零件组装出不同的形状,出来最后一个成体。

通俗来说:微服务就是把一个大系统按业务功能分解成多个职责单一的小系统,每一个小木块尽量专一的只做一件事,并利用简单的方式使多个小系统相互协作,组合成一个大系统后再同意对外提供整体服务。

学科派一点:专注于单一责任小型功能模块为基础,并利用轻量化机制(通常为HTTP RESTful API)实现通信完成复杂业务搭建的一种架构思想。

那么微服务架构需要的功能有以下:

1、服务注册与发现

2、服务调用

3、服务熔断

4、负载均衡

5、服务降级

6、服务消息队列

7、配置中心管理

8、服务网关

9、服务监控

10、全链路追踪

11、自动化构建部署

12、服务定时任务调度操作

等等。

springcloud和springboot的区别

Spring Boot 是「快速开发单个微服务应用」的基础框架,解决 “单体应用快速开发、配置简化” 问题;Spring Cloud 是「管理 / 协调多个微服务」的分布式框架,基于 Spring Boot 构建,解决 “微服务集群的服务治理、分布式协作” 问题

SpringCloud 和SpringCloud Alibaba 的区别

Spring Cloud Alibaba 是 Spring Cloud 生态的「本土化增强实现」,是 Spring Cloud 标准规范的具体落地;两者完全可以混着用,且实际生产中大多是 “Spring Cloud 核心组件 + Spring Cloud Alibaba 特色组件” 的组合方式

那么SpringCloud = 分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶

SpringCloud这个大集合里有多少种技术?大概20多种各种各样,但是下面就讲解通用的。

image-20260106033538015

image-20260106035205100

现在用的组件。

3、微服务架构编码Base工程模块构建

理论知道了,这里直接实战理解。

做一个订单 -> 支付 的案例

需求说明

image-20260106040501990

约定 > 配置 > 编码

IDEA新建Project和Maven父工程

微服务cloud整体聚合Maven父工程Project

1、New Project

image-20260106041413544

2、聚合总父工程名字

image-20260106041541275

3、字符编码

image-20260106041745035

4、注解生效激活

image-20260106041900303

5、java编译版本选17

image-20260106041943050

6、File Type过滤以及配置自己的Maven

image-20260106042255854

父工程POM文件内容

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

<groupId>com.bitzh</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>


<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hutool.version>5.8.22</hutool.version>
<lombok.version>1.18.26</lombok.version>
<druid.version>1.1.20</druid.version>
<mybatis.springboot.version>3.0.2</mybatis.springboot.version>
<mysql.version>8.0.11</mysql.version>
<swagger3.version>2.2.0</swagger3.version>
<mapper.version>4.2.3</mapper.version>
<fastjson2.version>2.0.40</fastjson2.version>
<persistence-api.version>1.0.2</persistence-api.version>
<spring.boot.test.version>3.1.5</spring.boot.test.version>
<spring.boot.version>3.2.0</spring.boot.version>
<spring.cloud.version>2023.0.0</spring.cloud.version>
<spring.cloud.alibaba.version>2022.0.0.0-RC2</spring.cloud.alibaba.version>
</properties>

<dependencyManagement>
<dependencies>
<!--springboot 3.2.0-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springcloud 2023.0.0-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springcloud alibaba 2022.0.0.0-RC2-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringBoot集成mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.springboot.version}</version>
</dependency>
<!--Mysql数据库驱动8 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--SpringBoot集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--通用Mapper4之tk.mybatis-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>${mapper.version}</version>
</dependency>
<!--persistence-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>${persistence-api.version}</version>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${swagger3.version}</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<!-- spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.test.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

Maven工程落地细节复习

Maven中的DependencyManagement和Dependencies

dependencyManagement

Maven 使用dependencyManagement 元素来提供了一种管理依赖版本号的方式。

通常会在一个组织或者项目的最顶层的父POM 中看到dependencyManagement 元素。

使用pom.xml 中的dependencyManagement 元素能让所有在子项目中引用一个依赖而不用显式的列出版本号。

Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement 元素的项目,然后它就会使用这个

dependencyManagement 元素中指定的版本号。

image-20260106042918071

这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,优势:

1、这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改 ;

2、另外如果某个子项目需要另外的一个版本,只需要声明version就可。

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

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

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

maven中跳过单元测试

1、配置

1
2
3
4
5
6
7
8
9
10
11
<build><!-- maven中跳过单元测试 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>

2、IDEA工具支持(推荐)

image-20260106043113543

mysql驱动说明

Mysql5
1
2
3
4
5
6
7
8
9
10
11
12
# mysql5.7---JDBC四件套
jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.url= jdbc:mysql://localhost:3306/db2024?useUnicode=true&characterEncoding=UTF-8&useSSL=false
jdbc.user = root
jdbc.password =123456

# Maven的POM文件处理
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
Mysql8
1
2
3
4
5
6
7
8
9
10
11
12
# mysql8.0---JDBC四件套
jdbc.driverClass = com.mysql.cj.jdbc.Driver
jdbc.url= jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
jdbc.user = root
jdbc.password =123456

# Maven的POM
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>

Mapper4之一键生成

这只是个单独的生成工具,也可以使用idea的一些插件,或者用ai生成常见的sql脚本,都是一样的。

mybatis-generator

官网:http://mybatis.org/generator/

MyBatis通用Mapper4官网

官网:https://github.com/abel533/Mapper

本次使用Mapper4

一键生成步骤

SQL

新建数据库db2024

创建库t_pay支付信息表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
DROP TABLE IF EXISTS `t_pay`;



CREATE TABLE `t_pay` (

`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,

`pay_no` VARCHAR(50) NOT NULL COMMENT '支付流水号',

`order_no` VARCHAR(50) NOT NULL COMMENT '订单流水号',

`user_id` INT(10) DEFAULT '1' COMMENT '用户账号ID',

`amount` DECIMAL(8,2) NOT NULL DEFAULT '9.9' COMMENT '交易金额',

`deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',

`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

PRIMARY KEY (`id`)

) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='支付交易表';



INSERT INTO t_pay(pay_no,order_no) VALUES('pay17203699','6544bafb424a');



SELECT * FROM t_pay;
Module

普通的Maven工程:mybatis_generator2024

POM
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
<?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.bitzh</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<!--我自己独一份,只是一个普通Maven工程,与boot和cloud无关-->
<artifactId>mybatis_generator2024</artifactId>

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

<dependencies>
<!--Mybatis 通用mapper tk单独使用,自己独有+自带版本号-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- Mybatis Generator 自己独有+自带版本号-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.2</version>
</dependency>
<!--通用Mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
</dependency>
<!--mysql8.0-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--persistence-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--lombok-->
<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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>${basedir}/src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.2</version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.2.3</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

</project>
配置

src\main\resources路径下新建

config.properties

1
2
3
4
5
6
7
8
#t_pay表包名
package.name=com.bitzh.cloud

# mysql8.0
jdbc.driverClass = com.mysql.cj.jdbc.Driver
jdbc.url= jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
jdbc.user = root
jdbc.password = root

generatorConfig.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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
<properties resource="config.properties"/>

<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>

<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
<property name="caseSensitive" value="true"/>
</plugin>

<jdbcConnection driverClass="${jdbc.driverClass}"
connectionURL="${jdbc.url}"
userId="${jdbc.user}"
password="${jdbc.password}">
</jdbcConnection>

<javaModelGenerator targetPackage="${package.name}.entities" targetProject="src/main/java"/>

<sqlMapGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java"/>

<javaClientGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java" type="XMLMAPPER"/>

<table tableName="t_pay" domainObjectName="Pay">
<generatedKey column="id" sqlStatement="JDBC"/>
</table>
</context>
</generatorConfiguration>
一键生成

双击插件 mybatis-generator:generate,一键生成

生成enetity + mapper接口 + xml实现SQL

Rest通用Base工程构建

工程V1

1、cloud-provider-payment8001微服务提供者支付Module模块
微服务小口诀

建module,改POM,写YML,主启动,业务类

步骤

1、建module

创建普通Maven模块:cloud-provider-payment8001

2、改POM

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
<?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.bitzh</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<artifactId>cloud-provider-payment8001</artifactId>

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

<dependencies>
<!--SpringBoot通用依赖模块-->
<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>
<!--SpringBoot集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!--mybatis和springboot整合-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--Mysql数据库驱动8 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--persistence-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
</dependency>
<!--通用Mapper4-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

3、写YML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 8001

# ==========applicationName + druid-mysql8 driver===================
spring:
application:
name: cloud-payment-service

datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: 123456

# ========================mybatis===================
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.cloud.entities
configuration:
map-underscore-to-camel-case: true

4、主启动

1
2
3
4
5
6
7
8
@SpringBootApplication
@MapperScan("com.bitzh.cloud.mapper")
public class Main8001 {
// 这是一个main方法,程序的入口
public static void main(String[] args) {
SpringApplication.run(Main8001.class, args);
}
}

5、业务类

这个版本有些bug,但是常见所以这里演示一些bug

首先将之前一键生成的代码直接拷贝进入8001模块

entities

主实体Pay

1
刚刚生成的

传递数值PayDTO(PO 对应数据库表,DTO 用于服务间 / 层间传输,VO 面向前端展示 / 接收)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayDTO implements Serializable {
private Integer id;
//支付流水号
private String payNo;
//订单流水号
private String orderNo;
//用户账号ID
private Integer userId;
//交易金额
private BigDecimal amount;
}

mapper

Mapper接口PayMapper用生成的

映射文件PayMapper.xml用生成的

service

服务接口 PayService

实现类 PayServiceImpl

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RestController
public class PayController{
@Resource PayService payService;
@PostMapping(value = "/pay/add")
public String addPay(@RequestBody Pay pay){
System.out.println(pay.toString());
int i = payService.add(pay);
return "成功插入记录,返回值:"+i;
}
@DeleteMapping(value = "/pay/del/{id}")
public Integer deletePay(@PathVariable("id") Integer id) {
return payService.delete(id);
}
@PutMapping(value = "/pay/update")
public String updatePay(@RequestBody PayDTO payDTO){
Pay pay = new Pay();
BeanUtils.copyProperties(payDTO, pay);

int i = payService.update(pay);
return "成功修改记录,返回值:"+i;
}
@GetMapping(value = "/pay/get/{id}")
public Pay getById(@PathVariable("id") Integer id){
return payService.getById(id);
}
@GetMapping(value = "/pay/getAll")
public List<Pay> getAll(){
return payService.getAll();
}
}
测试

1、postman/apifox一键生成

2、Swqgger3 / Knife4J

常用注解

注解列表

image-20260106060658087

Swagger想要启动需要配置,下面的配置含分组迭代

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
package com.bitzh.cloud.config;

import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @Auther: oyy0v0
* @Date: 2026/1/6 - 01 - 06 - 6:23
* @Description: com.bitzh.cloud.config
* @version: 1.0
*/
@Configuration
public class Swagger3Config
{
@Bean
public GroupedOpenApi PayApi()
{
return GroupedOpenApi.builder().group("支付微服务模块").pathsToMatch("/pay/**").build();
}
@Bean
public GroupedOpenApi OtherApi()
{
return GroupedOpenApi.builder().group("其它微服务模块").pathsToMatch("/other/**", "/others").build();
}
/*@Bean
public GroupedOpenApi CustomerApi()
{
return GroupedOpenApi.builder().group("客户微服务模块").pathsToMatch("/customer/**", "/customers").build();
}*/

@Bean
public OpenAPI docsOpenApi()
{
return new OpenAPI()
.info(new Info().title("cloud2024")
.description("通用设计rest")
.version("v1.0"))
.externalDocs(new ExternalDocumentation()
.description("www.atguigu.com")
.url("https://yiyan.baidu.com/"));
}
}

访问: http://localhost:8001/swagger-ui/index.html

2、上面模块还有哪些问题
1、时间格式问题

时间日志格式的统一和定制情况?

2、Java如何设计API接口实现统一格式返回?

影响前端/小程序/app等交互体验和开发

3、全局异常介入返回的标准格式

有统一返回值 + 全局统一异常

等等

工程V2

解决时间格式问题

方法一:在相应的类的属性上使用 @JsonFormat 注解

1
2
3
4
@JsonFormat(pattern = "yyy-MM-dd HH:mm:ss",timezone = "GMT+8")
或者
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:sss")
private Date billtime;

方法二:如果是 Spring Boot项目,也可以在 application.yml文件中制定

1
2
3
4
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8

但是后期配置到yml里面的文件越来越多,所以就不写进去了。

解决统一返回值
思路

定义返回标准格式,3大标配

  • code 状态值:由后端统一定义各种返回结果的状态码
  • message 描述:本次接口调用的结果描述
  • data 数据:本次返回的数据

扩展:接口调用时间之类

  • timestamp:接口调用时间
步骤

新建枚举类 ReturnCodeEnum

  • HTTP请求返回的状态码

image-20260106184327346

  • ReturnCodeEnum

枚举类小口诀:举值、构造、遍历

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
@Getter
public enum ReturnCodeEnum
{
/**操作失败**/
RC999("999","操作XXX失败"),
/**操作成功**/
RC200("200","success"),
/**服务降级**/
RC201("201","服务开启降级保护,请稍后再试!"),
/**热点参数限流**/
RC202("202","热点参数限流,请稍后再试!"),
/**系统规则不满足**/
RC203("203","系统规则不满足要求,请稍后再试!"),
/**授权规则不通过**/
RC204("204","授权规则不通过,请稍后再试!"),
/**access_denied**/
RC403("403","无访问权限,请联系管理员授予权限"),
/**access_denied**/
RC401("401","匿名用户访问无权限资源时的异常"),
RC404("404","404页面找不到的异常"),
/**服务异常**/
RC500("500","系统异常,请稍后重试"),
RC375("375","数学运算异常,请稍后重试"),

INVALID_TOKEN("2001","访问令牌不合法"),
ACCESS_DENIED("2003","没有权限访问该资源"),
CLIENT_AUTHENTICATION_FAILED("1001","客户端认证失败"),
USERNAME_OR_PASSWORD_ERROR("1002","用户名或密码错误"),
BUSINESS_ERROR("1004","业务逻辑异常"),
UNSUPPORTED_GRANT_TYPE("1003", "不支持的认证模式");

/**自定义状态码**/
private final String code;
/**自定义描述**/
private final String message;

ReturnCodeEnum(String code, String message){
this.code = code;
this.message = message;
}

//遍历枚举V1
public static ReturnCodeEnum getReturnCodeEnum(String code)
{
for (ReturnCodeEnum element : ReturnCodeEnum.values()) {
if(element.getCode().equalsIgnoreCase(code))
{
return element;
}
}
return null;
}
//遍历枚举V2
public static ReturnCodeEnum getReturnCodeEnumV2(String code)
{
return Arrays.stream(ReturnCodeEnum.values()).filter(x -> x.getCode().equalsIgnoreCase(code)).findFirst().orElse(null);
}


/*public static void main(String[] args)
{
System.out.println(getReturnCodeEnumV2("200"));
System.out.println(getReturnCodeEnumV2("200").getCode());
System.out.println(getReturnCodeEnumV2("200").getMessage());
}*/
}

新建统一定义返回对象ResultDataResultData<T>

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
@Data
@Accessors(chain = true)
public class ResultData<T> {

private String code;/** 结果状态 ,具体状态码参见枚举类ReturnCodeEnum.java*/
private String message;
private T data;
private long timestamp ;


public ResultData (){
this.timestamp = System.currentTimeMillis();
}

public static <T> ResultData<T> success(T data) {
ResultData<T> resultData = new ResultData<>();
resultData.setCode(ReturnCodeEnum.RC200.getCode());
resultData.setMessage(ReturnCodeEnum.RC200.getMessage());
resultData.setData(data);
return resultData;
}

public static <T> ResultData<T> fail(String code, String message) {
ResultData<T> resultData = new ResultData<>();
resultData.setCode(code);
resultData.setMessage(message);

return resultData;
}

}
修改PayController
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
@RestController
@Tag(name = "支付微服务模块",description = "支付CRUD")
public class PayController
{
@Resource
PayService payService;

@PostMapping(value = "/pay/add")
@Operation(summary = "新增",description = "新增支付流水方法,json串做参数")
public ResultData<String> addPay(@RequestBody Pay pay)
{
System.out.println(pay.toString());
int i = payService.add(pay);
return ResultData.success("成功插入记录,返回值:"+i);
}

@DeleteMapping(value = "/pay/del/{id}")
@Operation(summary = "删除",description = "删除支付流水方法")
public ResultData<Integer> deletePay(@PathVariable("id") Integer id) {
int i = payService.delete(id);
return ResultData.success(i);
}

@PutMapping(value = "/pay/update")
@Operation(summary = "修改",description = "修改支付流水方法")
public ResultData<String> updatePay(@RequestBody PayDTO payDTO)
{
Pay pay = new Pay();
BeanUtils.copyProperties(payDTO, pay);

int i = payService.update(pay);
return ResultData.success("成功修改记录,返回值:"+i);
}

@GetMapping(value = "/pay/get/{id}")
@Operation(summary = "按照ID查流水",description = "查询支付流水方法")
public ResultData<Pay> getById(@PathVariable("id") Integer id)
{
Pay pay = payService.getById(id);
return ResultData.success(pay);
}

//全部查询getall作为家庭作业
}

解决:全局异常介入返回的标准格式
为什么需要全局异常处理器

不要再手写 try catch

当然非要写也可以

新建全局异常类

GlobalExceptionHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler
{
/**
* 默认全局异常处理。
* @param e the e
* @return ResultData
*/
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResultData<String> exception(Exception e) {
System.out.println("----come in GlobalExceptionHandler");
log.error("全局异常信息exception:{}", e.getMessage(), e);
return ResultData.fail(ReturnCodeEnum.RC500.getCode(),e.getMessage());
}
}

引入微服务理念,从这里开始

订单微服务80如何才能调用到支付微服务8001?
cloud-consumer-order80微服务调用者订单Module模块
建立 cloud-consumer-order80
改POM
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
<?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.bitzh</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<groupId>com.bitzh.cloud</groupId>
<artifactId>cloud-consumer-order80</artifactId>

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

<dependencies>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool-all-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--fastjson2-->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
写YML
1
2
server:
port: 80

主启动

1
2
3
4
5
6
7
8
@SpringBootApplication
public class Main80
{
public static void main(String[] args)
{
SpringApplication.run(Main80.class,args);
}
}
业务类

那么我们现在有两个Module,怎么调用另一个模块呢?这时候就需要了解RestTemplate

entities 传递数值PayDTO

消费者调用支付模块,应该是支付模块暴露接口让消费者调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
*一般而言,调用者不应该获悉服务提供者的entity资源并知道表结构关系,所以服务提供方给出的

接口文档都都应成为DTO
* */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayDTO implements Serializable
{
private Integer id;
//支付流水号
private String payNo;
//订单流水号
private String orderNo;
//用户账号ID
private Integer userId;
//交易金额
private BigDecimal amount;
}

RestTemplate

是什么?

1
2
3
4
RestTemplate提供了多种便捷访问远程Http服务的方法, 
是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集

官网:https://docs.spring.io/spring-framework/docs/6.0.11/javadoc-api/org/springframework/web/client/RestTemplate.html

常用API使用说明

1
2
3
4
5
使用restTemplate访问restful接口非常的简单粗暴无脑。

(url, requestMap, ResponseBean.class)这三个参数分别代表

REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。

image-20260107005218211

getForObject方法/getForEntity方法

返回对象为响应体中数据转化成的对象,基本上可以理解为Json

image-20260107010234705

返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等

image-20260107010842861

postForObject / postForEntity

image-20260107011027418

GET请求方法

1
2
3
4
5
6
7
8
9
10
11
12
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables);

<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables);

<T> T getForObject(URI url, Class<T> responseType);


<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables);

<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables);

<T> ResponseEntity<T> getForEntity(URI var1, Class<T> responseType);

POST请求方法

1
2
3
4
5
6
7
8
9
10
11
12
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);

<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);

<T> T postForObject(URI url, @Nullable Object request, Class<T> responseType);


<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);

<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);

<T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType);

当然正常开发的时候不会每个都使用都new一次,而是写一个配置类利用容器管理对象

config配置类

1
2
3
4
5
6
7
8
9
@Configuration
public class RestTemplateConfig
{
@Bean
public RestTemplate restTemplate()
{
return new RestTemplate();
}
}

controller调用支付模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@RestController
public class OrderController {
public static final String PaymentSrv_URL = "http://localhost:8001";// 先写死,硬编码
@Resource
private RestTemplate restTemplate;

/**
* 一般情况下,通过浏览器的地址栏输入url,发送的只能是get请求
* 我们底层调用的是post方法,模拟消费者发送get请求,客户端消费者
* 参数可以不添加@RequestBody
*
* @param payDTO
* @return
*/
@GetMapping("/consumer/pay/add")
public ResultData addOrder(PayDTO payDTO) {
return restTemplate.postForObject(PaymentSrv_URL + "/pay/add", payDTO, ResultData.class);
}


@GetMapping("/consumer/pay/get/{id}")
public ResultData getPayInfo(@PathVariable("id") Integer id) {
return restTemplate.getForObject(PaymentSrv_URL + "/pay/get/{id}" , ResultData.class,id);
}

@PutMapping("/consumer/pay/update")
public ResultData updatePayInfo(@RequestBody PayDTO payDTO) {
HttpEntity<PayDTO> requestEntity = new HttpEntity<>(payDTO);
return restTemplate.exchange(
PaymentSrv_URL + "/pay/update",
HttpMethod.PUT,
requestEntity,
ResultData.class).getBody();
}

@DeleteMapping("/consumer/pay/delete/{id}")
public ResultData deletePayInfo(@PathVariable("id") Integer id) {
return restTemplate.exchange(
PaymentSrv_URL + "/pay/del/{id}",
HttpMethod.DELETE,
null,
ResultData.class,id).getBody();
}
}

一个个启动会很多,所以把他纳入到service里面,统一管理起来

测试

现在测试直接访问cosumer看能不能调用成功。

工程重构
观察问题

现在两个模块中有重复的部分,需要进行重构简化代码

新建Module

创建cloud-api-commons

创建对外暴露通用的组件 / api / 接口 / 工具类等

改POM
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
<?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.bitzh</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<groupId>com.bitzh.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>

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

<dependencies>
<!--SpringBoot通用依赖模块-->
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>

</project>
entities

这个模块不用启动,只是公共模块所以不用写主启动

然后现在把重复的类抽取出来

一个是 PayDTO,一个是统一返回

然后把通用的打包成 jar 包对外暴露,提供服务

订单80和支付8001分别改造

删除各自原先的 entities 和 统一返回体等内容

然后各自粘贴POM内容

1
2
3
4
5
6
<!--引入自己定义的api通用包-->
<dependency>
<groupId>com.bitzh.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
目前工程样图

image-20260107043123021

截至目前,没有引入任何SpringCloud相关内容

为什么要引入微服务

上一个问题controller写死的问题

微服务所在的IP地址和端口号硬编码到订单微服务中,会存在非常多的问题

(1)如果订单微服务和支付微服务的IP地址或者端口号发生了变化,则支付微服务将变得不可用,需要同步修改订单微服务中调用支付微服务的IP地址和端口号。

(2)如果系统中提供了多个订单微服务和支付微服务,则无法实现微服务的负载均衡功能。

(3)如果系统需要支持更高的并发,需要部署更多的订单微服务和支付微服务,硬编码订单微服务则后续的维护会变得异常复杂。

所以,在微服务开发的过程中,需要引入服务治理功能,实现微服务之间的动态注册与发现,从此刻开始我们正式进入SpringCloud实战

4、Consul服务注册与发现

为什么要引入服务注册中心

之前写死地址硬编码会有很多问题。

image-20260107182606501

为什么不再使用老牌的Eureka

1、Eureka停更了

2、Eureka对初学者不友好,自我保护机制

3、注册中心独立且和微服务功能解耦:目前主流服务中心,希望单独隔离出来,而不是嵌入到系统当中。也就是说订单服务,库存服务,支付服务,然后还有个Eureka服务还需要程序员自己开发,而不是独立出来解耦。

4、Spring Cloud Alibaba,现在能做两件事,Eureka只能做一件事。

consul简介

是什么

官网:https://www.consul.io/

Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。

提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。它具有很多优点。包括: 基于 raft 协议,比较简洁; 支持健康检查, 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群 提供图形界面 跨平台,支持 Linux、Mac、Windows

HashiCorp是一家非常知名的基础软件提供商,很多人可能没听过它的名字,但是其旗下的6款主流软件,Terraform、Consul、Vagrant、Nomad、Vault,Packer 相信不少程序员都听说或使用过,尤其是Consul使用者不尽其数。截止目前为止,从HashiCorp 官网上的声明来看,开源项目其实还是“安全”的,被禁用的只是Vault企业版(并且原因是Vault产品目前使用的加密算法在中国不符合法规,另一方面是美国出口管制法在涉及加密相关软件上也有相应规定。因此这两项原因使得HashiCorp不得不在声明中说明风险)而非其他所有开源产品(Terraform、Consul等)。因此,大家可以暂时放下心来,放心使用!

spring consul https://docs.spring.io/spring-cloud-consul/docs/current/reference/html/

能干嘛

服务发现

提供HTTP和DNS两种发现方式。

健康检测

支持多种方式,HTTP、TCP、Docker、Shell脚本定制化监控

KV存储

Key、Value的存储方式

多数据中心

Consul支持多数据中心

可视化Web界面

去哪里下

官网:https://developer.hashicorp.com/consul/install

centos

1
2
3
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install consul

怎么玩

看官网,两大作用:服务注册中心,分布式配置管理

安装并运行consul

下载解压后,在cmd里面然后进入到该解压路径下,然后输入命令 consul --version验证是否安装✅️

使用开发模式启动

1
2
consul agent -dev
通过访问Consul的首页:http://localhost:8500

image-20260107195631672

服务注册与发现

现在要把8001和80注册进入注册中心了。

服务提供者8001

支付服务provider8001注册进consul
POM
1
2
3
4
5
<!--SpringCloud consul discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
YML
1
2
3
4
5
6
7
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
主启动
1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@MapperScan("com.atguigu.cloud.mapper") //import tk.mybatis.spring.annotation.MapperScan;
@EnableDiscoveryClient
public class Main8001
{
public static void main(String[] args)
{
SpringApplication.run(Main8001.class,args);
}
}

因为没有业务和代码操作,本来的还有的业务类和controller,现在都不用操作了。

启动8001并查看consul控制台

image-20260107202947216

然后控制台让我们 remove 一个commons-logging.jar

1
2
3
4
5
6
7
8
9
10
11
<!--SpringCloud consul discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

服务消费者80

修改微服务 cloud-consumer-order80

POM
1
2
3
4
5
6
7
8
9
10
11
<!--SpringCloud consul discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
YML
1
2
3
4
5
6
7
8
9
10
11
spring:
application:
name: cloud-consumer-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
主启动
1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableDiscoveryClient //该注解用于向使用consul为注册中心时注册服务
public class Main80
{
public static void main(String[] args)
{
SpringApplication.run(Main80.class,args);
}
}
Controller
1
public static final String PaymentSrv_URL = "http://cloud-payment-service";//服务注册中心上的微服务名称
启动并查看consul控制台

image-20260107214431135

访问测试地址

http://localhost/consumer/pay/get/1

一个bug,报错了说找不到我们的地址。因为默认是负载均衡,需要在restTemplate支持负载均衡就行了。

配置修改RestTemplateConfig
1
2
3
4
5
6
7
8
9
10
@Configuration
public class RestTemplateConfig
{
@Bean
@LoadBalanced
public RestTemplate restTemplate()
{
return new RestTemplate();
}
}

三个注册中心异同点

面试会问还用没用过别的注册中心,有什么区别,这种问题需要从 CAP 的角度回答。

C:Consistency(强一致性)

A:Availability(可用性)

P:Partition tolerance(分区容错性)

C 是所有节点数据一致
A 是节点始终能提供服务
P 是节点通信中断也能运行

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

CAP理论的核心是

一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,

因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:

CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。

CA 系统的隐含假设:没有网络分区 + 节点不会挂(或单节点)

CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。

AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

image-20260107215509659

AP(Eureka)

image-20260107230008487

CP(Zookeeper / Consul)

image-20260107230028573

image-20260107220746555

服务配置与刷新

分布式系统面临的配置问题

微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。比如某些配置文件中的内容大部分都是相同的,只有个别的配置项不同。就拿数据库配置来说吧,如果每个微服务使用的技术栈都是相同的,则每个微服务中关于数据库的配置几乎都是相同的,有时候主机迁移了,我希望一次修改,处处生效。

当下我们每一个微服务自己带着一个application.yml,上百个配置文件的管理……/(ㄒoㄒ)/~~

官网说明

image-20260107231959496

image-20260107232009737

那么也就是说Consul 能管理服务配置也能进行服务注册中心

服务配置案例步骤

需求

通用全局配置信息,直接注册进Consul服务器,从Consul获取

既然从Consul获取自然要遵守Consul的配置规则要求

一次修改出处生效,一次修改广播生效

修改cloud-provider-payment8001
POM
1
2
3
4
5
6
7
8
9
<!--SpringCloud consul config-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
YML
配置规则说明

image-20260108035636382

新增配置文件bootstrap.yml

是什么

applicaiton.yml是用户级的资源配置项

bootstrap.yml是系统级的,优先级更加高

Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的Application Context的父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment

Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。 Bootstrap contextApplication Context有着不同的约定,所以新增了一个bootstrap.yml文件,保证Bootstrap ContextApplication Context配置的分离。

application.yml文件改为bootstrap.yml,这是很关键的或者两者共存

因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml

bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spring:
application:
name: cloud-payment-service
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
config:
profile-separator: '-' # default value is ",",we update '-'
format: YAML

# config/cloud-payment-service/data
# /cloud-payment-service-dev/data
# /cloud-payment-service-prod/data
application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 8001

# ==========applicationName + druid-mysql8 driver===================
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: root
profiles:
active: dev # 多环境配置加载内容dev/prod,不写就是默认default配置

# ========================mybatis===================
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.bitzh.cloud.entities
configuration:
map-underscore-to-camel-case: true
consul服务器key/value配置填写
参考规则
1
2
3
# config/cloud-payment-service/data
# /cloud-payment-service-dev/data
# /cloud-payment-service-prod/data
创建config文件夹,以 / 结尾

必须以config开头,然后子文件夹就是以服务名为文件夹名来确认是配置哪个服务,不写就是默认default。

data就不再是文件夹了,而是key - value 的 key

image-20260108124310037

image-20260108124403339

controller

然后现在controller看看能不能读取到配置文件内容。

1
2
3
4
5
6
7
@Value("${server.port}")
private String port;

@GetMapping(value = "/pay/get/info")
public String getInfoByConsul(@Value("$bitzh.info") String info){
return info + port;
}
测试

访问 http://localhost:8001/pay/get/info 看能否读取到consul的配置信息。

动态刷新案例步骤

我们在 consul 的 配置分支修改了内容,马上访问,结果无效

步骤
@RefreshScope 主启动类添加
1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
@MapperScan("com.atguigu.cloud.mapper") //import tk.mybatis.spring.annotation.MapperScan;
@EnableDiscoveryClient //服务注册和发现
@RefreshScope // 动态刷新
public class Main8001
{
public static void main(String[] args)
{
SpringApplication.run(Main8001.class,args);
}
}
bootstrap.yml修改下等待时间

这个只是教学,实际别改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
application:
name: cloud-payment-service
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
config:
profile-separator: '-' # default value is ",",we update '-'
format: YAML
watch:
wait-time: 1

# config/cloud-payment-service/data
# /cloud-payment-service-dev/data
# /cloud-payment-service-prod/data

默认已经有了默认值是 55 秒,这里改为1直接看效果

思考

截止到这,服务配置和动态刷新全部通过,假设我们重启Consul , 之前的配置还在吗?

就不在了,所以我们需要Consul配置持久化。后续再讲

5、LoadBalancer负载均衡服务调用

前身是Ribbon,目前进入维护模式

是什么

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

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

Ribbon未来替换方案

spring-cloud-loadbalancer

spring-cloud-loadbalancer概述

是什么

LB负载均衡(Load Balance)是什么

简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用),常见的负载均衡有软件Nginx,LVS,硬件 F5等

spring-cloud-starter-loadbalancer组件是什么

Spring Cloud LoadBalancer是由SpringCloud官方提供的一个开源的、简单易用的客户端负载均衡器,它包含在SpringCloud-commons中用它来替换了以前的Ribbon组件。相比较于Ribbon,SpringCloud LoadBalancer不仅能够支持RestTemplate,还支持WebClient(WeClient是Spring Web Flux中提供的功能,可以实现响应式异步请求)

客户端负载 vs 服务端负载的区别

loadbalancer本地负载均衡客户端 VS Nginx服务端负载均衡区别

Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求,即负载均衡是由服务端实现的。

image-20260109005710814

loadbalancer本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

image-20260109005800356

spring-cloud-loadbalancer负载均衡解析

负载均衡演示案例-理论

image-20260109010050415

LoadBalancer 在工作时分成两步:

第一步,先选择ConsulServer从服务端查询并拉取服务列表,知道了它有多个服务(上图3个服务),这3个实现是完全一样的,

默认轮询调用谁都可以正常执行。类似生活中求医挂号,某个科室今日出诊的全部医生,客户端你自己选一个。

第二步,按照指定的负载均衡策略从server取到的服务注册列表中由客户端自己选择一个地址,所以LoadBalancer是一个客户端的负载均衡器。

如果是nginx,压力都在服务器,需要接受所有的请求然后分发。

现在ribbion,压力在客户端也就是请求的人,而且压力分散,直接得知找的真正服务的地址,然后就能通过负载均衡调用对应的服务了。类似于去医院,选医生。

架构说明:80通过轮询负载访问 8001 / 8002 / 8003

负载均衡演示案例-实操

官网参考如何使用

image-20260109010507785

image-20260109010516823

按照8001拷贝后新建8002微服务

也是按照POM,YML,业务类,controller,主启动

启动Consul,将8001/8002启动后注册进微服务
1
2
3
consul agent -dev
将 8001 / 8002 启动后注册进微服务
出bug了,我们之前的配置完全消失了,没有持久化保存
Consul数据持久化配置

并且注册为Windows服务。

1
2
3
4
5
6
7
8
9
10
1、在一个D:\Study4It\SoftWare\consul\consul_1.17.0_windows_386目录下新建
D:\Study4It\SoftWare\consul\consul_1.17.0_windows_386\mydata
2、consul_start.bat内容信息
@echo.服务启动......
@echo off
@sc create Consul binpath= "D:\Study4It\SoftWare\consul\consul_1.17.0_windows_386\consul.exe agent -server -ui -bind=127.0.0.1 -client=0.0.0.0 -bootstrap-expect 1 -data-dir D:\Study4It\SoftWare\consul\consul_1.17.0_windows_386\mydata"
@net start Consul
@sc config Consul start= AUTO
@echo.Consul start is OK......success
@pause

然后consul 就持久化了。

现在启动8001 和 8002 然后登录 consul 然后发现有2个实例

然后访问 http://localhost:8001/pay/get/info

订单80模块修改POM并注册进consul,新增LoadBalancer组件

POM

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

Controller

1
2
3
4
5
@GetMapping(value = "/consumer/pay/get/info")
private String getInfoByConsul()
{
return restTemplate.getForObject(PaymentSrv_URL + "/pay/get/info", String.class);
}

然后负载均衡的模式默认是轮询。

测试访问

负载均衡演示案例-小总结

编码使用DiscoverClient动态获取所有上线的服务列表

是怎么实现知道哪些服务上线的呢?

image-20260109041145088

官网说有这个配置,那么我们就知道这个配置就是放所有服务节点的了。

然后我们从代码上测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/consumer/discovery")
public String discovery()
{
List<String> services = discoveryClient.getServices();
for (String element : services) {
System.out.println(element);
}

System.out.println("===================================");

List<ServiceInstance> instances = discoveryClient.getInstances("cloud-payment-service");
for (ServiceInstance element : instances) {
System.out.println(element.getServiceId()+"\t"+element.getHost()+"\t"+element.getPort()+"\t"+element.getUri());
}

return instances.get(0).getServiceId()+":"+instances.get(0).getPort();
}

那么负载均衡的原理是什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标  ,每次服务重启动后rest接口计数从1开始。

List<ServiceInstance> instances = discoveryClient.getInstances("cloud-payment-service");

如: List [0] instances = 127.0.0.1:8002

   List [1] instances = 127.0.0.1:8001

8001+ 8002 组合成为集群,它们共计2台机器,集群总数为2, 按照轮询算法原理:



当总请求数为1时: 1 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001

当总请求数位2时: 2 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002

当总请求数位3时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001

当总请求数位4时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002

如此类推......

负载均衡算法原理

默认算法是什么有几种?

默认是轮询,还有一个是随机。如果想要切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@LoadBalancerClient(
//下面的value值大小写一定要和consul里面的名字一样,必须一样
value = "cloud-payment-service",configuration = RestTemplateConfig.class)
public class RestTemplateConfig
{
@Bean
@LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
public RestTemplate restTemplate(){
return new RestTemplate();
}

@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}

6、OpenFeign服务接口调用

已经有LoadBalancer为什么还要学习OpenFeign?

两个都有道理日常用哪个?

是什么

openfeign是一个声明式的Web服务客户端

只需要创建一个Rest接口并在该接口上添加注解@FeignClient即可

image-20260109053040055

OpenFeign基本上就是当前微服务之间调用的事实标准

能干嘛

可插拔的注解支持,包括Feign注解和JAX-RS注解

支持可插拔的HTTP编码器和解码器

支持Sentinel和他的Fallback

支持SpringCloudLoadBalancer的负载均衡

支持HTTP请求和响应的压缩

前面在使用SpringCloud LoadBalancer+RestTemplate时,利用RestTemplate对http请求的封装处理形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。

也就是说,每次调用都要创建一个配置类然后new RestTemplate,每个系统都散落这些new很不规范

所以,OpenFeign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在OpenFeign的实现下,我们只需创建一个接口并使用注解的方式来配置它(在一个微服务接口上面标注一个@FeignClient\注解即可),即可完成对服务提供方的接口绑定,统一对外暴露可以被调用的接口方法,大大简化和降低了调用客户端的开发量,也即由服务提供者给出调用接口清单,消费者直接通过OpenFeign调用即可,O(∩_∩)O。

OpenFeign同时还集成SpringCloud LoadBalancer

可以在使用OpenFeign时提供Http客户端的负载均衡,也可以集成阿里巴巴Sentinel来提供熔断、降级等功能。而与SpringCloud LoadBalancer不同的是,通过OpenFeign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

怎么玩

接口+注解

微服务Api接口 + @FeignClient注解标签

架构说明图

image-20260109055528101

服务消费者80 → 调用含有@FeignClient注解的Api服务接口 → 服务提供者(8001/8002)

流程步骤

建Moudle

建立 cloud-consumer-feign-order80

改POM

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
<?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.bitzh</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<groupId>com.bitzh.cloud</groupId>
<artifactId>cloud-consumer-feign-order80</artifactId>

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

<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--SpringCloud consul discovery-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.bitzh.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool-all-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--fastjson2-->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

写YML

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

spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}

主启动

在主启动类上面配置 @EnableFeignClients表示开启OpenFeign功能并激活

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class MainOpenFeign80 {
// 这是一个main方法,程序的入口
public static void main(String[] args) {
SpringApplication.run(MainOpenFeign80.class, args);
}
}

业务类

按照架构图,服务提供者已经配好了,现在要配置公共模块了。

修改cloud-api-commons通用模块

在通用模块配置,就不用每个服务都配置一遍新建一个template了

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

新建服务接口PayFeignApi,头上配置@FeignClient注解

image-20260109060652304

参考微服务8001的Controller层,新建PayFeignApi接口

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
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi {
/**
* 新增一条支付相关流水记录
* @param payDTO
* @return
*/
@PostMapping("/pay/add")
public ResultData addPay(@RequestBody PayDTO payDTO);

/**
* 按照主键记录查询支付流水信息
* @param id
* @return
*/
@GetMapping("/pay/get/{id}")
public ResultData getPayInfo(@PathVariable("id") Integer id);

/**
* openfeign天然支持负载均衡演示
* @return
*/
@GetMapping(value = "/pay/get/info")
public String mylb();
}

也就是说80端口访问8001和8002不是访问本地的Ribbon了,而是访问Openfeign的这个接口然后在访问8001和8002了。

然后现在公共模块好了,现在修改80端口的controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@RestController
@Slf4j
public class OrderController
{
@Resource
private PayFeignApi payFeignApi;

@PostMapping("/feign/pay/add")
public ResultData addOrder(@RequestBody PayDTO payDTO)
{
System.out.println("第一步:模拟本地addOrder新增订单成功(省略sql操作),第二步:再开启addPay支付微服务远程调用");
ResultData resultData = payFeignApi.addPay(payDTO);
return resultData;
}

@GetMapping("/feign/pay/get/{id}")
public ResultData getPayInfo(@PathVariable("id") Integer id)
{
System.out.println("-------支付微服务远程调用,按照id查询订单支付流水信息");
ResultData resultData = payFeignApi.getPayInfo(id);
return resultData;
}

/**
* openfeign天然支持负载均衡演示
*
* @return
*/
@GetMapping(value = "/feign/pay/mylb")
public String mylb()
{
return payFeignApi.mylb();
}
}

测试

小总结

image-20260109074337179

高级特性

1、OpenFeign超时控制

OpenFeign的版本要注意,版本会影响配置的。

在Spring Cloud微服务架构中,大部分公司都是利用OpenFeign进行服务间的调用,而比较简单的业务使用默认配置是不会有多大问题的,但是如果是业务比较复杂,服务要进行比较繁杂的业务计算,那后台很有可能会出现Read Timeout这个异常,因此定制化配置超时时间就有必要了

超时设置,故意设置超时演示出错情况,自己使坏写bug

故意设置超时错误

服务提供方8001写暂停62秒程序

image-20260109191814318

为什么是62秒?

服务调用方80写好捕捉超时异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@GetMapping("/feign/pay/get/{id}")
public ResultData getPayInfo(@PathVariable("id") Integer id)
{
System.out.println("-------支付微服务远程调用,按照id查询订单支付流水信息");
ResultData resultData = null;
try
{
System.out.println("调用开始-----:"+DateUtil.now());
resultData = payFeignApi.getPayInfo(id);
} catch (Exception e) {
e.printStackTrace();
System.out.println("调用结束-----:"+DateUtil.now());
ResultData.fail(ReturnCodeEnum.RC500.getCode(),e.getMessage());
}
return resultData;
}
测试

然后看报错

结论

OpenFeign默认等待60秒钟,超过后报错。

官网解释+配置处理

默认OpenFeign客户端等待60秒钟,但是服务端处理超过规定时间会导致Feign客户端返回报错。

为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制,默认60秒太长或者业务时间太短都不好

yml文件中开启配置:

connectTimeout 连接超时时间

readTimeout 请求处理超时时间

image-20260109232216827

全局配置

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
openfeign:
client:
config:
default:
#连接超时时间
connectTimeout: 3000
#读取超时时间
readTimeout: 3000

全部的配置

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

spring:
application:
name: cloud-consumer-openfeign-order
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
default:
#连接超时时间
connectTimeout: 3000
#读取超时时间
readTimeout: 3000

指定配置

单个服务配置超时时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
spring:
cloud:
openfeign:
client:
config:

# default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
default:
#连接超时时间
connectTimeout: 4000
#读取超时时间
readTimeout: 4000

# 为serviceC这个服务单独配置超时时间,单个配置的超时时间将会覆盖全局配置

serviceC:
#连接超时时间
connectTimeout: 2000
#读取超时时间
readTimeout: 2000
1
2
3
4
5
6
7
8
9
10
spring:
cloud:
openfeign:
client:
config:
cloud-payment-service:
#连接超时时间
connectTimeout: 5000
#读取超时时间
readTimeout: 5000

全部内容

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

spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
#default:
#connectTimeout: 4000 #连接超时时间
#readTimeout: 4000 #读取超时时间
cloud-payment-service:
connectTimeout: 8000 #连接超时时间
readTimeout: 8000 #读取超时时间

2、OpenFeign重试机制

步骤

默认重试是关闭的,给了默认值

image-20260110000715003

开启重试功能

新增配置类FeignConfig并修改Retryer配置
1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class FeignConfig
{
@Bean
public Retryer myRetryer()
{
//return Retryer.NEVER_RETRY; //Feign默认配置是不走重试策略的

//最大请求次数为3(1+2),初始间隔时间为100ms,重试间最大间隔时间为1s
return new Retryer.Default(100,1,3);
}
}

如果你觉得效果不明显的同学,后续演示feign 日志功能的时候再演示,

目前控制台没有看到3次重试过程,只看到结果,正常的,正确的\,是feign的日志打印问题

3、OpenFeign默认HttpClient修改

是什么

OpenFeign中http client

如果不做特殊配置,OpenFeign默认使用JDK自带的HttpURLConnection发送HTTP请求,

由于默认HttpURLConnection没有连接池、性能和效率比较低,如果采用默认,性能上不是最牛B的,所以加到最大。

Apache HttpClient5替换

为什么

image-20260110002130716

修改微服务feign90
FeignConfig类里面将Retryer属性修改为默认
1
2
3
4
5
6
7
8
9
10
@Configuration
public class FeignConfig
{
@Bean
public Retryer myRetryer()
{
return Retryer.NEVER_RETRY; //Feign默认配置是不走重试策略的
}
}

POM修改
1
2
3
4
5
6
7
8
9
10
11
12
<!-- httpclient5-->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3</version>
</dependency>
<!-- feign-hc5-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hc5</artifactId>
<version>13.1</version>
</dependency>
配置
1
2
3
4
5
6
7
#  Apache HttpClient5 配置开启
spring:
cloud:
openfeign:
httpclient:
hc5:
enabled: true

全部配置

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

spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
default:
connectTimeout: 4000 #连接超时时间
readTimeout: 4000 #读取超时时间
httpclient:
hc5:
enabled: true
#cloud-payment-service:
#connectTimeout: 4000 #连接超时时间
#readTimeout: 4000 #读取超时时间

4、OpenFeign请求/响应压缩

是什么

对请求和响应进行GZIP压缩

Spring Cloud OpenFeign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。

通过下面的两个参数设置,就能开启请求与相应的压缩功能:

spring.cloud.openfeign.compression.request.enabled=true

spring.cloud.openfeign.compression.response.enabled=true

细粒度化设置

对请求压缩做一些更细致的设置,比如下面的配置内容指定压缩的请求数据类型并设置了请求压缩的大小下限,

只有超过这个大小的请求才会进行压缩:

spring.cloud.openfeign.compression.request.enabled=true

spring.cloud.openfeign.compression.request.mime-types=text/xml,application/xml,application/json #触发压缩数据类型

spring.cloud.openfeign.compression.request.min-request-size=2048 #最小触发压缩的大小

配置

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

spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
default:
#cloud-payment-service:
#连接超时时间
connectTimeout: 4000
#读取超时时间
readTimeout: 4000
httpclient:
hc5:
enabled: true
compression:
request:
enabled: true
min-request-size: 2048 #最小触发压缩的大小
mime-types: text/xml,application/xml,application/json #触发压缩数据类型
response:
enabled: true

5、OpenFeign日志打印功能

是什么

Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,

从而了解 Feign 中 Http 请求的细节,

说白了就是对Feign接口的调用情况进行监控和输出

日志级别

1
2
3
4
NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码及执行时间;
HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class FeignConfig
{
@Bean
public Retryer myRetryer()
{
return Retryer.NEVER_RETRY; //默认
}

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

YML文件里需要开启日志的Feign客户端

公式(三段):logging.level + 含有@FeignClient注解的完整带包名的接口名+debug

1
2
3
4
5
6
7
8
# feign日志以什么级别监控哪个接口
logging:
level:
com:
bitzh:
cloud:
apis:
PayFeignApi: debug

后台日志查看

带着压缩调用

image-20260110004515774

去掉压缩调用

image-20260110004533178

补充实验,重试机制控制台看到3次过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class FeignConfig
{
@Bean
public Retryer myRetryer()
{
//最大请求次数为3(1+2),初始间隔时间为100ms,重试间最大间隔时间为1s
return new Retryer.Default(100,1,3);
}
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
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
server:
port: 80

spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
default:
#cloud-payment-service:
#连接超时时间
connectTimeout: 2000
#读取超时时间
readTimeout: 2000
httpclient:
hc5:
enabled: true
compression:
request:
enabled: true
min-request-size: 2048
mime-types: text/xml,application/xml,application/json
response:
enabled: true

# feign日志以什么级别监控哪个接口
logging:
level:
com:
bitzh:
cloud:
apis:
PayFeignApi: debug

image-20260110004645751

最终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
42
server:
port: 80

spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
default:
connectTimeout: 2000 #连接超时时间
readTimeout: 2000 #读取超时时间
httpclient:
hc5:
enabled: true
compression:
request:
enabled: true
min-request-size: 2048
mime-types: text/xml,application/xml,application/json
response:
enabled: true
#cloud-payment-service:
#connectTimeout: 4000 #连接超时时间
#readTimeout: 4000 #读取超时时间

# feign日志以什么级别监控哪个接口
logging:
level:
com:
atguigu:
cloud:
apis:
PayFeignApi: debug

OpenFeign和Sentinel集成实现fallback服务降级

这个在后面的springcloud alibaba 篇章

7、CircuitBreaker断路器

Hystrix目前也进入维护模式

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

了解一下即可,2024年了不再使用Hystrix

未来替代方案:Resilience4J

概述

分布式系统面临的问题

分布式系统面临的问题

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

服务雪崩

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

所以,

通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

需求

问题:

禁止服务雪崩故障

解决:

- 有问题的节点,快速熔断(快速返回失败处理或者返回默认兜底数据【服务降级】)。

“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

如何解决问题

服务熔断
服务降级
服务限流
服务限时
服务预热
接近实时的监控
兜底的处理动作

一句话,出故障了“保险丝”跳闸,别把整个家给烧了,😄

Circuit Breaker是什么

这是一套规范,抽象实现,支持用Resilience4J实现和Spring Retry。这里我们用Resilience4J。

实现原理

CircuitBreaker的目的是保护分布式系统免受故障和异常,提高系统的可用性和健壮性。

当一个组件或服务出现故障时,CircuitBreaker会迅速切换到开放OPEN状态(保险丝跳闸断电),阻止请求发送到该组件或服务从而避免更多的请求发送到该组件或服务。这可以减少对该组件或服务的负载,防止该组件或服务进一步崩溃,并使整个系统能够继续正常运行。同时,CircuitBreaker还可以提高系统的可用性和健壮性,因为它可以在分布式系统的各个组件之间自动切换,从而避免单点故障的问题。

image-20260110014458140

一句话

Circuit Breaker只是一套规范和接口,落地实现者是Resilience4J

Resilience4J

是什么

image-20260110014601849

能干嘛

image-20260110014621645

缓存就用redis,重试用guava(单点重试)OpenFeign(跨节点重试),超时处理OpenFeign里面内置了。

案例实战

熔断 CircuitBreaker(服务熔断 + 服务降级)

断路器3大状态

image-20260110020327694

断路器3大状态之间的转换

image-20260110020350206

断路器所有配置参数参考
配置项名 说明
failure-rate-threshold 以百分比配置失败率峰值
sliding-window-type 断路器的滑动窗口期类型,可以基于“次数”(COUNT_BASED)或者“时间”(TIME_BASED)进行熔断,默认是 COUNT_BASED
sliding-window-size 若 COUNT_BASED,则10次调用中有50%失败(即5次)打开熔断断路器;若为 TIME_BASED 则,此时还有额外的两个设置属性,含义为:在N秒内(sliding-window-size)100%(slow-call-rate-threshold)的请求超过N秒(slow-call-duration-threshold)打开断路器
slowCallRateThreshold 以百分比的方式配置,断路器把调用时间大于 slowCallDurationThreshold 的调用视为慢调用,当慢调用比例大于等于峰值时,断路器开启,并进入服务降级
slowCallDurationThreshold 配置调用时间的峰值,高于该峰值的视为慢调用
permitted-number-of-calls-in-half-open-state 运行断路器在 HALF_OPEN 状态下时进行 N 次调用,如果故障或慢速调用仍然高于阈值,断路器再次进入打开状态
minimum-number-of-calls 在每个滑动窗口期样本数,配置断路器计算错误率或者慢调用率的最小调用数。比如设置为5意味着,在计算故障率之前,必须至少调用5次。如果只记录了4次,即使4次都失败了,断路器也不会进入到打开状态
wait-duration-in-open-state 从 OPEN 到 HALF_OPEN 状态需要等待的时间
熔断+降级案例需求说明

6次访问中当执行方法的失败率达到50%CircuitBreaker将进入开启OPEN状态(保险丝跳闸断电)拒绝所有请求。
等待5秒后,CircuitBreaker将自动从开启OPEN状态过渡到半开HALF_OPEN状态,允许一些请求通过以测试服务是否恢复正常。 如还是异常CircuitBreaker将重新进入开启OPEN状态;如正常将进入关闭CLOSE闭合状态恢复正常处理请求。

具体时间和频次等属性见具体实际案例,这里只是作为case举例讲解

image-20260110021625279

判定方式有按照次数有按照时间,也就是说6次3次失败,或者6秒中3秒失败。

按照COUNT_BASED 计数的滑动窗口
修改cloud-provider-payment8001

新建PayCircuitController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
public class PayCircuitController
{
//=========Resilience4j CircuitBreaker 的例子
@GetMapping(value = "/pay/circuit/{id}")
public String myCircuit(@PathVariable("id") Integer id)
{
if(id == -4) throw new RuntimeException("----circuit id 不能负数");
if(id == 9999){
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
}
return "Hello, circuit! inputId: "+id+" \t " + IdUtil.simpleUUID();
}
}

修改对外暴露的接口PayFeignApi接口

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
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi
{
/**
* 新增一条支付相关流水记录
* @param payDTO
* @return
*/
@PostMapping("/pay/add")
public ResultData addPay(@RequestBody PayDTO payDTO);

/**
* 按照主键记录查询支付流水信息
* @param id
* @return
*/
@GetMapping("/pay/get/{id}")
public ResultData getPayInfo(@PathVariable("id") Integer id);

/**
* openfeign天然支持负载均衡演示
* @return
*/
@GetMapping(value = "/pay/get/info")
public String mylb();

/**
* Resilience4j CircuitBreaker 的例子
* @param id
* @return
*/
@GetMapping(value = "/pay/circuit/{id}")
public String myCircuit(@PathVariable("id") Integer id);
}

保险丝装在调用者,所以修改cloud-consumer-feign-order80

1
2
3
4
5
6
7
8
9
10
<!--resilience4j-circuitbreaker-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!-- 由于断路保护等需要AOP实现,所以必须导入AOP包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

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

spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
default:
#cloud-payment-service:
#连接超时时间,为避免演示出错,讲解完本次内容后设置为20秒
connectTimeout: 20000
#读取超时时间,为避免演示出错,讲解完本次内容后设置为20秒
readTimeout: 20000
#开启httpclient5
httpclient:
hc5:
enabled: true
#开启压缩特性
compression:
request:
enabled: true
min-request-size: 2048
mime-types: text/xml,application/xml,application/json
response:
enabled: true
# 开启circuitbreaker和分组激活 spring.cloud.openfeign.circuitbreaker.enabled
circuitbreaker:
enabled: true
group:
enabled: true #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后


# feign日志以什么级别监控哪个接口
#logging:
# level:
# com:
# bitzh:
# cloud:
# apis:
# PayFeignApi: debug

# Resilience4j CircuitBreaker 按照次数:COUNT_BASED 的例子
# 6次访问中当执行方法的失败率达到50%时CircuitBreaker将进入开启OPEN状态(保险丝跳闸断电)拒绝所有请求。
# 等待5秒后,CircuitBreaker 将自动从开启OPEN状态过渡到半开HALF_OPEN状态,允许一些请求通过以测试服务是否恢复正常。
# 如还是异常CircuitBreaker 将重新进入开启OPEN状态;如正常将进入关闭CLOSE闭合状态恢复正常处理请求。
resilience4j:
circuitbreaker:
configs:
default:
failureRateThreshold: 50 #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。
slidingWindowType: COUNT_BASED # 滑动窗口的类型
slidingWindowSize: 6 #滑动窗⼝的⼤⼩配置COUNT_BASED表示6个请求,配置TIME_BASED表示6秒
minimumNumberOfCalls: 6 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。如果minimumNumberOfCalls为10,则必须最少记录10个样本,然后才能计算失败率。如果只记录了9次调用,即使所有9次调用都失败,断路器也不会开启。
automaticTransitionFromOpenToHalfOpenEnabled: true # 是否启用自动从开启状态过渡到半开状态,默认值为true。如果启用,CircuitBreaker将自动从开启状态过渡到半开状态,并允许一些请求通过以测试服务是否恢复正常
waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间
permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数,默认值为10。在半开状态下,CircuitBreaker将允许最多permittedNumberOfCallsInHalfOpenState个请求通过,如果其中有任何一个请求失败,CircuitBreaker将重新进入开启状态。
recordExceptions:
- java.lang.Exception
instances:
cloud-payment-service:
baseConfig: default

新建OrderCircuitController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
public class OrderCircuitController
{
@Resource
private PayFeignApi payFeignApi;

@GetMapping(value = "/feign/pay/circuit/{id}")
@CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "myCircuitFallback")
public String myCircuitBreaker(@PathVariable("id") Integer id)
{
return payFeignApi.myCircuit(id);
}
//myCircuitFallback就是服务降级后的兜底处理方法
public String myCircuitFallback(Integer id,Throwable t) {
// 这里是容错处理逻辑,返回备用结果
return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
}
}

测试

访问错误次数多了,就算范文正常的也是错误的响应过5秒后恢复。

按照TIME_BASED 时间的滑动窗口

image-20260110183737723

原理:不用深究,然后为了计算,每个桶里面有三个整形,失败调用数,慢调用数,总调用数、还有一个long类型变量,存储所有调用的响应时间。

修改cloud-consumer-feign-order80

写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
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
server:
port: 80

spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
default:
#cloud-payment-service:
#连接超时时间,为避免演示出错,讲解完本次内容后设置为20秒
connectTimeout: 20000
#读取超时时间,为避免演示出错,讲解完本次内容后设置为20秒
readTimeout: 20000
#开启httpclient5
httpclient:
hc5:
enabled: true
#开启压缩特性
compression:
request:
enabled: true
min-request-size: 2048
mime-types: text/xml,application/xml,application/json
response:
enabled: true
#开启circuitbreaker和分组激活
circuitbreaker:
enabled: true
group:
enabled: true #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后


# feign日志以什么级别监控哪个接口
logging:
level:
com:
atguigu:
cloud:
apis:
PayFeignApi: debug

# Resilience4j CircuitBreaker 按照时间:TIME_BASED 的例子
resilience4j:
timelimiter:
configs:
default:
timeout-duration: 10s #神坑的位置,timelimiter 默认限制远程1s,超于1s就超时异常,配置了降级,就走降级逻辑
circuitbreaker:
configs:
default:
failureRateThreshold: 50 #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。
slowCallDurationThreshold: 2s #慢调用时间阈值,高于这个阈值的视为慢调用并增加慢调用比例。
slowCallRateThreshold: 30 #慢调用百分比峰值,断路器把调用时间⼤于slowCallDurationThreshold,视为慢调用,当慢调用比例高于阈值,断路器打开,并开启服务降级
slidingWindowType: TIME_BASED # 滑动窗口的类型
slidingWindowSize: 2 #滑动窗口的大小配置,配置TIME_BASED表示2秒
minimumNumberOfCalls: 2 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。
permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数,默认值为10。
waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间
recordExceptions:
- java.lang.Exception
instances:
cloud-payment-service:
baseConfig: default

为了避免影响实验效果,记得关闭FeignConfig自己写的重试3次

测试(慢查询)

一次超时,一次正常访问,同时进行

小总结

断路器开启或者关闭的条件

image-20260111052237086

当满足一定的峰值和失败率达到一定条件后,断路器将会进入OPEN状态(保险丝跳闸),服务熔断

当OPEN的时候,所有请求都不会调用主业务逻辑方法,而是直接走fallbackmethod兜底背锅方法,服务降级。

一段时间之后,这个时候断路器会从OPEN进入到HALF_OPEN半开状态,会放几个请求过去探探链路是否通?

如果成功,断路器会关闭CLOSE(类似保险丝闭合,恢复可用)

如果失败,继续开启,重复上述。

不要混用,推荐按照调用次数。

隔离(BulkHead)

是什么

bulkhead(船的)舱壁/(飞机的)隔板

板来自造船行业,床仓内部一般会分成很多小隔舱,一旦一个隔舱漏水因为隔板的存在而不至于影响其它隔舱和整体船。

image-20260111053000936

用来限并发的。

能干嘛

一来隔离 & 负载保护:用来限制对于下游服务的最大并发数量的限制

Resilience4J提供了如下两种隔离的实现方式,可以限制并发执行的数量
实现SemaphoreBulkhead(信号量舱壁)

这个使用了信号量

信号量原理

基本上就是我们JUC信号灯内容的同样思想

信号量舱壁(SemaphoreBulkhead)原理

当信号量有空闲时,进入系统的请求会直接获取信号量并开始业务处理。

当信号量全被占用时,接下来的请求将会进入阻塞状态,SemaphoreBulkhead提供了一个阻塞计时器,

如果阻塞状态的请求在阻塞计时内无法获取到信号量则系统会拒绝这些请求。

若请求在阻塞计时内获取到了信号量,那将直接获取信号量并执行相应的业务处理。

源码分析

image-20260111060722914

cloud-provider-payment8001支付微服务

修改PayCircuitController

1
2
3
4
5
6
7
8
9
10
11
12
13
//=========Resilience4j bulkhead 的例子
@GetMapping(value = "/pay/bulkhead/{id}")
public String myBulkhead(@PathVariable("id") Integer id)
{
if(id == -4) throw new RuntimeException("----bulkhead id 不能-4");

if(id == 9999)
{
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
}

return "Hello, bulkhead! inputId: "+id+" \t " + IdUtil.simpleUUID();
}

PayFeignApi接口新增舱壁api方法

1
2
3
4
5
6
7
/**
* Resilience4j Bulkhead 的例子
* @param id
* @return
*/
@GetMapping(value = "/pay/bulkhead/{id}")
public String myBulkhead(@PathVariable("id") Integer id);

修改 cloud-consumer-feign-order80

POM

1
2
3
4
5
<!--resilience4j-bulkhead-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</dependency>

YML

示例

image-20260111061745375

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

spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
default:
#cloud-payment-service:
#连接超时时间,为避免演示出错,讲解完本次内容后设置为20秒
connectTimeout: 20000
#读取超时时间,为避免演示出错,讲解完本次内容后设置为20秒
readTimeout: 20000
#开启httpclient5
httpclient:
hc5:
enabled: true
#开启压缩特性
compression:
request:
enabled: true
min-request-size: 2048
mime-types: text/xml,application/xml,application/json
response:
enabled: true
#开启circuitbreaker和分组激活
circuitbreaker:
enabled: true
group:
enabled: true #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后


# feign日志以什么级别监控哪个接口
logging:
level:
com:
bitzh:
cloud:
apis:
PayFeignApi: debug

####resilience4j bulkhead 的例子
resilience4j:
bulkhead:
configs:
default:
maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量
maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
instances:
cloud-payment-service:
baseConfig: default
timelimiter:
configs:
default:
timeout-duration: 20s

业务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
*(船的)舱壁,隔离
* @param id
* @return
*/
@GetMapping(value = "/feign/pay/bulkhead/{id}")
@Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE)
public String myBulkhead(@PathVariable("id") Integer id)
{
return payFeignApi.myBulkhead(id);
}
public String myBulkheadFallback(Throwable t)
{
return "myBulkheadFallback,隔板超出最大数量限制,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
}

@Bulkhead Bulkhead.Type.SEMAPHORE

实现FixedThreadPoolBulkhead(固定线程池舱壁)

这个使用有界队列和固定大小线程池

概述

基本上就是我们JUC-线程池内容的同样思想

image-20260111163134319

固定线程池舱壁(FixedThreadPoolBulkhead)

FixedThreadPoolBulkhead的功能与SemaphoreBulkhead一样也是用于限制并发执行的次数的,但是二者的实现原理存在差别而且表现效果也存在细微的差别。FixedThreadPoolBulkhead使用一个固定线程池和一个等待队列来实现舱壁。

当线程池中存在空闲时,则此时进入系统的请求将直接进入线程池开启新线程或使用空闲线程来处理请求。

当线程池中无空闲时时,接下来的请求将进入等待队列,

若等待队列仍然无剩余空间时接下来的请求将直接被拒绝,

在队列中的请求等待线程池出现空闲时,将进入线程池进行业务处理。

另外:ThreadPoolBulkhead只对CompletableFuture方法有效,所以我们必创建返回CompletableFuture类型的方法

信号量舱壁就是利用调用方的线程,对并发量限制,固定线程池舱壁就是服务器里面专属线程池,和调用方线程隔离

想要理解具体源码,去JUC里面看了

这里了解如何使用

修改cloud-consumer-feign-order80

POM

1
2
3
4
5
<!--resilience4j-bulkhead-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</dependency>

YML

image-20260111164117503

image-20260111164506626

这里讲解原理,也就是说线程进入CorePool如果多了就进入到阻塞队列,如果阻塞队列满了就扩Pool,然后到maximumPool。

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

spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
default:
#cloud-payment-service:
#连接超时时间,为避免演示出错,讲解完本次内容后设置为20秒
connectTimeout: 20000
#读取超时时间,为避免演示出错,讲解完本次内容后设置为20秒
readTimeout: 20000
#开启httpclient5
httpclient:
hc5:
enabled: true
#开启压缩特性
compression:
request:
enabled: true
min-request-size: 2048
mime-types: text/xml,application/xml,application/json
response:
enabled: true
#开启circuitbreaker和分组激活
circuitbreaker:
enabled: true
# group:
# enabled: true # 演示Bulkhead.Type.THREADPOOL时spring.cloud.openfeign.circuitbreaker.group.enabled

#设为false新启线程和原来主线程脱离了。



# feign日志以什么级别监控哪个接口
logging:
level:
com:
bitzh:
cloud:
apis:
PayFeignApi: debug

####resilience4j bulkhead -THREADPOOL的例子
resilience4j:
timelimiter:
configs:
default:
timeout-duration: 10s #timelimiter默认限制远程1s,超过报错不好演示效果所以加上10秒
thread-pool-bulkhead:
configs:
default:
core-thread-pool-size: 1
max-thread-pool-size: 1
queue-capacity: 1
#所以这里最大并发数就是2个
instances:
cloud-payment-service:
baseConfig: default
# spring.cloud.openfeign.circuitbreaker.group.enabled 请设置为false 新启线程和原来主线程脱离

image-20260111164158477

order80的controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* (船的)舱壁,隔离,THREADPOOL
* @param id
* @return
*/
@GetMapping(value = "/feign/pay/bulkhead/{id}")
@Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadPoolFallback",type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<String> myBulkheadTHREADPOOL(@PathVariable("id") Integer id)
{
System.out.println(Thread.currentThread().getName()+"\t"+"enter the method!!!");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t"+"exist the method!!!");

return CompletableFuture.supplyAsync(() -> payFeignApi.myBulkhead(id) + "\t" + " Bulkhead.Type.THREADPOOL");
}
public CompletableFuture<String> myBulkheadPoolFallback(Integer id,Throwable t)
{
return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~");
}

限流

限流:限制 “单位时间内进入大楼的总人数”,防止大楼入口拥堵。

舱壁:把大楼分成多个独立房间,限制 “每个房间的人数”,防止一个房间爆满影响其他房间。

限流 就是限制最大访问流量。系统能提供的最大并发是有限的,同时来的请求又太多,就需要限流。

比如商城秒杀业务,瞬时大量请求涌入,服务器忙不过就只好排队限流了,和去景点排队买票和去医院办理业务排队等号道理相同。

所谓限流,就是通过对并发访问/请求进行限速,或者对一个时间窗口内的请

求进行限速,以保护应用系统,一旦达到限制速率则可以拒绝服务、排队或

等待、降级等处理。

限流是频率控制

常见的限流算法
漏斗算法

一个固定容量的漏桶,按照设定常量固定速率流出水滴,类似医院打吊针,不管你源头流量多大,我设定匀速流出。 如果流入水滴超出了桶的容量,则流入的水滴将会溢出了(被丢弃),而漏桶容量是不变的。

image-20260112002111218

缺点:这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发(burst)到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率

漏桶算法(Leaky Bucket)在处理突发(bursty)流量时,无法灵活应对短时间内的大量请求,容易造成不必要的拒绝或延迟,从而降低系统对合法突发流量的利用率和响应效率。

image-20260112003939773

令牌桶算法(重点)

SpringCloud默认使用该算法

image-20260112004632214

滚动时间窗口

允许固定数量的请求进入(比如1秒取4个数据相加,超过25值就over)超过数量就拒绝或者排队,等下一个时间段进入。

由于是在一个时间间隔内进行限制,如果用户在上个时间间隔结束前请求(但没有超过限制),同时在当前时间间隔刚开始请求(同样没超过限制),在各自的时间间隔内,这些请求都是正常的。下图统计了3次,but……

image-20260112004801480

缺点:间隔临界的一段时间内的请求就会超过系统限制,可能导致系统被压垮

image-20260112004851173

假如设定1分钟最多可以请求100次某个接口,如12:00:00-12:00:59时间段内没有数据请求但12:00:59-12:01:00时间段内突然并发100次请求,紧接着瞬间跨入下一个计数周期计数器清零;在12:01:00-12:01:01内又有100次请求。那么也就是说在时间临界点左右可能同时有2倍的峰值进行请求,从而造成后台处理请求加倍过载的bug,导致系统运营能力不足,甚至导致系统崩溃,/(ㄒoㄒ)/~~

1
2
3
4
5
假设限流是 100 人/分钟。
00:59 秒: 100 人冲进来。滚动窗口保安看了一眼表:“嗯,这一分钟正好 100 人,准入!”
01:00 秒: 窗口重置,计数器清零。
01:01 秒: 又冲进来 100 人。滚动窗口保安又看了一眼表:“这一分钟刚开始,现在才 100 人,准入!”
结果: 保安在 2 秒内放进了 200 人。后端服务器直接被压垮了。
滑动时间窗口

滑动时间窗口(sliding time window)

顾名思义,该时间窗口是滑动的。所以,从概念上讲,这里有两个方面的概念需要理解:

- 窗口:需要定义窗口的大小

- 滑动:需要定义在窗口中滑动的大小,但理论上讲滑动的大小不能超过窗口大小

滑动窗口算法是把固定时间片进行划分并且随着时间移动,移动方式为开始时间点变为时间列表中的第2个时间点,结束时间点增加一个时间点,

不断重复,通过这种方式可以巧妙的避开计数器的临界点的问题。下图统计了5次

image-20260112013020379

1
2
3
4
5
6
7
滑动窗口:那个“时刻警惕”的保安
假设限流依然是 100 人/分钟,但我们用滑动窗口(每秒移动一次)。
00:59 秒: 100 人冲进来。保安看了一下过去 60 秒(00:00-01:00),正好 100 人,准入。
01:00 秒: 保安的窗口移动了,现在看的是(00:01-01:01)。
01:01 秒: 又有 1 人想冲进来。
关键时刻: 保安低头看表,发现 00:01 到 01:01 这过去的一分钟里,已经有 100 人了!
保安反应: “停!这 1 分钟名额满了,你不能进去!”(直接丢弃请求或报 429 错误)。
实战

cloud-provider-payment8001支付微服务 修改PayCircuitController新增myRatelimit方法

1
2
3
4
5
6
//=========Resilience4j ratelimit 的例子
@GetMapping(value = "/pay/ratelimit/{id}")
public String myRatelimit(@PathVariable("id") Integer id)
{
return "Hello, myRatelimit欢迎到来 inputId: "+id+" \t " + IdUtil.simpleUUID();
}

PayFeignApi接口新增限流api方法

1
2
3
4
5
6
7
/**
* Resilience4j Ratelimit 的例子
* @param id
* @return
*/
@GetMapping(value = "/pay/ratelimit/{id}")
public String myRatelimit(@PathVariable("id") Integer id);

修改 cloud-consumer-feign-order80

POM

1
2
3
4
5
<!--resilience4j-ratelimiter-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</dependency>

YML

1
2
3
4
5
6
7
8
9
10
11
####resilience4j ratelimiter 限流的例子
resilience4j:
ratelimiter:
configs:
default:
limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数
limitRefreshPeriod: 1s # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriod
timeout-duration: 1 # 线程等待权限的默认等待时间
instances:
cloud-payment-service:
baseConfig: default

order的controller

1
2
3
4
5
6
7
8
9
10
@GetMapping(value = "/feign/pay/ratelimit/{id}")
@RateLimiter(name = "cloud-payment-service",fallbackMethod = "myRatelimitFallback")
public String myBulkhead(@PathVariable("id") Integer id)
{
return payFeignApi.myRatelimit(id);
}
public String myRatelimitFallback(Integer id,Throwable t)
{
return "你被限流了,禁止访问/(ㄒoㄒ)/~~";
}

8、Sleuth(Micrometer)+ZipKin分布式链路追踪

❌️Sleuth目前也进入维护模式

Sleuth未来替换方案:Micrometer Tracing

分布式链路追踪概述

为什么会出现这个技术,需要解决什么问题

问题

在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。

image-20260112023002044

在分布式与微服务场景下,我们需要解决如下问题:

在大规模分布式与微服务集群下,如何实时观测系统的整体调用链路情况。

在大规模分布式与微服务集群下,如何快速发现并定位到问题。

在大规模分布式与微服务集群下,如何尽可能精确的判断故障对系统的影响范围与影响程度。

在大规模分布式与微服务集群下,如何尽可能精确的梳理出服务之间的依赖关系,并判断出服务之间的依赖关系是否合理。

在大规模分布式与微服务集群下,如何尽可能精确的分析整个系统调用链路的性能与瓶颈点。

在大规模分布式与微服务集群下,如何尽可能精确的分析系统的存储瓶颈与容量规划。

上述问题就是我们的落地议题答案:

分布式链路追踪技术要解决的问题,分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。

Micrometer Tracing

zipkin是啥

Spring Cloud Sleuth (micrometer)提供了一套完整的分布式链路追踪解决方案且兼容支持了zipkin展现

image-20260112140645022

Sleuth负责收集,zipkin负责展现

小总结

将一次分布式请求还原成调用链路,进行日志记录和性能监控,并将一次分布式请求的调用情况集中web展示。

行业内比较成熟的其他分布式链路追踪技术解决方案

image-20260112140902801

如果不想用 zipkin 推荐用 skywalking

分布式链路追踪原理

假定3个微服务调用链路

Service1 调用 2,2调用3和4

上一步的完整的调用链路

1
2
3
那么一条链路追踪会在每个服务调用的时候加上Trace ID 和 Span ID
链路通过TraceId唯一标识,
Span标识发起的请求信息,各span通过parent id 关联起来 (Span:表示调用链路来源,通俗的理解span就是一次请求信息)

image-20260112143807061

上面都是请求,下面都是响应。

简化图:

image-20260112144357391

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

节点序号 Span ID Parent ID 节点描述 涉及服务
1 A null Service 1 接收到请求 Service 1
2 B A Service 1 发送请求到 Service 2 返回响应给 Service 1 的过程 Service 1、Service 2
3 C B Service 2 的中间解决过程 Service 2
4 D C Service 2 发送请求到 Service 3 返回响应给 Service 2 的过程 Service 2、Service 3
5 E D Service 3 的中间解决过程 Service 3
6 F C Service 3 发送请求到 Service 4 返回响应给 Service 3 的过程 Service 3、Service 4
7 G F Service 4 的中间解决过程 Service 4

Zipkin

Zipkin是一种分布式链路跟踪系统图形化的工具,Zipkin 是 Twitter 开源的分布式跟踪系统,能够收集微服务运行过程中的实时调用链路信息,并能够将这些调用链路信息展示到Web图形化界面上供开发人员分析,开发人员能够从ZipKin中分析出调用链路中的性能瓶颈,识别出存在问题的应用程序,进而定位问题和解决问题。

下载+安装+运行

下载职业

https://zipkin.io/pages/quickstart

支持3个方式

image-20260112145627177

2023.12,版本名称

zipkin-server-3.0.0-rc0-exec.jar

1
2
3
4
5
# 方式1:直接下载指定版本JAR包(推荐,跳过默认脚本的版本自动选择)
curl -sSL -o zipkin-server-3.0.0-rc0-exec.jar https://repo1.maven.org/maven2/io/zipkin/zipkin-server/3.0.0-rc0/zipkin-server-3.0.0-rc0-exec.jar

# 方式2:启动指定版本的Zipkin
java -jar zipkin-server-3.0.0-rc0-exec.jar

Micrometer+ZipKin搭建链路监控案例步骤

Micrometer 数据采样

ZipKin 图形展示

步骤

总体POM父工程

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
<!--micrometer-tracing-bom导入链路追踪版本中心  1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bom</artifactId>
<version>${micrometer-tracing.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--micrometer-tracing指标追踪 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
<version>${micrometer-tracing.version}</version>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
<version>${micrometer-tracing.version}</version>
</dependency>
<!--micrometer-observation 4-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
<version>${micrometer-observation.version}</version>
</dependency>
<!--feign-micrometer 5-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
<version>${feign-micrometer.version}</version>
</dependency>
<!--zipkin-reporter-brave 6-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
<version>${zipkin-reporter-brave.version}</version>
</dependency>

这些 jar 包什么意思?

由于Micrometer Tracing是一个门面工具自身并没有实现完整的链路追踪系统,具体的链路追踪另外需要引入的是第三方链路追踪系统的依赖:

序号 组件名称 说明
1 micrometer-tracing-bom 导入链路追踪的版本中心(Bill of Materials),用于统一管理 Micrometer Tracing 相关依赖的版本,实现体系化的依赖管理。
2 micrometer-tracing 提供分布式链路追踪的核心抽象和基础能力,是 Micrometer 生态中用于跟踪(Tracing)的主模块。
3 micrometer-tracing-bridge-brave Micrometer 的桥接模块,用于与分布式追踪工具 Brave 集成。Brave 是一个开源的分布式追踪库,通过在请求头中传递“跟踪上下文”(Trace Context),记录请求在各服务间的流转路径、耗时等信息,实现端到端的链路追踪。
4 micrometer-observation 基于 Micrometer 的观测(Observation)模块,提供统一的编程模型,用于收集指标(Metrics)、日志(Logs)和追踪(Traces)等可观测性数据。
5 feign-micrometer 为 Feign HTTP 客户端提供的 Micrometer 集成模块,自动收集 Feign 发起的远程调用的指标和追踪数据(如请求次数、延迟、成功率等)。
6 zipkin-reporter-brave 将 Brave 采集的分布式追踪数据上报到 Zipkin 可视化追踪系统的报告器(Reporter)库,实现追踪数据的集中存储与展示。

补充包:spring-boot-starter-actuator SpringBoot框架的一个模块用于监视和管理应用程序

若是没有使用父工程的要添加开启spring boot监控和管理工具的依赖

总POM

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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
 搜索

便笺
<?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>

<groupId>com.atguigu.cloud</groupId>
<artifactId>mscloudV6</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>cloud-provider-payment8001</module>
<module>cloud-consumer-order80</module>
<module>cloud-api-commons</module>
<module>cloud-provider-payment8002</module>
<module>cloud-consumer-feign-order80</module>
</modules>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hutool.version>5.8.22</hutool.version>
<lombok.version>1.18.26</lombok.version>
<druid.version>1.1.20</druid.version>
<mybatis.springboot.version>3.0.2</mybatis.springboot.version>
<mysql.version>8.0.11</mysql.version>
<swagger3.version>2.2.0</swagger3.version>
<mapper.version>4.2.3</mapper.version>
<fastjson2.version>2.0.41</fastjson2.version>
<persistence-api.version>1.0.2</persistence-api.version>
<spring.boot.test.version>3.1.5</spring.boot.test.version>
<spring.boot.version>3.2.0</spring.boot.version>
<spring.cloud.version>2023.0.0</spring.cloud.version>
<spring.cloud.alibaba.version>2022.0.0.0-RC2</spring.cloud.alibaba.version>
<micrometer-tracing.version>1.2.0</micrometer-tracing.version>
<micrometer-observation.version>1.12.0</micrometer-observation.version>
<feign-micrometer.version>12.5</feign-micrometer.version>
<zipkin-reporter-brave.version>2.17.0</zipkin-reporter-brave.version>
</properties>

<dependencyManagement>
<dependencies>
<!--springboot 3.2.0-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springcloud 2023.0.0-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springcloud alibaba 2022.0.0.0-RC2-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringBoot集成mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.springboot.version}</version>
</dependency>
<!--Mysql数据库驱动8 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--SpringBoot集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--通用Mapper4之tk.mybatis-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>${mapper.version}</version>
</dependency>
<!--persistence-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>${persistence-api.version}</version>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.40</version>
</dependency>
<!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${swagger3.version}</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<!-- spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.test.version}</version>
<scope>test</scope>
</dependency>
<!--micrometer-tracing-bom导入链路追踪版本中心 1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bom</artifactId>
<version>${micrometer-tracing.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--micrometer-tracing指标追踪 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
<version>${micrometer-tracing.version}</version>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
<version>${micrometer-tracing.version}</version>
</dependency>
<!--micrometer-observation 4-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
<version>${micrometer-observation.version}</version>
</dependency>
<!--feign-micrometer 5-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
<version>${feign-micrometer.version}</version>
</dependency>
<!--zipkin-reporter-brave 6-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
<version>${zipkin-reporter-brave.version}</version>
</dependency>

</dependencies>
</dependencyManagement>
</project>

有时候A调用B,有时候B调用A所以最好两个都加上。但是这次是单向,80 调用 8001

服务提供者8001

POM

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
<?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.bitzh</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<artifactId>cloud-provider-payment8001</artifactId>

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

<dependencies>
<!--micrometer-tracing指标追踪 1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!--micrometer-observation 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<!--feign-micrometer 4-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>
<!--zipkin-reporter-brave 5-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<!--SpringCloud consul config-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--SpringCloud consul discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.bitzh.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--SpringBoot通用依赖模块-->
<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>
<!--SpringBoot集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!--mybatis和springboot整合-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--Mysql数据库驱动8 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--persistence-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
</dependency>
<!--通用Mapper4-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

新建业务类PayMicrometerController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
public class PayMicrometerController
{
/**
* Micrometer(Sleuth)进行链路监控的例子
* @param id
* @return
*/
@GetMapping(value = "/pay/micrometer/{id}")
public String myMicrometer(@PathVariable("id") Integer id)
{
return "Hello, 欢迎到来myMicrometer inputId: "+id+" \t 服务返回:" + IdUtil.simpleUUID();
}
}

Api接口PayFeignApi

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
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi
{
/**
* 新增一条支付相关流水记录
* @param payDTO
* @return
*/
@PostMapping("/pay/add")
public ResultData addPay(@RequestBody PayDTO payDTO);

/**
* 按照主键记录查询支付流水信息
* @param id
* @return
*/
@GetMapping("/pay/get/{id}")
public ResultData getPayInfo(@PathVariable("id") Integer id);

/**
* openfeign天然支持负载均衡演示
* @return
*/
@GetMapping(value = "/pay/get/info")
public String mylb();

/**
* Resilience4j CircuitBreaker 的例子
* @param id
* @return
*/
@GetMapping(value = "/pay/circuit/{id}")
public String myCircuit(@PathVariable("id") Integer id);

/**
* Resilience4j Bulkhead 的例子
* @param id
* @return
*/
@GetMapping(value = "/pay/bulkhead/{id}")
public String myBulkhead(@PathVariable("id") Integer id);

/**
* Resilience4j Ratelimit 的例子
* @param id
* @return
*/
@GetMapping(value = "/pay/ratelimit/{id}")
public String myRatelimit(@PathVariable("id") Integer id);


/**
* Micrometer(Sleuth)进行链路监控的例子
* @param id
* @return
*/
@GetMapping(value = "/pay/micrometer/{id}")
public String myMicrometer(@PathVariable("id") Integer id);
}

服务调用者80

POM

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
<!--micrometer-tracing指标追踪  1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!--micrometer-observation 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<!--feign-micrometer 4-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>
<!--zipkin-reporter-brave 5-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>

总POM

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

便笺
<?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.bitzh.cloud</groupId>
<artifactId>mscloudV6</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<artifactId>cloud-consumer-feign-order80</artifactId>

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


<dependencies>
<!--micrometer-tracing指标追踪 1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!--micrometer-observation 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<!--feign-micrometer 4-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>
<!--zipkin-reporter-brave 5-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<!--resilience4j-ratelimiter-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</dependency>
<!--resilience4j-bulkhead-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</dependency>
<!--resilience4j-circuitbreaker-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!-- 由于断路保护等需要AOP实现,所以必须导入AOP包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- httpclient5-->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3</version>
</dependency>
<!-- feign-hc5-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hc5</artifactId>
<version>13.1</version>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--SpringCloud consul discovery-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.bitzh.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool-all-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--fastjson2-->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

yml

1
2
3
4
5
6
7
8
# zipkin图形展现地址和采样率设置
management:
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
tracing:
sampling:
probability: 1.0 #采样率默认为0.1(0.1就是10次只能有一次被记录下来),值越大收集越及时。

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@Slf4j
public class OrderMicrometerController
{
@Resource
private PayFeignApi payFeignApi;

@GetMapping(value = "/feign/micrometer/{id}")
public String myMicrometer(@PathVariable("id") Integer id)
{
return payFeignApi.myMicrometer(id);
}
}

测试

访问 http://localhost:9411 即可

9、Gateway新一代网关

是什么?为什么有了nginx还要有gateway,两个都要用还是只用一个?

是什么

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring6,Spring Boot 3和Project Reactor等技术。它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式,并为它们提供跨领域的关注点,例如:安全性、监控/度量和恢复能力。

体系定位

Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;

但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关SpringCloud Gateway替代Zuul,

那就是SpringCloud Gateway一句话:gateway是原zuul1.x版的替代

image-20260112182358045

微服务架构中网关在哪

image-20260112182429219

这里的负载均衡就是nginx,那么这里又会有疑问,那之前的loadbalancer不也是负载均衡吗?

组件 类型 工作位置 主要作用
Nginx L7 反向代理 + 负载均衡器(外部/边缘层) 客户端 ↔ 微服务集群之间(南北向流量 对外统一入口,将用户请求分发到后端多个微服务实例(如 user-service 的多个节点)
Spring Cloud LoadBalancer 客户端负载均衡器(内部/服务间) 微服务内部(东西向流量 当 Service A 调用 Service B 时,在 Service A 内部自动选择一个健康的 Service B 实例

OpenFeign也是内部的,nginx是客户端调用微服务集群之间的

能干嘛

  • 反向代理
  • 鉴权
  • 流量控制
  • 熔断
  • 日志监控

小总结

Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。 Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,从而加强安全保护。Spring Cloud Gateway本身也是一个微服务,需要注册进服务注册中心。

image-20260112191929165

Gateway三大核心

Route (路由)

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

Predicate(断言)

参考的是Java8的 java.util.function.Predicate 开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

Filter(过滤)

指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

总结

image-20260112193322894

web前端请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。

predicate就是我们的匹配条件;

filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

Gateway工作流程

image-20260112193513401

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

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

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

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

核心逻辑:路由转发 + 断言判断 + 执行过滤器链

入门配置

因为这个也是一个微服务,所以建Module ,改POM,写YML,主启动,业务类,测试

建Module

cloud-gateway9527

改POM

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
<?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.bitzh</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<groupId>com.bitzh.cloud</groupId>
<artifactId>cloud-gateway9527</artifactId>

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

<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--服务注册发现consul discovery,网关也要注册进服务注册中心统一管控-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 指标监控健康检查的actuator,网关是响应式编程删除掉spring-boot-starter-web dependency-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

写YML

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}

主启动

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableDiscoveryClient //服务注册和发现
public class Main9527
{
public static void main(String[] args)
{
SpringApplication.run(Main9527.class,args);
}
}

业务类

无,不写任何业务代码,网关和业务无关

测试

先启动8500服务中心Consul,在启动9527网关入驻

9527网关如何做路由映射

如何做路由映射

诉求

我们目前不想暴露8001端口,希望在8001真正的支付微服务外面套一层9527网关

8001新建PayGateWayController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class PayGateWayController
{
@Resource
PayService payService;

@GetMapping(value = "/pay/gateway/get/{id}")
public ResultData<Pay> getById(@PathVariable("id") Integer id)
{
Pay pay = payService.getById(id);
return ResultData.success(pay);
}

@GetMapping(value = "/pay/gateway/info")
public ResultData<String> getGatewayInfo()
{
return ResultData.success("gateway info test:"+ IdUtil.simpleUUID());
}
}

现在直接访问8001端口测试通过,说明调用没问题,下面开始映射

9527网关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: 9527

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由


- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

现在配好之后访问gateway就能成功访问到支付服务了。

测试2

之前9527到8001可以了,现在要测试80到9527

image-20260112235418865

我们启动80订单微服务,它从Consul注册中心通过微服务名称找到8001支付微服务进行调用,

80 → 9527 → 8001

要求访问9527网关后才能访问8001,如果我们此时启动80订单,可以做到吗?

修改PayFeignApi接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* GateWay进行网关测试案例01
* @param id
* @return
*/
@GetMapping(value = "/pay/gateway/get/{id}")
public ResultData getById(@PathVariable("id") Integer id);

/**
* GateWay进行网关测试案例02
* @return
*/
@GetMapping(value = "/pay/gateway/info")
public ResultData<String> getGatewayInfo();

修改feign-order80

新建OrderGateWayController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
public class OrderGateWayController
{
@Resource
private PayFeignApi payFeignApi;

@GetMapping(value = "/feign/pay/gateway/get/{id}")
public ResultData getById(@PathVariable("id") Integer id)
{
return payFeignApi.getById(id);
}

@GetMapping(value = "/feign/pay/gateway/info")
public ResultData<String> getGatewayInfo()
{
return payFeignApi.getGatewayInfo();
}
}

测试之后发现feign是通过服务名直接到,服务器里面了,绕开了网关。

正确做法

同一家公司自己人,系统内环境,直接找微服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@FeignClient(value = "cloud-payment-service")//自己人内部,自己访问自己,写微服务名字OK
public interface PayFeignApi
{
/**
* GateWay进行网关测试案例01
* @param id
* @return
*/
@GetMapping(value = "/pay/gateway/get/{id}")
public ResultData getById(@PathVariable("id") Integer id);

/**
* GateWay进行网关测试案例02
* @return
*/
@GetMapping(value = "/pay/gateway/info")
public ResultData<String> getGatewayInfo();
}

不同家公司有外人,系统外访问,先找网关再服务

image-20260113000344562

刷新feign接口jar包

也就是把feignApi接口改成网关的服务名

然后重启80微服务,这时候没有网关就会报错了。

还有问题:网关9527的Yml配置映射写死问题

image-20260113000813036

GateWay高级特性

Route以微服务名-动态获取服务URI

解决 uri 地址写死问题

9527修改前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: 9527

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

9527修改后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
server:
port: 9527

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

Predicate断言

是什么

image-20260113023841002

整体架构概述

image-20260113023950357

常用内置Route Predicate
配置语法总体概述

Shortcut

image-20260113025622383

用过滤器过滤名字,然后用减号来区别参数,参数值之间用 逗号分隔

Fully expanded

image-20260113025704614

用key value 键值对来配置,然后用args来确定值

常用的是shortcut

常用断言Api
  • After Route Predicate

时间断言,这个常用于秒杀,什么时间之后再放开等

image-20260113030644656

我们的问题是:上述这个After好懂,这个时间串串???对应的格式如何获得?

1
2
3
4
5
6
7
8
public class ZonedDateTimeDemo
{
public static void main(String[] args)
{
ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
System.out.println(zbj);
}
}

然后修改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
server:
port: 9527

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
  • Before Route Predicate

image-20260113030826356

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
- Before=2023-11-27T15:25:06.424566300+08:00[Asia/Shanghai] #超过规定时间不可访问

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
  • Between Route Predicate

image-20260113030852820

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
#- Before=2023-11-20T17:58:13.586918800+08:00[Asia/Shanghai]
- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

  • Cookie Route Predicate

image-20260113030931599

Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。

路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
- Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai]
#- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
- Cookie=username,zzyy

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
  • Header Route Predicate

image-20260113031133107

两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
- Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai]
#- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
  • Host Route Predicate

image-20260113031803740

Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。

它通过参数中的主机地址作为匹配规则。

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
- Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai]
#- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
#- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
- Host=**.bitzh.com

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
  • Path Route Predicate

image-20260113032515069

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
- Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai]
#- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
#- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
- Host=**.atguigu.com

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

  • Query Route Predicate

image-20260113032848031

支持传入两个参数,一个是属性名,一个为属性值,属性值可以是正则表达式。

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
- Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai]
#- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
#- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

  • RemoteAddr Route Predicate

image-20260113161013704

远程地址访问

image-20260113161044562

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
- Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai]
#- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
#- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
- RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
  • Method Route Predicate

配置某个请求地址,只能用Get/Post方法访问,访问限制

image-20260113161431546

All

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
#- RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。

自定义断言

XXXRoutePredicateFactory规则

看看源码

image-20260113162229685

结合架构图

image-20260113162717781

照着抄一个

模板套路

  • 要么实现AbstractRoutePredicateFactory抽象类
  • 要么实现RoutePredicateFactory接口
  • 开头任意取名,但是必须以RoutePredicateFactory后缀结尾
自定义路由断言规则步骤套路

编写步骤

  • 新建类名XXX需要以RoutePredicateFactory结尾并继承AbstractRoutePredicateFactory类
1
2
3
@Component标注不可忘
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>{
}
  • 重写apply方法
1
2
3
4
5
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config)
{
return null;
}
  • 新建apply方法所需要的静态内部类MyRoutePredicateFactory.Config(这个Config类就是我们的路由断言规则,重要)
1
2
3
4
5
6
7
@Validated
public static class Config{
@Setter
@Getter
@NotEmpty
private String userType; //钻、金、银等用户等级
}
  • 空参构造方法,内部调用super
1
2
3
4
public MyRoutePredicateFactory()
{
super(MyRoutePredicateFactory.Config.class);
}
  • 重写apply方法第二版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config)
{
return new Predicate<ServerWebExchange>()
{
@Override
public boolean test(ServerWebExchange serverWebExchange)
{
//检查request的参数里面,userType是否为指定的值,符合配置就通过
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");

if (userType == null) return false;

//如果说参数存在,就和config的数据进行比较
if(userType.equals(config.getUserType())) {
return true;
}

return false;
}
};
}

All

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
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>
{
public MyRoutePredicateFactory()
{
super(MyRoutePredicateFactory.Config.class);
}

@Validated
public static class Config{
@Setter
@Getter
@NotEmpty
private String userType; //钻、金、银等用户等级
}

@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config)
{
return new Predicate<ServerWebExchange>()
{
@Override
public boolean test(ServerWebExchange serverWebExchange)
{
//检查request的参数里面,userType是否为指定的值,符合配置就通过
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");

if (userType == null) return false;

//如果说参数存在,就和config的数据进行比较
if(userType.equals(config.getUserType())) {
return true;
}

return false;
}
};
}
}

测试发现有bug,不行了,报错说没有绑定属性错误。

然后发现短促的写法不对要用full的写法。这个fully expanded arguments 的格式可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
server:
port: 9527

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
#- RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
#- My=diamond
- name: My
args:
userType: diamond

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

原来想要短促写法,是缺少shortcutFieldOrder方法的视线,所以不支持短格式

image-20260113165745362

最终完整代码

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
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>
{
public MyRoutePredicateFactory()
{
super(MyRoutePredicateFactory.Config.class);
}

@Validated
public static class Config{
@Setter
@Getter
@NotEmpty
private String userType; //钻、金、银等用户等级
}

@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config)
{
return new Predicate<ServerWebExchange>()
{
@Override
public boolean test(ServerWebExchange serverWebExchange)
{
//检查request的参数里面,userType是否为指定的值,符合配置就通过
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");

if (userType == null) return false;

//如果说参数存在,就和config的数据进行比较
if(userType.equals(config.getUserType())) {
return true;
}

return false;
}
};
}

@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("userType");
}
}

现在这样配置就可以了

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
#- RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
- My=diamond
#- name: My
# args:
# userType: diamond

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

Filter过滤

SpringMVC里面的拦截器Interceptor,Servlet的过滤器

prepost分别会在请求被执行前调用和被执行后调用,用来修改请求和响应信息

断言(Predicate)决定“这个请求要不要路由”
过滤器(Filter)决定“路由前后对请求/响应做什么处理”

概述
能干嘛

请求鉴权,异常处理,记录接口调用时长统计(大厂面试设计题)

类型

1、全局默认过滤器 Global Filter

gateway出厂默认已有的,直接用即可,主要作用于所有的路由

不需要再配置文件中配置,作用在所有的路由上,实现GlobalFilter接口即可

2、单一内置过滤器GatewayFilter

也可以成为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组

3、自定义过滤器

Gateway内置的过滤器
是什么

单一内置过滤器GatewayFilter

只看常见和通用的,不是全部。

常用的内置过滤器

1、请求头(RequestHeader)相关组

The AddRequestHeader GatewayFilter Factory

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
指定请求头内容ByName
8001微服务PayGateWayController新增方法
@RestController
public class PayGateWayController
{
@Resource
PayService payService;

@GetMapping(value = "/pay/gateway/get/{id}")
public ResultData<Pay> getById(@PathVariable("id") Integer id)
{
Pay pay = payService.getById(id);
return ResultData.success(pay);
}

@GetMapping(value = "/pay/gateway/info")
public ResultData<String> getGatewayInfo()
{
return ResultData.success("gateway info test:"+ IdUtil.simpleUUID());
}

@GetMapping(value = "/pay/gateway/filter")
public ResultData<String> getGatewayFilter(HttpServletRequest request)
{
String result = "";
Enumeration<String> headers = request.getHeaderNames();
while(headers.hasMoreElements())
{
String headName = headers.nextElement();
String headValue = request.getHeader(headName);
System.out.println("请求头名: " + headName +"\t\t\t"+"请求头值: " + headValue);
if(headName.equalsIgnoreCase("X-Request-atguigu1")
|| headName.equalsIgnoreCase("X-Request-atguigu2")) {
result = result+headName + "\t " + headValue +" ";
}
}
return ResultData.success("getGatewayFilter 过滤器 test: "+result+" \t "+ DateUtil.now());
}
}

server:
port: 9527

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
#- RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
- My=gold
# - name: My
# args:
# userType: diamond

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由
filters:
- AddRequestHeader=X-Request-bitzh1,bitzhValue1 # 请求头kv,若一头含有多参则重写一行设置
- AddRequestHeader=X-Request-bitzh2,bitzhValue2

The RemoveRequestHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
删除请求头ByName
9527YML
- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由
filters:
- AddRequestHeader=X-Request-bitzh1,bitzhValue1 # 请求头kv,若一头含有多参则重写一行设置
- AddRequestHeader=X-Request-bitzh2,bitzhValue2
- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site

The SetRequestHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
11
修改请求头ByName
9527YML
- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由
filters:
- AddRequestHeader=X-Request-bitzh1,bitzhValue1 # 请求头kv,若一头含有多参则重写一行设置
- AddRequestHeader=X-Request-bitzh2,bitzhValue2
- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy

2、请求参数(RequestParameter)相关组

The AddRequestParameter GatewayFilter Factory

The RemoveRequestParameter GatewayFilter Factory

上述两个合一块

1
2
3
4
5
6
7
8
9
10
11
- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 请求头kv,若一头含有多参则重写一行设置
- AddRequestHeader=X-Request-atguigu2,atguiguValue2
- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
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
@GetMapping(value = "/pay/gateway/filter")
public ResultData<String> getGatewayFilter(HttpServletRequest request)
{
String result = "";
Enumeration<String> headers = request.getHeaderNames();
while(headers.hasMoreElements())
{
String headName = headers.nextElement();
String headValue = request.getHeader(headName);
System.out.println("request headName:" + headName +"---"+"request headValue:" + headValue);

if(headName.equalsIgnoreCase("X-Request-atguigu1")
|| headName.equalsIgnoreCase("X-Request-atguigu2")) {
result = result+headName + "\t " + headValue +" ";
}
}

System.out.println("=============================================");
String customerId = request.getParameter("customerId");
System.out.println("request Parameter customerId: "+customerId);

String customerName = request.getParameter("customerName");
System.out.println("request Parameter customerName: "+customerName);
System.out.println("=============================================");

return ResultData.success("getGatewayFilter 过滤器 test: "+result+" \t "+ DateUtil.now());
}

3、回应头(ResponseHeader)相关组

image-20260113231806088

The AddResponseHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
11
12
- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 请求头kv,若一头含有多参则重写一行设置
- AddRequestHeader=X-Request-atguigu2,atguiguValue2
- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
- AddResponseHeader=X-Response-atguigu, BlueResponse # 新增响应参数X-Response-atguigu并设值为BlueResponse

The SetResponseHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
11
12
13
- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 请求头kv,若一头含有多参则重写一行设置
- AddRequestHeader=X-Request-atguigu2,atguiguValue2
- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
- AddResponseHeader=X-Response-atguigu, BlueResponse # 新增请求参数X-Response-atguigu并设值为BlueResponse
- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11

The RemoveResponseHeader GatewayFilter Factory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 请求头kv,若一头含有多参则重写一行设置
- AddRequestHeader=X-Request-atguigu2,atguiguValue2
- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
- AddResponseHeader=X-Response-atguigu, BlueResponse # 新增请求参数X-Response-atguigu并设值为BlueResponse
- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11
- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除

4、前缀和路径相关组

The PrefixPath GatewayFilter Factory 自动添加路径前缀

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
- Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai]
#- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
#- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
#- RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24。

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由


- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
#- Path=/pay/gateway/filter/** # 被分拆为: PrefixPath + Path

- Path=/gateway/filter/** # 断言,为配合PrefixPath测试过滤,暂时注释掉/pay
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 #请求头kv,若一头含有多参则重写一行设置
#- AddRequestHeader=X-Request-atguigu2,atguiguValue2
#- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
#- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
#- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
#- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
#- AddResponseHeader=X-Response-atguigu, BlueResponse # 新增请求参数X-Response-atguigu并设值为BlueResponse
#- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11
#- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除
- PrefixPath=/pay # http://localhost:9527/pay/gateway/filter

image-20260113235326569

The SetPath GatewayFilter Factory 访问路径修改

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
- Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai]
#- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
#- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
#- RemoteAddr=192.168.1.196/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24。

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由


- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
#- Path=/pay/gateway/filter/** # 真实地址
#- Path=/gateway/filter/** # 断言,为配合PrefixPath测试过滤,暂时注释掉/pay
- Path=/XYZ/abc/{segment} # 断言,为配合SetPath测试,{segment}的内容最后被SetPath取代
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 #请求头kv,若一头含有多参则重写一行设置
#- AddRequestHeader=X-Request-atguigu2,atguiguValue2
#- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
#- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
#- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
#- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
#- AddResponseHeader=X-Response-atguigu, BlueResponse # 新增请求参数X-Response-atguigu并设值为BlueResponse
#- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11
#- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除
#- PrefixPath=/pay # http://localhost:9527/pay/gateway/filter
- SetPath=/pay/gateway/{segment} # {segment}表示占位符,你写abc也行但要上下一致

image-20260113235450155

The RedirectTo GatewayFilter Factory 重定向到某个页面

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
#- After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai]
- Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai]
#- Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
#- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
#- RemoteAddr=192.168.1.196/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24。

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由


- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 真实地址
#- Path=/gateway/filter/** # 断言,为配合PrefixPath测试过滤,暂时注释掉/pay
#- Path=/XYZ/abc/{segment} # 断言,为配合SetPath测试,{segment}的内容最后被SetPath取代
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 #请求头kv,若一头含有多参则重写一行设置
#- AddRequestHeader=X-Request-atguigu2,atguiguValue2
#- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
#- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
#- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
#- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
#- AddResponseHeader=X-Response-atguigu, BlueResponse # 新增请求参数X-Response-atguigu并设值为BlueResponse
#- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11
#- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除
#- PrefixPath=/pay # http://localhost:9527/pay/gateway/filter
- RedirectTo=302, http://www.atguigu.com/ # 访问http://localhost:9527/pay/gateway/filter跳转到http://www.atguigu.com/

5、其他

Default Filters 配置在此处相当于全局通用,自定义秒变Global

image-20260114000011177

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

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
#- RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
- My=gold
# - name: My
# args:
# userType: diamond

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由,默认正确地址
#- Path=/gateway/filter/** # 断言,为配合PrefixPath测试过滤,暂时注释掉/pay
#- Path=/XYZ/abc/{segment} # 断言,为配合SetPath测试,{segment}的内容最后被SetPath取代
filters:
- RedirectTo=302, http://www.atguigu.com/ # 访问http://localhost:9527/pay/gateway/filter跳转到http://www.atguigu.com/
#- SetPath=/pay/gateway/{segment} # {segment}表示占位符,你写abc也行但要上下一致
#- PrefixPath=/pay # http://localhost:9527/pay/gateway/filter 被分拆为: PrefixPath + Path
#- AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 请求头kv,若一头含有多参则重写一行设置
#- AddRequestHeader=X-Request-atguigu2,atguiguValue2
#- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
#- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
#- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
#- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
#- AddResponseHeader=X-Response-atguigu, BlueResponse # 新增请求参数X-Response-atguigu并设值为BlueResponse
#- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11
#- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除
Gateway自定义过滤器
自定义全局Filter

面试题

统计接口调用耗时情况,如何落地,谈谈设计思路

通过自定义全局过滤器搞定上述需求

自定义接口调用耗时统计的全局过滤器

步骤

新建类MyGlobalFilter 并实现GolbalFilter,Ordered两个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component //不要忘记
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered
{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
return null;
}

@Override
public int getOrder()
{
return 0;
}
}

配置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
server:
port: 9527

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai]

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由,默认正确地址
filters:
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 请求头kv,若一头含有多参则重写一行设置

完整的过滤器

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
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered
{

/**
* 数字越小优先级越高
* @return
*/
@Override
public int getOrder()
{
return 0;
}

private static final String BEGIN_VISIT_TIME = "begin_visit_time";//开始访问时间
/**
*第2版,各种统计
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//先记录下访问接口的开始时间
exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());

return chain.filter(exchange).then(Mono.fromRunnable(()->{
Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);
if (beginVisitTime != null){
log.info("访问接口主机: " + exchange.getRequest().getURI().getHost());
log.info("访问接口端口: " + exchange.getRequest().getURI().getPort());
log.info("访问接口URL: " + exchange.getRequest().getURI().getPath());
log.info("访问接口URL参数: " + exchange.getRequest().getURI().getRawQuery());
log.info("访问接口时长: " + (System.currentTimeMillis() - beginVisitTime) + "ms");
log.info("我是美丽分割线: ###################################################");
System.out.println();
}
}));
}

}
自定义条件Filter

全局是所有都生效,条件是符合条件的才会生效

先参考GateWay内置出厂默认的

然后开始自定义网关过滤器规则步骤套路

1、新建类名XXX需要以GatewayFilterFactory结尾,并继承AbstractGatewayFilterFactory类

1
2
3
@Component标注不可忘
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>{
}

2、新建XXXGatewayFilterFactory.Config内部类

1
2
3
4
5
6
7
8
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>
{
public static class Config {
@Setter @Getter
private String status;
}
}

3、重写apply方法

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
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>
{
@Override
public GatewayFilter apply(MyGatewayFilterFactory.Config config)
{
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
System.out.println("进入自定义网关过滤器MyGatewayFilterFactory,status===="+config.getStatus());
if(request.getQueryParams().containsKey("atguigu")) {
return chain.filter(exchange);
}else {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
}
};
}

public static class Config {
@Setter @Getter
private String status;
}
}

4、重写shortcutFieldOrder

1
2
3
4
5
6
@Override
public List<String> shortcutFieldOrder() {
List<String> list = new ArrayList<String>();
list.add("status");
return list;
}

5、空参构造方法,内部调用super

1
2
3
public MyGatewayFilterFactory() {
super(MyGatewayFilterFactory.Config.class);
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>
{
public MyGatewayFilterFactory()
{
super(MyGatewayFilterFactory.Config.class);
}


@Override
public GatewayFilter apply(MyGatewayFilterFactory.Config config)
{
return new GatewayFilter()
{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
ServerHttpRequest request = exchange.getRequest();
System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:"+config.getStatus());
if(request.getQueryParams().containsKey("atguigu")){
return chain.filter(exchange);
}else{
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
}
};
}

@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("status");
}

public static class Config
{
@Getter@Setter
private String status;//设定一个状态值/标志位,它等于多少,匹配和才可以访问
}
}
//单一内置过滤器GatewayFilter

现在自定义Filter设置好了,现在写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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
server:
port: 9527

spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
#- RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
#- My=gold
# - name: My
# args:
# userType: diamond

- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由

- id: pay_routh3 #pay_routh3
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由,默认正确地址
#- Path=/gateway/filter/** # 断言,为配合PrefixPath测试过滤,暂时注释掉/pay
#- Path=/XYZ/abc/{segment} # 断言,为配合SetPath测试,{segment}的内容最后被SetPath取代
filters:
- My=atguigu
#- RedirectTo=302, http://www.atguigu.com/ # 访问http://localhost:9527/pay/gateway/filter跳转到http://www.atguigu.com/
#- SetPath=/pay/gateway/{segment} # {segment}表示占位符,你写abc也行但要上下一致
#- PrefixPath=/pay # http://localhost:9527/pay/gateway/filter 被分拆为: PrefixPath + Path
- AddRequestHeader=X-Request-atguigu1,atguiguValue1 # 请求头kv,若一头含有多参则重写一行设置
#- AddRequestHeader=X-Request-atguigu2,atguiguValue2
#- RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
#- SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
#- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
#- RemoveRequestParameter=customerName # 删除url请求参数customerName,你传递过来也是null
#- AddResponseHeader=X-Response-atguigu, BlueResponse # 新增请求参数X-Response-atguigu并设值为BlueResponse
#- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11
#- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除

image-20260114005134341

Gateway整合阿里巴巴Sentinel实现容错

10、SpringCloud Alibaba入门简介

SpringCloud Alibaba是SpringCloud的Alibaba版本,和阿里云融合很好。

里面整合了一站式解决方案,以及必须组件。什么是必须组件

image-20260114022229904

后面SchedulerX相当于xxl-job

能干嘛

image-20260114024833283

怎么玩

image-20260114033032993

image-20260114033048524

11、SpringCloudAlibaba Nacos服务注册和配置中心

image-20260114033423983

Nacos简介

就是服务注册和服务配置中心

Nacos下载安装

Nacos 快速开始 | Nacos 官网

各种注册中心比较

image-20260114034606155

据说 Nacos 在阿里巴巴内部有超过 10 万的实例运行,已经过了类似双十一等各种大型流量的考验,Nacos默认是AP模式,但也可以调整切换为CP,我们一般用默认AP即可。

先从官网下载Nacos

这里先2.2.3版本的

解压安装包然后运行bin目录下的startup.cmd

1
startup.cmd -m standalone

然后访问localhost:8848/nacos 默认账号密码都是nacos然后就能进入页面了

关闭服务器

1
shutdown.cmd

Nacos Discovery 服务注册中心

通过Nacos Server 和 spring-cloud-starter-alibaba-nacos-discovery 实现服务的注册与发现

基于Nacos的服务提供者

新建Module cloudalibaba-provider-payment9001

改POM

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
<?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.bitzh</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<artifactId>cloudalibaba-provider-payment9001</artifactId>

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



<dependencies>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.bitzh.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--SpringBoot通用依赖模块-->
<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>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

写YML

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

spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址

image-20260114205311592

主启动

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

业务类

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class PayAlibabaController
{
@Value("${server.port}")
private String serverPort;

@GetMapping(value = "/pay/nacos/{id}")
public String getPayInfo(@PathVariable("id") Integer id)
{
return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
}
}

测试

基于Nacos的服务消费者

新建Module cloudalibaba-consumer-nacos-order83

POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.bitzh</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<artifactId>cloudalibaba-consumer-nacos-order83</artifactId>

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


<dependencies>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--loadbalancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

YML

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

spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
#消费者将要去访问的微服务名称(nacos微服务提供者叫什么你写什么)
service-url:
nacos-user-service: http://nacos-payment-provider

image-20260114211755546

主启动

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

配置config

1
2
3
4
5
6
7
8
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 赋予RestTemplate负载均衡的能力
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

业务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
public class OrderNacosController
{
@Resource
private RestTemplate restTemplate;

@Value("${service-url.nacos-user-service}")
private String serverURL;

@GetMapping("/consumer/pay/nacos/{id}")
public String paymentInfo(@PathVariable("id") Integer id)
{
String result = restTemplate.getForObject(serverURL + "/pay/nacos/" + id, String.class);
return result+"\t"+" 我是OrderNacosController83调用者。。。。。。";
}
}

负载均衡

参照9001新建9002

要么老实新建,要么取巧,直接拷贝虚拟端口映射,之前用的是老实方法,这次用拷贝虚拟端口映射的方法

image-20260115093810797

image-20260115094207432

然后添加-DServer.port=9002

然后就有个虚拟的service是9002了

然后测试,发现9001和9002交替出现,负载均衡达到

Nacos Config服务配置中心

之前案例Consul8500服务配置动态变更功能可以被Nacos取代

通过Nacos和spring-cloud-starter-alibaba-nacos-config实现中心化全局配置的动态变更

Nacos作为配置中心配置步骤

建Module cloudalibaba-config-nacos-client3377

POM

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
<?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.bitzh</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<groupId>com.bitzh.cloud</groupId>
<artifactId>cloudalibaba-config-nacos-client3377</artifactId>

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

<dependencies>
<!--bootstrap-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

引入bootstrap,记得之前的bootstrap.yml就知道这个依赖是做什么的了。

YML

配置两个,为啥要配置两个

Nacos同Consul一样,在项目初始化时,要保证先从配置中心进行配置拉取,

拉取配置之后,才能保证项目的正常启动,为了满足动态刷新和全局广播通知

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

bootstrap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# nacos配置
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式的配置

# nacos端配置文件DataId的命名规则是:
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# 本案例的DataID是:nacos-config-client-dev.yaml

application.yml

1
2
3
4
5
6
7
8
server:
port: 3377

spring:
profiles:
active: dev # 表示开发环境
#active: prod # 表示生产环境
#active: test # 表示测试环境

主启动

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

业务类

NacosConfigClientController

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
public class NacosConfigClientController
{
@Value("${config.info}")
private String configInfo;

@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}

Nacos的数据会持久化到内嵌数据库derby中,可以自行配置持久化到MySQL中。此外,Nacos不想Consul持久化后能够开机自启动,在服务列表没有Nacos服务

Nacos中添加配置信息

Nacos中的匹配规则

设置DataId理论

公式:

1
2
3
# nacos端配置文件DataId的命名规则是:
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# 本案例的DataID是:nacos-config-client-dev.yaml

image-20260115104955157

配置DataId实操

创建配置

image-20260115105114743

测试一下,然后也是动态刷新的。

历史配置

Nacos会记录配置文件的历史版本,默认保留30天,此外还有一键回滚功能,回滚会触发配置更新。

Nacos数据模型之Namespace-Group-DataId

问题

多环境多项目管理

问题1:

实际开发中,通常一个系统会准备

dev开发环境

test测试环境

prod生产环境。

如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?

问题2:

一个大型分布式微服务系统会有很多微服务子项目,

每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境……

那怎么对这些微服务配置进行分组和命名空间管理呢?

Namespace+Group+DataId三者关系?为什么这么设计

image-20260115162246779

概念 核心解释 默认值
Namespace 类似 Java 的 package 名 / 类名,最外层维度,用于区分部署环境(如开发 / 测试 / 生产),实现环境隔离 public
Group 逻辑上区分目标对象,可将不同微服务划分到同一个分组中 DEFAULT_GROUP
DataID 与 Namespace、Group 配合,逻辑上区分两个目标对象(补充自概念 1 的描述) -
Service 即微服务,是 Nacos 服务注册发现的核心对象 -
Cluster 对指定微服务的虚拟划分,一个 Service 可包含一个或多个 Cluster DEFAULT

image-20260115162407441

Namespace 核心价值是环境隔离:如创建 dev/test/prod 三个 Namespace,不同环境的配置 / 服务互相隔离;

Group 核心价值是业务分组:如将订单、支付微服务归到 “BUSINESS_GROUP”,将基础组件归到 “BASE_GROUP”;

Cluster 核心价值是微服务集群划分:如一个 Service 下的 Cluster 可区分华北集群、华南集群等。

dataID有具体的命名规则,每个微服务不同。

三种方案加载配置

1、DataID方案

指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置

默认空间Public+默认分组DEFAULT_GROUP+新建DataID

新建test配置DataID nacos-config-client-test.yaml

修改application.yml

2、Group方案

通过Group实现环境区分

默认空间public+新建PROD_GROUP+新建DataID

新建prod配置DataID nacos-config-client-prod.yaml

新建Group PROD_GROUP

修改YML

在config下新增一条group配置即可

3、Namespace方案

通过Namespace实现明明空间环境区分

新建Namespace: Prod_Namespaceimage-20260115164604201

Prod_Namespace+PROD_GROUP+DataID(nacos-config-client-prod.yaml)

然后选定Prod_Namespace后新建配置

然后修改YML,在config下面新增一条namespace

12、SpringCloud Alibaba Sentinel实现熔断和限流

Sentinel

image-20260116002259267

从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性

怎么玩

image-20260116063050116

面试题

缓存穿透+缓存击穿+缓存雪崩如何解决。回顾redis

这里大概讲讲。

缓存穿透就是访问不存在的值直接打到mysql里面。解决方案一就是存空白或者不存在的值,方案二就是布隆过滤器,让过滤器充当黑名单,不存在的值存入过滤器里直接过滤大部分非法请求。

击穿就是热点key失效,这里单机版就用jvm锁,分布式就用redis分布式锁,然后来进行双检加锁。大概;意思就是更新热点key的时候先把热点key缓存,再放锁,否则就等待,防止暴打mysql。

缓存雪崩就是大面积redis缓存失效。redis 中 key 设置为永不过期 or 过期时间错开

redis 缓存集群实现高可用

​ 主从 + 哨兵

​ Redis Cluster

​ 开启Redis持久化机制aof / rdb ,尽快回复缓存集群

多缓存结合预防雪崩

​ caffeine或者ehcache本地缓存 + redis缓存

服务降级

​ Hystrix 或者 阿里sentinel限流 & 降级

人民币玩家 阿里云-云数据库Redis版上面全部都提供服务

服务雪崩

A服务调用B服务,然后各种调用,如果一个服务出错,整个链上的服务全部失败,这就是服务雪崩。

服务降级

服务降级,说白了就是一种服务托底方案,如果服务无法完成正常的调用流程,就使用默认的托底方案来返回数据。

例如,在商品详情页一般都会展示商品的介绍信息,一旦商品详情页系统出现故障无法调用时,会直接获取缓存中的商品介绍信息返回给前端页面。

服务熔断

在分布式与微服务系统中,如果下游服务因为访问压力过大导致响应很慢或者一直调用失败时,上游服务为了保证系统的整体可用性,会暂时断开与下游服务的调用连接。这种方式就是熔断。类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。

服务熔断一般情况下会有三种状态:闭合、开启和半熔断;

闭合状态(保险丝闭合通电OK):服务一切正常,没有故障时,上游服务调用下游服务时,不会有任何限制。

开启状态(保险丝断开通电Error):上游服务不再调用下游服务的接口,会直接返回上游服务中预定的方法。

半熔断状态:处于开启状态时,上游服务会根据一定的规则,尝试恢复对下游服务的调用。此时,上游服务会以有限的流量来调用下游服务,同时,会监控调用的成功率。如果成功率达到预期,则进入关闭状态。如果未达到预期,会重新进入开启状态。

服务限流

服务限流就是限制进入系统的流量,以防止进入系统的流量过大而压垮系统。其主要的作用就是保护服务节点或者集群后面的数据节点,防止瞬时流量过大使服务和数据崩溃(如前端缓存大量实效),造成不可用;还可用于平滑请求,类似秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。

限流算法有两种,一种就是简单的请求总量计数,一种就是时间窗口限流(一般为1s),如令牌桶算法和漏牌桶算法就是时间窗口的限流算法。漏桶无法应对突发状况但是能控制速率,速率非常稳定,就是只能固定设置桶大小和流速,令牌桶用的是预存令牌,一个请求消耗一个令牌,有令牌的才能进入,否则就排队而不是超出就立刻拒绝。

服务隔离

有点类似于系统的垂直拆分,就按照一定的规则将系统划分成多个服务模块,并且每个服务模块之间是互相独立的,不会存在强依赖的关系。如果某个拆分后的服务发生故障后,能够将故障产生的影响限制在某个具体的服务内,不会向其他服务扩散,自然也就不会对整体服务产生致命的影响。

互联网行业常用的服务隔离方式有:线程池隔离和信号量隔离。

信号量舱壁就是利用调用方的线程,对并发量限制,固定线程池舱壁就是服务器里面专属线程池,和调用方线程隔离

服务超时

整个系统采用分布式和微服务架构后,系统被拆分成一个个小服务,就会存在服务与服务之间互相调用的现象,从而形成一个个调用链。

形成调用链关系的两个服务中,主动调用其他服务接口的服务处于调用链的上游,提供接口供其他服务调用的服务处于调用链的下游。服务超时就是在上游服务调用下游服务时,设置一个最大响应时间,如果超过这个最大响应时间下游服务还未返回结果,则断开上游服务与下游服务之间的请求连接,释放资源。

安装Sentinel

sentinel组件由2部分构成

image-20260116082446478

后台8719默认

前台8080开启

安装步骤

先下载到本地,然后运行命令,前提8080端口不能被占用

1
java -jar sentinel-dashboard-1.8.6.jar

账号密码都为sentinel

微服务8401整合Sentinel入门案例

启动Nacos8848成功

1
startup.cmd -m standalone

启动Sentinel8080成功

1
java -jar sentinel-dashboard-1.8.6.jar

新建微服务8401

cloudalibaba-sentinel-service8401 将被哨兵纳入管控的8401微服务提供者

POM

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
<?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.bitzh.cloud</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<artifactId>cloudalibaba-sentinel-service8401</artifactId>

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


<dependencies>
<!--SpringCloud alibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.bitzh.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--SpringBoot通用依赖模块-->
<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>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

YML

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

server:
port: 8401

spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard控制台服务地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口

主启动

1
2
3
4
5
6
7
8
9
10
11
@EnableDiscoveryClient
@SpringBootApplication
public class Main8401
{
public static void main(String[] args)
{
SpringApplication.run(Main8401.class,args);
}
}


业务类FlowLimitController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class FlowLimitController
{

@GetMapping("/testA")
public String testA()
{
return "------testA";
}

@GetMapping("/testB")
public String testB()
{
return "------testB";
}
}

启动后查看控制台

发现空空如也啥也没有。

Sentinel采用的懒加载说明。

注意 想要使用Sentinel对某个接口进行限流和降级等操作,一定要先访问下接口,使用Sentinel检测出相应的接口。访问一次接口即可。

然后看到一些指标

指标 全称 含义 与 QPS 的关系
QPS Queries Per Second 每秒处理请求数 吞吐量的核心体现
RT Response Time 响应时间(毫秒) QPS ↑ 通常 RT ↑(资源竞争)
并发数(Concurrency) 同时处理的请求数 近似公式:并发数 ≈ QPS × 平均 RT(秒)
TPS Transactions Per Second 每秒事务数 在数据库/支付场景更常用,1 事务可能含多个查询
RPS Requests Per Second 每秒请求数 与 QPS 常混用,但 RPS 可包含失败请求