SpringCloud
1. 认识微服务
- 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构。那这些架构之间有怎样的区别呢?
1.2 单体架构
将业务的所有功能集中在一个项目中开发,打成一个包部署
- 单体架构优缺点如下
- 优点
- 架构简单
- 部署成本低
- 缺点
- 耦合度高(维护困难,升级困难)
- 优点
1.3 分布式架构
根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务
分布式架构优缺点如下
- 优点
- 降低服务耦合
- 有利于服务升级和拓展
- 缺点
- 服务调用关系错综复杂
- 优点
分布式结构虽然降低了服务耦合,但是服务拆分时也有许多问题需要思考
- 服务拆分的细粒度如果界定?
- 服务之间如何调用?
- 服务的调用关系如果管理?
人们需要指定一套行之有效的标准来约束分布式框架,
微服务架构应运而生
1.4 微服务
微服务的架构特征
- 单一职责:微服务拆分粒度更小,每个服务都对应唯一的业务能力,做到单一职责
- 自治:团队独立,技术独立,数据独立,独立部署和交付
- 面向服务:服务提供统一标准的接口,与语言和技术无关
- 隔离性强:服务调用做好隔离,容错,降级,避免出现级联问题(例如积分服务挂了,不印象用户服务等其他服务)。

alt 微服务架构
微服务的上述特征其实是给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性,做到高内聚,低耦合。
因此,可以认为微服务是一种经过良好架构设计的分布式架构
1.5 SpringCloud
SpringCloud是目前国内使用最广泛的微服务架构 官网地址:https://spring.io/projects/spring-cloudSpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供同了良好的开箱即用体验。其中常见的组件包括
- 微服务注册与发现
EurekaNacosConsul
- 服务远程调用
OpenFeignDubbo
- 服务链路监控
ZipkinSleuth
- 统一配置管理
SpringCloudConfigNacos
- 统一网关路由
SpringCloudGatewayZuul
- 流控、降级、保护
HystixSentinel
- 微服务注册与发现
另外,SpringCloud底层依赖于SpringBoot,并且有版本的兼容关系,如下
| Release Train | Boot Version |
|---|---|
| 2020.0.x aka llford | 2.4.x |
| Hoxton | 2.2.x,2.3.x (Starting with SR5) |
| Greenwich | 2.1.x |
| Finchley | 2.0.x |
| Edgware | 1.5.x |
| Dalston | 1.5.X |
- 我们学习的版本是
Hoxton.SR10,因此对应的是SpringBoot版本是2.3.x
1.6 总结
- 单体架构:简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统
- 分布式架构:松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目。例如:京东、淘宝
- 微服务:一种更好的分布式架构方案
- 优点:拆分力度更小、服务更独立、耦合度更低
- 缺点:架构非常复杂,运维、监控、部署难度提高
- SpringCloud 是微服务架构的一站式解决方案,集成了各种优秀的微服务功能组件
2. 服务拆分和远程调用
- 任何分布式架构都离不开服务的拆分,微服务也是一样
2.1 服务拆分原则
- 微服务拆分有几个原则:
- 不同微服务,不要重复开发相同业务
- 微服务数据独立,不要访问其他微服务的数据库
- 微服务可以将自己的业务暴露为接口,供其他微服务调用

alt 微服务拆分图
2.2 服务拆分示例
cloud-demo:父工程,管理依赖
order-service:订单微服务,负责订单相关业务user-service:用户微服务,负责用户相关业务
需求
订单微服务和用户微服务必须有各自的数据库,相互独立
订单服务和用户服务都对外暴露Restful的接口
订单服务如果需要查询用户信息,只能调用用户服务的Restful接口,不能查询用户数据库
- 导入SQL语句
1 | CREATE DATABASE cloud_order; |
1 | CREATE DATABASE cloud_user; |
- 导入demo
- 导入黑马提供的demo,里面包含了
order-service和user-service,将配置文件中的数据库修改为自己的配置,随后将这两个服务启动,开始我们的调用案例
2.3 实现远程调用案例
在
order-service中的web包下,有一个OrderController,是根据id查询订单的接口1
2
3
4
5
public Order queryOrderByUserId( Long orderId) {
// 根据id查询订单并返回
return orderService.queryOrderById(orderId);
}我们启动服务(注意jdk版本必须是8并且改一下连接数据库的配置),访问http://localhost:8080/order/101,是可以查到数据的,但此时的user是null
1
2
3
4
5
6
7
8{
"id": 101,
"price": 699900,
"name": "Apple 苹果 iPhone 12 ",
"num": 1,
"userId": 1,
"user": null
}在user-servie的web包下,也有一个UserController,其中包括一个根据id查询用户的接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UserController {
private UserService userService;
/**
* 路径: /user/110
*
* @param id 用户id
* @return 用户
*/
public User queryById( Long id) {
return userService.queryById(id);
}
}我们打开浏览器,访问http://localhost:8081/user/1 ,查询到的数据如下
1
2
3
4
5{
"id": 1,
"username": "柳岩",
"address": "湖南省衡阳市"
}案例需求:
- 修改order-service中的根据id查询订单业务,要求在查询订单的同时,根据订单中包含的userId查询出用户信息,一并返回

alt 远程调用案例流程图
- 修改order-service中的根据id查询订单业务,要求在查询订单的同时,根据订单中包含的userId查询出用户信息,一并返回
因此,我们需要在order-service中向user-service 发起一个http请求,调用http://localhost:8081/user/{userId}这个接口。
大概步骤:
- 注册一个
RestTemplate的实例到Spring容器 - 修改order-service 服务中的
OrderService类中的queryOrderById方法,根据Order对象中的userID查询User。 - 将查询到的User 填充到Order 对象,一并返回。
- 注册一个
2.4 实现远程调用需求
首先我们在order-service服务中的OrderApplication启动类中,注册
RestTemplate实例1
2
3
4
5
6
7
8
9
10
11
12
13
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
public RestTemplate restTemplate() {
return new RestTemplate();
}
}实现远程调用,修改order-service服务中的
queryOrderById1
2
3
4
5
6
7
8
9
10
11
12
13public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2. 远程查询User
//2.1 URL地址,这里URL写死了,后面会改进
String url = "http://localhost:8081/user/" + order.getUserId();
//2.2 发起调用
User user = restTemplate.getForObject(url, User.class);
//3. 存入user
order.setUser(user);
// 4.返回
return order;
}再次访问http://localhost:8080/order/101 这次就能看到User数据了
1
2
3
4
5
6
7
8
9
10
11
12{
"id": 101,
"price": 699900,
"name": "Apple 苹果 iPhone 12 ",
"num": 1,
"userId": 1,
"user": {
"id": 1,
"username": "柳岩",
"address": "湖南省衡阳市"
}
}
2.5 提供者与消费者
在服务调用关系中,会有两个不同的角色
- 服务提供者:一次业务中,被其他微服务调用的服务(提供接口给其他微服务)
- 服务消费者:一次业务中,调用其他微服务的服务(调用其他微服务提供的接口)
但是,服务提供者与服务消费者的角色并不是绝对的,而是相对于业务而言。
3. Eureka注册中心
- 加入我们的服务提供者user-service提供了三个实例,分别占用8081,8082,8083端口
- 我们来思考三个问题:
- order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?
- 有多个user-service实例地址,order-service应该调用哪个?
- order-service如何得知某个user-service实例是否宕机?
3.1 Eureka的结构和作用
这些问题都需要由SpringCloud中的注册中心来解决,其中最广为人知的注册中心就是Eureka,其结构如下:

alt Eureka结构 那现在来回答之前的各个问题:
问题一:order-service如何得知user-service的实例地址?- user-service服务实例启动后,将自己的信息注册到eureka-server(Eureka服务器),这个叫服务注册。
- eureka-server保存服务名称到服务实例地址列表的映射关系。
- order-server根据服务名称,拉去实例地址列表,这个叫做服务发现(服务拉取)。
问题二:order-service如何从多个user-service实例中获取具体的实例?- order-service从实例列表中利用负载均衡算法选中一个实例地址。
- 向该实例地址发起远程调用
问题三:order-service如何得知某个user-service是否宕机?- user-service每隔一段时间(默认30s)向eureka-server发送请求,报告自己的状态,称为心跳
- 当超过一定时间没有发送心跳,eureka-server会认为微服务实例故障,将该实例从服务列表中剔除。
- order-service拉取服务时,就能将该故障排除了
注册中心的步骤
- 搭建注册中心
EurekaServer - 服务注册,将user-service和order-service都注册到eureka
- 服务发现,在order-service中完成服务拉取,然后通过负载均衡挑选一个服务实现远程调用
- 搭建注册中心
3.2 搭建eureka-server
创建eureka-server服务
- 在cloud-demo父工程下创建一个子模块,这里使用maven项目,然后填写服务信息
引入eureka依赖
- 在eureka-server模块的配置文件
pom.xml里注入starter依赖- 这里不需要注入版本,因为在父工程中已经把版本管理好了
1
2
3
4
5
6<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>- 在eureka-server模块的配置文件
编写启动类
- 给eureka-server编写一个启动类
EurekaApplication,必须添加@EnableEurekaSercer注解,开启eureka注册中心功能
1
2
3
4
5
6
7
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}- 给eureka-server编写一个启动类
配置文件
- 编写
application.yml,内容如下- 为什么eureka自己需要配置
eureka-client? –> 因为eureka自己也是个微服务项目,也需要被自己管理
- 为什么eureka自己需要配置
1
2
3
4
5
6
7
8
9server:
port: 10086 # 服务端口
spring:
application:
name: eureka-server ## eureka的服务名称
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka- 编写
启动服务
- 启动微服务,在服务器访问http://localhost:10086 看到如下页面就是启动成功

alt Eureka注册中心界面 - 从图中看eruka的确将自己注册成了一个服务
UP (1) - localhost:eureka-server:10086
- 启动微服务,在服务器访问http://localhost:10086 看到如下页面就是启动成功
3.2 服务注册
- 下面,我们将user-service和order-service注册到eureka-server中
引入依赖
- 在user-service和order-service的pom.xml文件中,引入下面的eureka-client依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>配置文件
- 在user-service和order-service中,修改
application.yml文件,添加服务名称、eureka地址
1
2
3
4
5
6
7spring:
application:
name: order-service/user-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka- 在user-service和order-service中,修改
启动多个user-service实例
为了演示一个服务有多个实例的场景,我们添加一个SpringBoot的启动配置,再启动一个user-service
在服务里复制一份user-service的配置,name为
UserApplication2,点击修改选项按钮,然后将虚拟器选项点出来,修改端口号-Dserver.port=8082,点击确定之后,在IDEA的服务选项卡中,就会出现两个user-service启动配置,一个端口是8081,一个端口是8082之后我们按照相同的方法配置order-service,并将两个user-service和一个order-service都启动,然后查看eureka-server管理页面,发现服务确实都启动了,而且user-service有两个
踩过的坑
我在把所有Application启动之后,发现全都报错了,然后看我的Eureka终端里面也没有其他的服务,这是为什么?
因为把EurekaApplication和其他的Application同时启动了,Eureka还没反应过来,所以其他的Application就会报错。
解决方法就是配置完Eureka之后不再重启
3.3 服务发现
引入依赖和配置文件
- 我们已经在之前完成了对user-service的引入依赖和配置文件
服务拉取和负载均衡
- 最后,我们要去eureka-server中拉取user-service服务的实例列表,并实现负载均衡
- 不过这些操作不需要我们去做,只需要添加一些注解即可
- 在order-service的OrderApplication中,给RestTemplate这个Bean添加一个
@LoadBalanced注解
1
2
3
4
5
public RestTemplate restTemplate() {
return new RestTemplate();
}- 修改order-service服务中的OrderService类的
queryOrderById方法,修改访问路径,用服务名称来代替IP和端口(这里服务名称是配置文件中的application:name)
1
2
3
4
5
6
7
8
9
10
11
12
13public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2. 远程查询User
//2.1 URL地址,用user-service代替地址
String url = "http://user-service/user/" + order.getUserId();
//2.2 发起调用
User user = restTemplate.getForObject(url, User.class);
//3. 存入user
order.setUser(user);
// 4.返回
return order;
}
- Spring会自动帮我们从eureka-server端,根据user-service这个服务名称,获取实例列表,然后完成负载均衡。
- 这时我们访问localhost:8080/order/101就会发现远程调用了user-service
4. Ribbon负载均衡
- 在这个小结,我们来说明
@LocalBalanced注解如何实现的负载均衡功能
4.1 负载均衡原理
- SpringCloud底层其实利用了一个名为Ribbon的组件来实现负载均衡功能的

alt 负载均衡原理图
如图,为什么我们发送的请求http://userservice/user/1 是怎么轮询到 http://localhost:8080/user/1 的呢?
5. Nacos注册中心
- 国内一般使用
SpringCloud Alibaba的Nacos注册中心
5.1 认识和安装Nacos
- Nacos是阿里巴巴的产品,现在是SpringCloud的一个组件,相比于Eureka,功能更加丰富
- 在Nacos的Github页面,提供了下载连接,可以下载编译好的Nacos服务端或者源代码
- GitHub主页:https://github.com/alibaba/nacos
- 下载好之后,将文件安装到非中文路径下任意目录
bin:启动脚本conf:配置脚本
- Nacos的默认端口是8848。
- Nacos的启动很简单,进入bin目录,打开cmd窗口执行下面命令 之后在浏览器访问 http://localhost:8848/nacos 即可,默认的登录账号和密码都是nacos
1
startup.cmd -m standalone
5.2 服务注册到Nacos
Nacos是SpringCloudAlibaba的组件,而
SpringCloudAlibaba也遵循SpringCloud中定义的服务注册,服务发现规范,因此Nacos与Eureka在微服务方面没太大区别主要差异在于:
- 依赖不同
- 服务地址不同
引入依赖
- 在cloud-demo父工程的
pom.xml文件中引入SpringCloudAlibaba的依赖1
2
3
4
5
6
7<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency> - 然后在user-service和order-service的pom文件中引入nacos-discovery依赖
- 同时将eureka的依赖注释掉
1
2
3
4<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 同时将eureka的依赖注释掉
- 在cloud-demo父工程的
配置Nacos地址
- 在user-service和order-service的
application.yml文件中添加Nacos地址- 同时将eureka地址注释掉
1
2
3
4spring:
cloud:
nacos:
server-addr: localhost:8848- 在user-service和order-service的
重启服务
- 重启微服务后,登录nacos的管理页面,可以看到微服务信息

alt Nacos终端
- 重启微服务后,登录nacos的管理页面,可以看到微服务信息
5.3 服务分级存储模型
一个服务可以有多个实例,例如我们的user-service,可以有
- 127.0.0.1:8081
- 127.0.0.1:8082
- 127.0.0.1:8083
假如这些实例分布于全国各地的不同机房,Nacos就会将同一个机房的所有实例划分成一个**
集群**也就是说.user-service是服务,一个服务可以包含多个集群,每个集群可以有多个实例,形成分级模型。
微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快,本集群内不可用再去访问其他集群。
5.3.1 集群配置
- 修改user-service的
application.yml文件,添加集群配置
1 | spring: |
启动两个user-service实例
之后再复制一个user-service的启动配置,端口号设置8083,之后修改
application.yml,将集群名称改为上海SH,之后启动该服务然后刷新Nacos终端,可以发现有两个实例的集群HZ和只有一个实例的集群SH

alt 不同集群Nacos Nacos服务分级
- 一级:服务 如user-service
- 二级:集群 如上海或者杭州
- 三级:实例 如上海某台部署了user-service的服务器
- 二级:集群 如上海或者杭州
- 一级:服务 如user-service
5.3.2 同集群优先的负载均衡
默认的
ZoneAvoidanceRule并不能根据同集群优先实现负载均衡因此Nacos中提供了一个NacosRule的实现,可以优先从集群中挑选实例
给order-service配置集群信息,修改
application.yml,将集群改为HZ,- 并修改负载均衡规则,规则是
com.alibaba.cloud.nacos.ribbon.NacosRule1
2
3
4
5
6
7
8
9spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称,杭州
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
- 并修改负载均衡规则,规则是
将三个实例的user-service的日志清空,重启order-service,然后多次调用向order-service发送请求,发现只有HZ集群的实例才会执行,而其他集群不会。
- 并且是在相同集群的实例里随机挑选
将HZ集群的实例停用,然后再次发送请求,这次SH集群的实例运行了。
NacosRule负载均衡的策略
- 优先选择本地集群的服务实例列表
- 本地集群找不到实例,才回去其他集群勋章,并且会报警告
- 确定了可用实例列表之后,采用随机负载均衡挑选实例。
5.4 权重配置
实际部署中肯定会有这样的场景
- 服务器设备性能有差异,部分实例所在的机器性能较好,而另一些较差,所以我们希望性能好的机器承担更多的用户请求。
- 但默认下NacosRule是集群随机挑选,不会考虑机器性能问题。
因此Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高(权重范围0~1之间)
在Nacos控制台,找到user-service的实例列表,点击标记,即可以修改权重
注意:若权重为0则该实例永远不会被访问
我们可以将某个服务的权重修改为0,然后进行更新,然后也不会影响到用户正常访问别的服务集群,之后我们可以给更新后的该服务设置一个很小的权重,这样就会有一小部分用户访问该服务,测试服务是否稳定(类似于灰度测试)。
5.5 环境隔离
- Nacos提供了namespace来实现环境隔离功能
- nacos中可以有多个namespace
- namespace下可以有group,service等
- 不同的namespace之间互相隔离,例如不同的namespace的服务互相不可见
创建namespace
- 默认情况下,所有service,data,group都是在同一个namespace,名为public
- 我们点击
命名空间->新建命名空间->填写表单,可以创建一个新的namespcae - 如图,我们新建了个命名空间
dev

alt 命名空间
给微服务配置namespace
- 给微服务配置namespace只能通过修改配置了实现
- 例如,修改order-service的
application.yml文件
1
2
3
4
5
6cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称,杭州
namespace: 982827c7-8362-478f-a1c5-c1a208fa5ab4 # 命名空间,填上图中的命名空间ID- 重启order-service,访问nacos控制台,可以看到order-service在dev下,此时访问order-service,因为namespace不同,就会导致找不到user-service,若访问则会直接报错
5.6 Nacos和Eureka的区别
Nacos的服务实例可以分为两种类型
- 临时实例:如果实例宕机超过一定时间,会从服务列表中删除,是默认的类型
- 非临时实例:如果实例宕机,不会从服务列表剔除,也叫永久实例
配置一个服务实例为永久实例
1
2
3
4
5spring:
cloud:
nacos:
discovery:
ephemeral: false ## 设置为非临时实例Nacos和Eureka整体结构相似,服务注册,服务拉取,心跳等待,但是也存在一定的差异

alt Nacos注册中心原理图 Nacos与Eureka的共同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康监测
Nacos与Eureka的区别
- Nacos支持服务端主动检测提供者状态:
- 临时实例采用心跳模式。
- 非临时实例采用主动检测模式 (但是对服务器压力比较大,不推荐)
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
AP方式:强调数据可用性
CP方式:强调数据一致性和可靠性
6. Nacos配置管理
- Nacos除了可以做注册中心,同样还可以做配置管理来使用
6.1 统一配置管理
- 当微服务部署的实例越来越多,达到数十数百时,修改服务器配置就会让人抓狂,而且容易出错,所以我们需要一种统一配置管理方案,可以集中管理所有实例的配置。
- Nacos以方便可以将配置集成管理,另一方面可以在配置变更时,及时通过微服务,实现配置的热更新。
6.1.1 在Nacos中配置文件
- 如何在Nacos中管理配置?
- 点击
配置管理–>配置列表–>创建配置 - 选择YAML文件格式,将需要热更新的配置写进去
注意:只有需要热更新的配置才有放到Nacos管理的必要,一些基本不会更改的配置,如数据库的连接,还是保存到微服务本地比较好
- 点击
6.1.2 从微服务拉取配置
这里为了演示,将时间格式配置放到Nacos配置文件中
1
2pattern:
dateformat: yyyy-MM-dd HH:mm:ss微服务要拉取Nacos中管理的配置,并且与本地的
application.yml配置合并,才能完成项目启动但是如果上位读取
application.yml,怎么得知Nacos地址?Spring引入了一种新的配置文件:
bootstrap.yml,会在application.yml之前被读取,流程如下:- 项目启动
- 加载
bootstrap.yml,获取Nacos地址,配置文件id - 根据配置文件id,读取Nacos中的配置文件
- 读取本地配置文件
application.yml,与Nacos拉取的配置合并 - 创建Spring容器
- 加载bean
引入 nacos-config依赖
- 首先在user-service服务中,引入nacos-config的客户端依赖
1
2
3
4
5<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>添加
bootstrap.yml- 在user-service下添加一个
bootstrap.yml文件,内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15spring:
application:
name: user-service # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
enabled: true
namespace: 982827c7-8362-478f-a1c5-c1a208fa5ab4
discovery:
cluster-name: SH # 集群名称,上海
namespace: 982827c7-8362-478f-a1c5-c1a208fa5ab4 # 命名空间,填上图中的命名空间ID
- 在user-service下添加一个
这里根据
spring.cloud.nacos:server-addr获取Nacos地址,再根据${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件ID,来读取配置在本例中,就是读取了
user-service-dev.yaml为了测试是否真的读取到,我们在user-service的UserController中添加业务,读取nacos中的配置信息
pattern.dateformat配置1
2
3
4
5
6
7
private String dateformat;
public String time(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}打开浏览器,访问http://localhost:8081/user/time 会看到时间
踩过的坑
这是我踩过最傻逼的坑,傻逼黑马,傻逼grok,我在这里搞了三个小时才搞出来🤬
- 我的user-service被dev命名空间管理着,所以我就像将配置管理也写在dev命名空间里,但是发现咋运行也运行不了。
- 我问了grok,他一会说我需要用
@NacosValue注解 ,一会又要让我加bootstrap的依赖,结果都不行 - 然后我将这个配置也写在了public里,发现终于能运行了
- 为什么?我明明这个实例被dev管理啊,为啥能运行public里的配置
- 然后我才发现nacos的傻逼之处😅
- 我之前是在discovery里配置了服务注册在dev里,我需要在config(配置中心)里也加上一个namespace指向dev。
- 太傻逼了。
6.2 配置热更新
- 我们最终的目的,是修改Nacos中的配置,微服务中无需重启也可以让配置生效,也就是配置热更新
- 要实现配置热更新,有两种方式
刷新作用域
- 在
@Value注入的变量类上添加注解@RefreshScope(刷新作用域)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserController {
private UserService userService;
public User queryById( Long id) {
return userService.queryById(id);
}
private String dateformat;
public String time(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
}- 测试是否热更新
- 启动服务,打开浏览器,访问
http://localhost:8081/user/time,由于我们之前配置的dateformat是yyyy-MM-ddMM:hh:ss,所以看到的日期为2025-11-23 21:06:36 - 将Nacos中编辑配置信息改一下,并保存
- 启动服务,打开浏览器,访问
1
yyyy年MM月dd日 HH:mm:ss
- 无需重启服务器,直接刷新页面,看到的是
2025年11月23日 21:07:35
- 在
使用
@ConfigurationProperties注解代替@Value注解- 在user-service服务中,添加一个类,读取
pattern.dateformat属性
1
2
3
4
5
6
public class PatternProperties {
private String dateformat;
}- 在UserController中使用这个类来代替
@Value
1
2
3
4
5
6
7
private PatternProperties patternProperties;
public String time(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}- 在user-service服务中,添加一个类,读取
关于第二种我的理解
SpringBoot会自动把Nacos里最新的配置值实时绑定到标注了@ConfigurationProperties的对象上
Nacos配置中心
│
│ 配置发生变更(比如把 pattern.dateformat 改了)
▼
Spring Cloud Nacos 客户端监听到变更事件
│
│ 发出 RefreshRemoteApplicationEvent 事件(刷新事件)
▼
Spring Boot 的 ConfigurationPropertiesBindingPostProcessor
│
│ 重新把最新的配置值“重新绑定”(re-bind)到所有
│ @ConfigurationProperties 标注的 Bean 的字段上
▼
代码里直接使用这个普通 Java 对象,就能拿到最新值
所以这个配置属性类需要加四个注解
@Component // 必须让 Spring 管理
@ConfigurationProperties(prefix = “pattern”) // 前缀是 pattern
@Data // lombok 自动生成 getter/setter
- 在实际开发场景中,我们通常就使用第二种,@RefreshScope+Value不利于维护性和健壮性,而
ConfigurationProperties实现了零注入入侵,只改值,不会重建Bean
6.3 配置共享
微服务启动时会取Nacos读取了多个配置文件:
[spring.application.name]-[spring.profiles.active].yaml例:user-service-dev.yaml[spring.application.name].yaml例:userservice.yaml这里的意思是读取了一个dev也就是开发环境和读取了一个总环境
而
[spring.application.name].yaml是总环境,会给多个子环境比如dev/test的环境共享配置
6.3.1 添加一个环境共享配置
- 我们在Nacos中添加一个
Data ID为user-service.yaml的文件,编写配置内容如下1
2
3
4
5
6
7
8
9pattern:
envSharedValue: 多环境共享属性值
```
* 修改`user-service-dev.yaml`
```yml
pattern:
dateformat: yyyy/MM/dd HH:mm:ss
env: user-service开发环境配置
6.3.2 在user-service中读取共享配置
修改我们的
PatternProperties类,添加envSharedValue和env属性1
2private String envSharedValue;
private String env;同时修改UserController,添加一个方法
prop- 这里是为了测试有没有读取到总环境配置的共享
1
2
3
4
public PatternProperties prop() {
return patternProperties;
}
- 这里是为了测试有没有读取到总环境配置的共享
先启动
UserApplication1,这时我们在bootstrap.yml的配置文件里写了,这个是dev开发环境将profiles:active从dev改成test,然后再启动
UserApplication2,这样就是两个不同环境下的两个实例,但是他们都归属于user-service总环境,被其配置共享。然后在Nacos里新建一个
user-service-test.yaml1
2
3pattern:
dateformat: yyyy-MM-dd HH:mm:ss
env: user-service测试环境配置现在
- UserApplication实例读取了
user-service-dev.yaml和user-service.yaml - UserApplication2实例读取了
user-service-test.yaml和user-service.yaml
- UserApplication实例读取了
打开浏览器分别访问http://localhost:8081/user/prop 和http://localhost:8082/user/prop,看到的结果如下
- 8081:
{"dateformat":"yyyy/MM/dd HH:mm:ss","envSharedValue":"多环境共享属性值","env":"user-service开发环境配置"} - 8082:
{"dateformat":"yyyy-MM-dd HH:mm:ss","envSharedValue":"多环境共享属性值","env":"user-service测试环境配置"}
- 8081:
由此可见,实例1和实例2都读取到了总配置环境user-service.yaml的配置,然后分别读取他们自己的子环境的配置
6.3.3 配置共享的优先级
- 当Nacos、服务笨蛋同时出现相同属性时,优先级也有高低之分
- 服务名-profile.yaml > 服务名.yaml > 本地配置
- user-service-dev.yaml > user-service.yaml > application.yaml
- 服务名-profile.yaml > 服务名.yaml > 本地配置
6.4 搭建Nacos集群
- 集群结构图
Nacos生产环境下一定要部署为集群状态
官方给的Nacos集群图:

alt Nacos集群图 其中包含了三个Nacos节点,然后一个负载均衡器配置三个Nacos,这里负载均衡器可以用 Nginx。
7. Feign远程调用
先看看以前如何使用
RestTemplate发起远程调用的代码1
2
3
4//2.1 URL地址,用user-service代替地址
String url = "http://user-service/user/" + order.getUserId();
//2.2 发起调用
User user = restTemplate.getForObject(url, User.class);存在以下问题:
- 代码可读性差,编程体验不统一
- 参数复杂的URL难以维护(百度随便搜一个中文名词,然后看一下url有多长,有多少参数)
我们可以利用Feign来解决上面提到的问题
- Feign是一个声明式的http客户端,官网地址https://github.com/OpenFeign/feign, 其作用就是帮助我们优雅的实现http请求的发送
7.1 Feign代替RestTemplate
Feign的使用步骤:
引入依赖
- 给order-service的pom文件引入Feign的依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 给order-service的pom文件引入Feign的依赖
添加注解
- 在order-service的启动类上添加
@EnableFeignClients注解,开启Feign功能
- 在order-service的启动类上添加
编写Feign客户端
在order-service中新建com.itcast.order.client包,然后新建一个接口,内容如下
这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如
- 服务名称:user-service
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了
- 测试
- 修改order-service中的
OrderService类中的queryOrderById方法,使用Feign客户端代替RestTemplate
- 修改order-service中的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private OrderMapper orderMapper;
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2. 远程查询User
User user = userClient.findById(order.getUserId());
//3. 存入user
order.setUser(user);
// 4.返回
return order;
}- 总结
- 使用Feign的步骤
- 引入依赖
- 主启动类添加@EnableFeignClients注解
- 编写FeignClient接口
- 使用FeignClient中定义的方法替代RestTemplate
- 注意:这里两个服务的组必须一样(别踩坑)
7.2 自定义配置
- Feign可以支持很多自定义配置,如下表
| 类型 | 作用 | 说明 |
|---|---|---|
| feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
| feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
| feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
| feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
| feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
- 一般情况下,默认值就能满足我们的使用,如果需要自定义,只需要创建自定义的
@Bean覆盖默认的Bean即可,下面以日志为例演示如果自定义配置
基于配置文件修改Feign的日志级别
可以针对单个服务
1
2
3
4
5feign:
client:
config:
userservice: ## 针对某个微服务的配置
loggerLevel: FULL ## 日志级别也可以针对所有服务
1
2
3
4
5feign:
client:
config:
default: ## 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL ## 日志级别而日志的级别分为四种
NONE:不记录任何日志信息,这是默认值BASIC:仅记录请求的方法,URL以及响应状态码和执行时间HEADERS:在BASIC的基础上,额外记录了请求和响应头的信息FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据
修改之后发送请求,发现日志很完整
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2211-24 18:40:17:623 INFO 32172 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
11-24 18:40:17:623 INFO 32172 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
11-24 18:40:17:626 INFO 32172 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 3 ms
11-24 18:40:17:654 INFO 32172 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
11-24 18:40:17:768 INFO 32172 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
11-24 18:40:17:775 DEBUG 32172 --- [nio-8080-exec-1] c.i.order.mapper.OrderMapper.findById : ==> Preparing: select * from tb_order where id = ?
11-24 18:40:17:785 DEBUG 32172 --- [nio-8080-exec-1] c.i.order.mapper.OrderMapper.findById : ==> Parameters: 101(Long)
11-24 18:40:17:796 DEBUG 32172 --- [nio-8080-exec-1] c.i.order.mapper.OrderMapper.findById : <== Total: 1
11-24 18:40:17:800 DEBUG 32172 --- [nio-8080-exec-1] cn.itcast.order.client.UserClient : [UserClient#findById] ---> GET http://user-service/user/1 HTTP/1.1
11-24 18:40:17:800 DEBUG 32172 --- [nio-8080-exec-1] cn.itcast.order.client.UserClient : [UserClient#findById] ---> END HTTP (0-byte body)
11-24 18:40:18:402 INFO 32172 --- [nio-8080-exec-1] c.netflix.loadbalancer.BaseLoadBalancer : Client: user-service instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=user-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
11-24 18:40:18:406 INFO 32172 --- [nio-8080-exec-1] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
11-24 18:40:18:455 WARN 32172 --- [nio-8080-exec-1] c.alibaba.cloud.nacos.ribbon.NacosRule : A cross-cluster call occurs,name = user-service, clusterName = HZ, instance = [Instance{instanceId='192.168.74.1#8081#SH#DEFAULT_GROUP@@user-service', ip='192.168.74.1', port=8081, weight=1.0, healthy=true, enabled=true, ephemeral=true, clusterName='SH', serviceName='DEFAULT_GROUP@@user-service', metadata={preserved.register.source=SPRING_CLOUD}}, Instance{instanceId='192.168.74.1#8082#SH#DEFAULT_GROUP@@user-service', ip='192.168.74.1', port=8082, weight=1.0, healthy=true, enabled=true, ephemeral=true, clusterName='SH', serviceName='DEFAULT_GROUP@@user-service', metadata={preserved.register.source=SPRING_CLOUD}}]
11-24 18:40:18:474 DEBUG 32172 --- [nio-8080-exec-1] cn.itcast.order.client.UserClient : [UserClient#findById] <--- HTTP/1.1 200 (671ms)
11-24 18:40:18:474 DEBUG 32172 --- [nio-8080-exec-1] cn.itcast.order.client.UserClient : [UserClient#findById] connection: keep-alive
11-24 18:40:18:474 DEBUG 32172 --- [nio-8080-exec-1] cn.itcast.order.client.UserClient : [UserClient#findById] content-type: application/json
11-24 18:40:18:474 DEBUG 32172 --- [nio-8080-exec-1] cn.itcast.order.client.UserClient : [UserClient#findById] date: Mon, 24 Nov 2025 10:40:18 GMT
11-24 18:40:18:474 DEBUG 32172 --- [nio-8080-exec-1] cn.itcast.order.client.UserClient : [UserClient#findById] keep-alive: timeout=60
11-24 18:40:18:474 DEBUG 32172 --- [nio-8080-exec-1] cn.itcast.order.client.UserClient : [UserClient#findById] transfer-encoding: chunked
11-24 18:40:18:474 DEBUG 32172 --- [nio-8080-exec-1] cn.itcast.order.client.UserClient : [UserClient#findById]
11-24 18:40:18:474 DEBUG 32172 --- [nio-8080-exec-1] cn.itcast.order.client.UserClient : [UserClient#findById] {"id":1,"username":"柳岩","address":"湖南省衡阳市"}
11-24 18:40:18:474 DEBUG 32172 --- [nio-8080-exec-1] cn.itcast.order.client.UserClient : [UserClient#findById] <--- END HTTP (59-byte body)基于
Java代码修改日志级别- 先声明一个类,然后声明一个Logger.Level的对象
1
2
3
4
5
6public class DefaultFeignConfiguration {
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; //日志级别设置为 BASIC
}
}- 如果要全局生效,将其放到启动类的
@EnableFeignClients这个注解中- 注意,这里修改的是启动类里的
@EnableFeignClients注解里
- 注意,这里修改的是启动类里的
1
- 如果局部生效,则把它放到对应的
@FeignClient注解中
1
绝大部分情况下,我们都用第一种方式来实现对Feign的自定义配置
7.3 Feign使用优化
Feign底层发起http请求,依赖于其他的框架,其底层框架实现包括
URLconnnection:默认实现,不知识连接池Apache HttpClient:支持连接池OKHttp:支持连接池
因此提高Feign的性能主要手段就是使用连接池,
这里使用
Apache HttpClient
引入依赖
- 在 order-service的pom文件中引入Apache的HTTPClient依赖
1
2
3
4
5<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>配置连接池
- 在order-service的
application.yml文件中添加配置
1
2
3
4httpclient:
enabled: true
max-connections: 200 # 最大连接数
max-connections-per-route: 50 # 每个路径的最大连接数- 在order-service的
- 总结
- 日志级别尽量用
BASIC - 使用
httpClient或者OKHttp来代替URLConnection
- 日志级别尽量用
7.4 最佳实践
- 仔细观察,Feign的客户端于服务提供者与Controller层的代码十分相似,
1 |
|
1 |
|
- 他们都有同样的请求方式
GET,都是一样的参数,因为他们都是去实现一个功能:返回给前端用户信息,我们有什么方法取简化这种重复代码的编写呢?
7.4.1 继承方法
这两部分相同的代码,可以通过继承来共享
- 定义一个API接口,利用定义方法,并基于SpringMVC注解做声明
1
2
3
4public interface UserAPI{
User findById( Long id);
}- Feign客户端和Controller都继承该接口
- 这里不做演示,直接说缺点了
- 服务提供方和消费方都继承了一个接口,造成了紧耦合。
- 参数列表中的注解映射不会被继承,所以Controller层必须再次声明方法,参数和注解
7.4.2 抽取方法
- 将Feign的Client抽取为独立的模块,并且把接口有关的POJO,默认的Feign都配置到这个模块,提供给所有消费者使用。
- 举例:
- 将UserClient,User,Feign的默认配置都抽取到一个feign-api包中,所有的微服务都引用该依赖包,即可直接使用

alt Feign抽取方法实现远程调用
- 将UserClient,User,Feign的默认配置都抽取到一个feign-api包中,所有的微服务都引用该依赖包,即可直接使用
7.4.3 实现基于抽取的Feign远程调用
首先新建一个
module,命名为feign-api,然后在POM文件中引入feign的starter依赖1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>将order-service中的UserClient,User等类和接口都转移到feign-api模块里,然后在order-service中的pom文件中引入我们自己编写的feign-api依赖
1
2
3
4
5
6<!-- 引入feign的统一api-->
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>解决包扫描问题
我们实现了上面的代码,启动但是报错了,为什么?
现在UserClient在feign-api模块的feign.client包下,而order-service的
@EnableFeignClients注解在order模块下,不在同一个模块同一个包,无法扫描UserClient解决方法一:指定Feign应该扫描的包
1
- 解决方法二:指定需要加载的Client字节码
1
转移feign配置文件- 因为所有的feign代码都拿到了
feign-api的模块,在微服务模块中只有@EnableFeignClients(clients = UserClient.class)与feign有关,所以将feign所有的配置全转移到feign-api中
1
2
3
4
5
6
7
8
9feign:
client:
config:
default:
loggerLevel: BASIC
httpclient:
enabled: true
max-connections: 200 # 最大连接数
max-connections-per-route: 50 # 每个路径的最大连接数- 因为所有的feign代码都拿到了
- 方法一有个缺陷,会扫描包里的所有客户端,第二个方法只会扫描指定的客户端,所以更推荐第二种
8. Gateway服务网关
- SpringCloudGateway只在于为微服务框架提供一个简单有效的统一的API路由管理方式
8.1 为什么需要网关
Gateway网管是服务的守门神,是所有微服务的统一入口
网管的核心功能特性:
- 请求路由
- 权限控制
- 限流
架构图如下:

alt Gateway架构图 路由:一切请求必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由,当路由的目标服务有多个时,还需要做到负载均衡。
权限控制:网关作为微服务入口,需要校验用户是否有请求资格,如果没有则拦截
限流:当请求量过高时,在网关中按照微服务能够接受的速度来放行请求,避免服务压力过大。
在SpringCloud中网关的实现包括两种:
- gateway
- zuul
Zuul是基于
Servlet实现的,属于阻塞式编程,而SpringCloudGateway则是基于Spring5提供的WebFlux,属于响应式编程,具有更好的性能。
8.2 Gateway快速入门
- 下面,我们来演示一下网关的基本路由功能,基本步骤如下:
- 创建Gateway的模块,引入SpringCloudGateway和nacos的依赖
1
2
3
4
5
6
7
8
9
10<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
编写启动类
1
2
3
4
5
6
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}编写基础配置和路由规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
discovery:
cluster-name: HZ # 集群名称,杭州
namespace: 982827c7-8362-478f-a1c5-c1a208fa5ab4
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可,这里配置的user-service的路由
uri: lb://user-service # 路由地址,lb表示负载均衡,后面跟服务名
# url: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
predicates: # 路由断言,也就是判断是否符合路由规则
- Path=/user/** # 这个按照路径匹配,只要是以/user开头就符合规则
- id: order-service # 路由id,这里配置的order-service的路由
uri: lb://order-service # 路由地址,lb表示负载均衡,后面跟服务名
predicates: # 路由断言,也就是判断是否符合路由规则
- Path=/order/** # 这个按照路径匹配,只要是以/user开头就符合规则重启网关服务进行测试
访问http://localhost:10010/user/1 时,符合/user/**规则 会访问到 http://user-service/user/1
网关路由流程图

alt 网关路由流程图 总结
- 网关搭建的步骤
- 创建项目,引入nacos和gateway依赖
- 配置application.yml,包括服务基本信息,nacos地址、路由
- 路由配置
- 路由id:路由的唯一表示
- 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名称负载均衡
- 路由断言(predicates):判断路由的规则
- 路由过滤器(filters):对请求或相应做处理
- 网关搭建的步骤
8.3 断言工厂
- 我们在配置文件中写的断言规则只是字符串,这些字符串会被
Predicate Factory读取并且处理,转变为路由判断的条件。 - 例如
Path=/user/**式按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类处理的,像这样的断言工厂吗,在SpingCloudGateway中还有十几个
| 名称 | 说明 | 示例 |
|---|---|---|
| After | 是某个时间点后的请求 | - After=2037-01-20T17c:42:47.789-07:00[America/Denver] |
| Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
| Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
| Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
| Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
| Host | 请求必须是访问某个host(域名) | - Host=.somehost.org,.anotherhost.org |
| Method | 请求方式必须是指定方式 | - Method=GET,POST |
| Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
| Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
| RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
| Weight | 权重处理 |
8.4 过滤器工厂
GatewayFliter是网关中提供的一种过滤器,可以对进去网关的请求和微服务返回的响应做处理

alt Gateway过滤器工厂原理图
8.4.1 路由过滤器的种类
- Spring提供了31种不同的路由过滤器,例如
| 名称 | 说明 |
|---|---|
| AddRequestHeader | 给当前请求添加一个请求头 |
| RemoveRequestHeader | 移除请求中的一个请求头 |
| AddResponseHeader | 给响应结果中添加一个响应头 |
| RemoveResponseHeader | 从响应结果中移除有一个响应头 |
| RequestRateLimiter | 限制请求的流量 |
官方文档的使用举例
1 | spring: |
This listing adds X-Request-red:blue header to the downstream request’s headers for all matching requests.
8.4.2 请求头过滤器
下面我们以
AddRequestHeader为例,作为讲解需求:给所有进去user-service实例的请求都添加一个请求头: World.search(you);
只需要修改gateway服务的
application.yml文件,添加路由过滤即可1
2
3
4
5
6
7
8
9gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可,这里配置的user-service的路由
uri: lb://users-service # 路由地址,lb表示负载均衡,后面跟服务名
# url: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
predicates: # 路由断言,也就是判断是否符合路由规则,如果不符合就拦截
- Path=/user/** # 这个按照路径匹配,只要是以/user开头就符合规则
filters:
- AddRequestHeader=HelloWorld,where are you # 添加请求头,注意是KV对当前过滤器写在user-service路由下,因为仅对user-service实例的请求有效,我们在UserController中编写对应的方法来测试
1
2
3
4
public void test( String tmp) {
System.out.println(tmp);
}
8.5 全局过滤器
- 上面的过滤器的每一种作用都是固定的,如果我们希望拦截请求,做业务逻辑,则无法实现,这就需要用到我们的全局过滤器
8.5.1 全局过滤器的作用
也是处理一切进入网关的请求和微服务相应,与GatewayFilter的作用一样,区别在于GatewayFilter通过配置定义,处理的逻辑是固定的,而
GlobalFilter的逻辑需要我们自己编写代码去实现定义的方式就是
借助GlobalFilter接口
1 | public interface GlobalFilter { |
- 在filter中编写自定义逻辑,可以实现以下功能
- 登录状态判断
- 权限校验
- 请求限流
8.5.2 自定义全局过滤器
需求:定义全局过滤器,拦截请求,判断请求参数是否满足以下条件
- 参数中是否含有
authorization authorization参数是否有admin
- 参数中是否含有
如果同时满足,则放行,否则拦截
具体实现如下:
- 在gateway模块下新建一个filter包,在其中编写
AuthorizationFilter类,实现GlobalFilter接口,重写filter方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1. 获取请求参数
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
//2. 获取authorization参数 getFirst代表取出第一个匹配的
String authorization = queryParams.getFirst("authorization");
//3. 校验
if("admin".equals(authorization)){
//4. 满足条件放行 这里是传给下一个过滤器
return chain.filter(exchange);
}
//5. 不满足条件,设置状态码,常量底层是401 在restful中401表示未登录
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//6. 结束处理
return exchange.getResponse().setComplete();
}- 在gateway模块下新建一个filter包,在其中编写
给过滤器加上
@Component,注入到Spring里,并加上@Order注解来设置过滤器优先级这时访问 http://localhost:10010/user/1 会显示401未登录。
访问 http://localhost:10010/user/1?authorization=admin 就能查到数据过滤器执行顺序
- 请求进入网关会碰到三类过滤器:当前路由的过滤器,DefaultFilter,GlobalFilter
- 请求路由后,会将当前路由过滤器和DefaultFilter,GlobalFilter合并到一个过滤器链(集合)中,排序后一次执行每个过滤器
- 排序的顺序就是看过滤器的order值,order值越小,优先级越高,执行顺序越靠前(默认值为2147483647,即int最大值)
8.6 跨域问题
跨域
域名不一样就是跨域,包括:- 域名不同:
www.baidu.com和www.bilibili.com - 域名相同,端口不同:
localhost:8080和localhost:8081
**
- 域名不同:
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方法:
CORS- CORS是一个W3C标准,全程是跨域资源共享(Cross-origin resource sharing)
- 他允许浏览器向跨源服务器发送
XMLHttpRequest请求。
解决跨域问题:
- 在gateway服务的application.yml文件中添加以下配置
1 | spring: |
9. 结语
- 一共学了四天,完结撒花 -2025/11/25
- 标题: SpringCloud
- 作者: yin_bo_
- 创建于 : 2025-11-21 15:25:59
- 更新于 : 2025-11-25 17:50:00
- 链接: https://www.blog.yinbo.xyz/2025/11/21/微服务/SpringCloud/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。














