目录
6.1 将路由配置保存到 Nacos 中,可以在变更时推送给网关。
1. 关于微服务
1.1 微服务与单体架构的区别
单体架构:将业务的所有功能集中在一个项目中完成,打包成一个包部署。
优点: 1. 架构简单 2. 部署成本低
缺点: 1. 团队协作成本高 2.系统发布效率低
3.系统可用性差(无法解决高并发)
总结:适合开发功能相对简单,规模较小的项目。
微服务架构:是服务化思想指导下的一套最佳实践架构方案。
(服务化:就是把单体架构中的功能模块拆分成为多个模块。)
优点:1.单一职责 2.团队自洽 3.服务自洽
总结:微服务架构解决了单体架构存在的问题,特别适合大型互联网项目的开发,因此被各大互联网公司普遍采用。分布式就是服务拆分的过程,其实微服务架构正式分布式架构的一种最佳实践的方案。
1.2 SpringCloud 技术
SpringCloud 是目前国内使用最广泛的微服务框架,集成了各种微服务功能和组件,并基于 SpringBoot 实现了这些组件的自动装配,从而提供了良好的开箱即用体验。(SpringCloud 与 SpringBoot 的版本有对应要求)
SpringCloud版本 | SpringBoot版本 |
---|---|
2022.0.x aka Kilburn | 3.0.x |
2021.0.x aka Jubilee | 2.6.x, 2.7.x (Starting with 2021.0.3) |
2020.0.x aka Ilford | 2.4.x, 2.5.x (Starting with 2020.0.3) |
2.2.x, 2.3.x (Starting with SR5) | |
2.1.x | |
2.0.x | |
1.5.x | |
1.5.x |
2. 学习前准备
2.1 环境搭建
将之前在 Docker 学习中导入的黑马商城项目移除并且重新配置好Mysql数据库
使用 IDEA 打开资料中的hmall 项目,将 application-local.yaml 配置文件中的地址一栏修改为本机的虚拟机地址,而后在使用 Alt + 8 调出 Services 将 SpringBoot 的运行环境更改为 local 。
将资料中已经配置好的 nginx 在命令行窗口 start nginx.exe 。需要注意项目使用的 jdk 的版本是 11 需要更改为想应的 jdk 、mybatisplus 版本为 3.4.2 。
2.2 熟悉项目
之后我们需要先熟悉 黑马商城 这个项目结构,它按功能可以分为 用户 商品 购物车 订单 支付 这五大部分,在之后的微服务拆分中我们也是按照这五大功能来拆分的(纵向拆分)。
其次,我们需要了解 服务拆分原则
1.什么时候拆:
1)创业型项目:先单体架构 ,快速开发,快速试错,随着规模扩大,逐渐拆分。
2)确定的大型项目:资金充足,目标明确,可以直接选择微服务架构。
2.怎么拆(方向):
1)纵向拆分:按照业务模块进行拆分。
2)横向拆分: 抽取公共服务,提高复用性。
目标:
高内聚:每个微服务的取责尽量单一,包含的业务关联度高,完整度高。
低耦合:每个微服务的功能要相对独立,尽量减少对其他服务的依赖。
分类:
1)独立的 project:拆分的每个微服务都是 project ,都放入一个文件夹中,项目结构上是分离的,耦合度低,管理困难。
2)Maven 聚合:拆的每个微服务作为模块,包含在一个项目中。
3. 正式拆分
3.1 拆分商品功能模块
首先我们要将项目中的 商品 与 购物车 功能模块拆出,以 hmall 作为父工程在其下创建新的item-service 模块,导入所需的依赖,创建相应结构目录,创建 SpringBoot 的启动类,复制配置文件,将端口改为 8081 避免冲突,以及微服务名称、数据库名称(需提前将资料中单独功能模块的数据库导入),还有底部 swagger 文档配置扫描包结构的更改。
对应目录结构
将 hm-service 中与商品有关的类全部迁移到新建的 item-service 中,拆分的顺序依次为domain ——> mapper ——> service ——> controller 最后再检查是否遗漏相应的 config 配置类、enums 枚举类、utils 工具类等。
到此,商品功能模块的拆分就结束了,再将 service 界面中的 item-service 启动类的运行环境更改为 local 就可以启动服务了,地址栏输入 localhost:8081/doc.html 进入 swagger 接口文档管理界面,测试接口根据 id 批量查询,如下图响应成功返回状态码 200 。
【Tips】 想要成功运行需要开启的程序有 资料中的 nginx 以及 Linux 虚拟机中的 mysql 。
3.2 拆分购物车功能模块
拆分购物车功能模块与拆分商品功能模块的方法区别不大,依照上面的步骤可以解决大部分问题,但在 cart-service 功能模块中我们发现需要实现 购物车 中的 实时 显示价格距加入购物车时的变化时,需要调用购物车中的相应方法,而将这些方法重新引入到cart-service 功能模块中时会让代码变得 冗余,导致我们无法查询。
这就引出了我们要学习的新技术 —— RestTemplate(远程调用)
4. 服务调用
4.1 介绍
在拆分的时候,我们发现一个问题:就是购物车业务中需要查询商品信息,但商品信息查询的逻辑全部迁移到了 item-service 服务,导致我们无法查询,一旦服务做了拆分,数据做了隔离,每个服务只能使用自己的服务和数据,无法使用别的服务的数据。
最终结果就是查询到的购物车数据不完整,因此要想解决这个问题,我们就必须改造其中的代码,把原本本地方法调用,改造成跨微服务的远程调用(RPC,即Remote Produce Call),解决了微服务之间无法互相调用之间的方法的问题
原理:微服务之间虽然在物理层面上被隔离开来,但是在网络上是可以互通的,我们可以通过网络发送请求(Http),来进行微服务之间的连接。
RestTemplate
Spring 提供 RestTemplate 工具,实现了 Http 请求的发送,是 Spring 用于同步 client 端的核心类,简化了与 http 服务的通信,程序代码可以给它提供 URL ,并提取结果。默认情况下,RestTemplate 默认依赖jdk的 HTTP 连接工具。
4.2 RustTemplate 的使用
使用方法
1)先将 RustTemplate 配置到 Spring 容器中,注册成一个 Bean。
2)发起远程调用。
在 service 的实现类中注入 RustTemplate,可以使用构造函数注入,但我们可以简化。在注入的元素前加上 final 修饰使之必须初始化,必需使用构造函数初始化,但我们可在类上加上 @RequirdArgsConstructor 注解代替构造函数。
restTemplate 方法解析
restTemplate(url, 请求方式, 请求实体, 返回值类型, 请求参数)
请求实体可以填 null ,在传递的返回值类型为 List<> 时不能使用使用字节码,它的泛型会被字节码擦除。
- 当Java编译器编译带有泛型的代码时,它会进行类型检查以确保类型安全。但是,在生成字节码时,所有的泛型信息都会被擦除。
- 泛型类型被替换为它们的原始类型(即不带泛型的类型)。例如,
List<String>
在运行时会被视为List
,所有的String
类型信息都会被擦除。
所以,想要解决这个问题,需要创建一个对象,这是参数化类型的引用。
new ParameterizedTypeReference<List<ItemDTO>>() {}
在对象的泛型中写入要传的类型,它可以利用反射拿到对象的泛型。最后会返回一个响应 response。
解析响应 response
response.getbody //获取响应体
response.getStatusCode //获取响应状态码
response.getHeaders //获取响应头
将 cart 与 item 两个微服务都启动后,便可实现远程调用,解决了微服务之间无法相互调用之间方法的问题。
存在问题:
1)无法解决压力大,并发请求多的情况,同时端口写死。无法确定端口的状态。
2)服务调用者不知道服务提供者的地址,当存在多个端口时,该调用哪一个。
3)选中的服务挂了,怎么办。
4.3 服务治理-注册中心
该技术用于解决上文中 RustTemplate 存在的问题,用来集中管理微服务,实现服务的注册,发现,检查等功能; 服务 A 与服务 B 注册进注册中心 C,形成服务注册表(表里登记了服务 A 和服务 B 的地址等相关信息)。
注意,可以实现功能的技术有很多,他们都实现了 Spring Cloud 中 DiscoveryClient 这个顶级接口,所以底层性质是类似的,只是细节上略微不同。
服务调用者:调用其他服务提供的接口,通过负载均衡从提供的多个实例中选择一个。
服务提供者:暴露服务接口,供其他服务使用。
注册中心:记录并监控微服务各实例状态,推送服务变更信息。
心跳检测:用于实时的检查服务提供者的状态,防止调用者调取到挂掉的服务。
Nacos 注册中心组件
1.部署
提前将资料中的表导入到 Mysql 中,里面包含了 Nacos 的一些相关信息,同时将资料中准备好的 custom.env Nacos 配置文件连同文件夹以及打包好的 nacos.tar 镜像文件移动到 虚拟机中(配置文件中的 MYSQL_SERVICE_HOST 需要更改为自己的虚拟机 ip 地址)。
使用 docker load -i nacos.tar 命令加载镜像,最后再使用 docker run 命令启动 nacos(mysql 要先于 nacos 启动,因为 nacos 的一些数据储存在 mysql 数据库中,如用户名,密码等)。
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim
启动 Nacos 后,使用浏览器可以访问 http:/【你的虚拟机ip地址】/:8848/nacos 来进入 nacos 官方的控制平台,使用数据库中的用户名和密码登录(账号密码都是 nacos),以后就可以在此页面监控和查看你已在其中注册的微服务。
接下来我们需要在 项目中 引入 nacos 的依赖,同时完成 nacos 的地址的配置。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
地址处为自己虚拟机的 ip 地址
可以看到这时将服务启动后 nacos 的服务管理中出现了对应的功能模块,我们还可以点击详情查看关于单个服务的具体信息,如实例数等。
4.4 服务发现
服务调用者想要去 nacos 订阅服务提供者的过程叫做服务发现,我们需要先引入依赖, 接着再去配置地址,步骤与服务治理中的配置方法一致。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
然后我们就可以去使用它了,先在实现类中注入 DiscoveryClient 然后就可以去订阅其它微服务,动态感知服务提供者的地址 ,解决了地址写死的问题。
1)根据微服务的名称获取事务的服务列表,判断其不为 null。
2)使用负载均衡算法中的随机选择一个实例使用
3)使用 getUri( )方法获取 url 拼接成一串地址。
4.5 OpenFeign 技术
上面使用的方法还是太冗杂繁琐了,仅仅是调用一个微服务中的方法,就写了超过10行代码,所以我们学习了简化这个操作的新技术——OpenFeign,它简化了繁琐的代码,是一种声明式的 Http 客户端。
使用方法
1.引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
2.在启动类上加上 @EnableFeignClient (开关)注解
3.定义一个接口,用于编写 Feign 客户端
@FeignClient(“服务名”) @GetMapping(“完整的请求路径”)
方法与具体微服务中要调用的方法,参数与原本方法一致,不用实现。
4.调用方法
在实现类中注入已经写好的 FeignClient 接口,接着调用接口中的方法获取到商品,feign 替我们完成了服务拉取、负载均衡、发送 http 请求的所有工作,简化了原本繁琐的操作。
4.6 优化 OpenFeign
Feign 底层发起 http 请求,依赖于其它的框架。其底层支持的 http 客户端实现包括:
-
HttpURLConnection:默认实现,不支持连接池,这意味着每次 HTTP 请求都需要建立一个新的连接,并在请求完成后关闭连接。这种方式在处理大量并发请求时会导致性能瓶颈,因为连接的建立和关闭都需要消耗时间和资源。
-
Apache HttpClient :支持连接池,可以显著提高性能,因为连接池允许 Feign 复用已经建立的连接,而不是每次请求都重新建立连接。这样可以减少连接的建立和关闭次数,降低资源消耗,提高请求的响应速度和吞吐量。
-
OKHttp:支持连接池。与 Apache HttpClient 类似,OKHttp 通过连接池来复用连接,提高请求的效率。此外,OKHttp 还提供了异步请求、缓存等功能,进一步增强了其性能和灵活性。
使用步骤:
1)引入 OKHttp 依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
2)在配置中开启连接池功能
重启服务后,连接池就生效了。
4.7 最佳实践
解决重复代码问题
-
思路一:抽取到微服务之外的公共module
-
思路二:每个微服务自己抽取一个module
方案一抽取更加简单,工程结构也比较清晰,但缺点是整个项目耦合度偏高。
方案二抽取相对麻烦,工程结构相对更复杂,但服务之间耦合度降低。
为了避免代码的复用性过高,选择思路一进行实现,不必在每个模块中都编写 Client 接口以及各种 dto 对象,我们可以将其再次拆分,重新聚合成一个 api 模块,统一将要调用的 dto 与 Client 写入一个模块中,之后在有需求的模块中引入 api 模块,解决重复代码问题。
而因为 ItemClient 现在定义到了 com.hmall.api.client 包下,而 cart-service 的启动类定义在 com.hmall.cart 包下,扫描不到 ItemClient,所有我们要在启动类的@EnableFeignClient中加上 api 中 client 所在的路径 ( "com.hmall.api.client" ) ,或声明要使用的 Client ( clients = {ItemClient.class} ) 。
4.8 日志输出
OpenFeign 只会在 Client 所在包的日志级别为 DeBug 时才会输出日志,且日志级别有4级:
NONE:不记录任何日志信息,默认。
BASIC:仅记录请求的方法,URL 及响应状态码和执行时间。
HEADER:在 BASIC 的基础上,额外记录了请求和响应的头信息。
FULL:记录所有的请求和响应的明细,包括头信息、请求体、元数据。
开启步骤:
1)需在 api 模块声明一个 Logger.level 的 Bean 。在其中定义日志级别。
2)1.在 @FeignClient 中声明( configuration = Logger.level 所在配置文件名 )(局部)
2.在启动类上的 @EnableFeignClient(defaultConfiguration = 配置文件.class)(全局)
调试中可以看到日志输出。
5. 网关
5.1 如何使用网关
网关就是网络的关口,负责请求的路由、转发、身份校验。相当于一个小区的保安,所有的请求都需要发送到网关,由它进行统一的校验和转发,并通过负载均衡选择一个微服务进行转发以及通过注册中心拉取相应微服务的地址。对前端来说,只需将请求地址变为网关的地址,与单体架构无异,且里面的微服务相对而言是隐藏的,增加了安全性。
在 SpringCloud 中网关的实现包括两种:
1. Spring Cloud Gateway:
Spring 官方出品,基于 WebFlux 响应式编程,性能优异。(荐)
2.Netflie Zuul:
Netflie 出品,基于 Servlet 的阻塞式编程,需要调优才能获得与前者类似的性能,现在已停止维护。
使用步骤
1)在 hmall 父模块下创建新的模块 hm-gateway 。
2)引入依赖
<dependencies>
<!--common-->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-common</artifactId>
<version>1.0.0</version>
</dependency>
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3)编写启动类
4)配置路由规则
新建 ymal 配置文件,在其中配置 port(端口号)、name(微服务名)、server-addr(服务拉取 nacos 地址)、以及 routes(路由规则)。
其中,id 为微服务名称;uri 为路由目标,lb 为负载均衡协议后跟 ://服务名称;predicates 下接具体的参数 - name 路由规则名 与 - args 参数 可简写为图中 - 路由规则名=参数 ,而多个colltroller 中的请求路径可以在其下方按规则接着写,也可在其后方使用 ‘,’ 隔开接着写。
成功启动项目后,可以看到无论是 8081 端口还是 8080 端口(gateway 模块)都可以成功的查询到商品的数据,证明了网关服务成功的实现了。
5.2 路由属性
进入源码中查看
id:路由的唯一标识。
uri:路由目标地址。
predicates:路由断言,判断请求是否符合当前路由。
filters:路由过滤器,对请求或响应做特殊处理。
Spring 提供了 12 种路由断言
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17: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 | 权重处理 | -Weight=group1,2 |
XForwarded Remote Addr | 基于请求的来源IP做判断 | -XForwardedRemoteAddr192.168.10.100/24 |
以及网关中提供的 33 种路由过滤器,每种过滤器都有其独特的功能。
配置的默认过滤器对所有路由都生效。
网关请求处理流程
HeadlerMapping:路由映射器,基于路由断言进行匹配。 ——> WebHeadler:请求处理器,找到当前请求对应路由生效的过滤器,放到过滤器链中排序,依次执行。——> NettyRouting Filter:默认在所有路由中都生效,在过滤器链的最后执行,负责将请求转发到微服务,当返回结果后将其存入上下文中,并按来时顺序依次返回执行 post 逻辑。
网关:内部含有两个逻辑,pre 和 post,在请求路由到微服务,之前和之后执行,如果 pre 逻辑中的任何检查失败,网关将拦截请求,并直接返回错误响应给客户端,而不会将请求转发到后端的微服务,接收到微服务的响应后,网关执行 post 逻辑。
5.3 自定义 GatewayFilter 过滤器(了解)
1)实现 AbstractGatewayFilterFactory 工厂,实现 apply 方法,返回一个过滤器,我们也可以选择实现装饰类 Ordered 来控制过滤器的执行顺序。
2)自定义一个类用于接受配置的参数。
3)使用本类的构造函数调用父类的构造函数将自定义的参数类的字节码传递给父类,让它帮我们读取配置,固定的类名称后缀 GatewatFilterFactory。
4)实现 shortcutFieldOrder 方法,其中返回收集的参数。
在 application.yaml 配置文件中可以配置 default-filters 为 构造函数前缀名=参数。
5.4 使用网关实现登录校验
1) 如何在网关转发之前做登录校验?
在网关内自定义一个过滤器( pre逻辑 ),进行 JWT 校验。
2)网关如何将信息传递给微服务?
将得到的用户信息存放在请求头中,发送请求到微服务,由微服务获取请求头。
3)如何在微服务之间传递用户信息?
与第二个问题解决方法类似,但微服务之间由 Openfeign 发起,具体实现有所不同。
网关过滤器:
1)GatewayFilter:路由过滤器,作用于任意指定的路由,默认不生效,要配置到路由后生效。
2)GlobalFliter:全局过滤器,global(全局)作用范围是所有路由,声明后自动生效。
两种方法的底层过滤方法签名完全一致,都是 filter 方法。
【Tips】方法签名由 方法名、参数、返回值构成。
filter 方法中第一个参数是请求上下文,包含整个过滤器连内共享的数据。第二个参数是过滤器链,当前过滤器执行完后要调用下一个过滤器,将链条串起来。返回值 Mono 可以调用你写的回调函数完成 post 逻辑,不用等待,是一种非阻塞式的编程,但现在用不到。
代 码 实 现
5.4.1 写入用户信息
首先将必备的 config 配置文件、util 工具类、以及关于 jwt 和 排除路径 配置到 application.yaml 配置文件中。
hm:
jwt:
location: classpath:hmall.jks
alias: hmall
password: hmall123
tokenTTL: 30m
auth:
excludePaths:
- /search/**
- /users/login
- /items/**
创建一个新的拦截器,使用 @Component 标记该类为 Bean,同时实现 GlobleFilter 和 Ordered。
1)获取 Request
我们可以直接从上下文中获取到请求 request 。
2)判断是否要做登录拦截
通过我们自己定义的 isExclude 方法来判断请求路径是否是要排除的路径,使用 request.getPath () 获取请求,注入 AuthProperties(存放了放行的请求路径) 来获取放行的请求路径,在判断方法中,遍历获取放行路径,由于两种路径的格式不同,我们需要使用 Spring 提供的 AntPathMatcher(匹配器)来进行比对,使用 AntPathMatcher.match(参数1,参数2) 来比较。
3)获取 token
通过获取上下文 exchange 中请求头里规定好的 authorization 参数来获取请求头。
4)校验并验证 token
注入 util 包中的 JwtTool 后使用其中提供好的 parseToken () 方法来校验,之后使用 try-catch 捕获异常,在其中通过上下文 exchange 获取响应 response 并将状态码设置为 401 (HttpStatus.UNAUTHORIZED)表示用户未登录,最后返回 response.setComplete() 终止,后续拦截器不再进行,请求也不再转发。
5)传递用户信息
使用上下文 exchange 中提供好的 mutate () 方法就可以对上下文内容进行修改,写入的请求头要规定好名称,方便读取。
6)放行
最后使用 return chain.filter(webExchange) 将修改好的上下文传入放行给下一个过滤器。
5.4.2 获取用户信息
由于最后是将用户信息存放在请求头中传递,所以我们需要用到学过的拦截器来将请求拦截获取用户信息,但每一个微服务都可能需要获取用户的信息所以,我们不能在每一个微服务里都写一个拦截器,所以我们需要将拦截器写在 common 模块中,避免重复编写。
因为在过滤器中已经写好了登录校验的逻辑,所以在拦截器中不需要拦截任何请求,全部放行,只需获取其中的用户信息即可。但在微服务的远程调用中,例如在购物车中提交订单后删除购物车中商品时还需要用户信息,微服务之间是用 OpenFeign 远程调用的,所以需要在它们之间定义一个拦截器将由网关转发请求而被拦截保存的用户信息再次写入到远程调用的请求头中,从而成功传递用户信息。
1)在 hm-common 模块中定义一个拦截器,使之能对所有微服务生效。
1.1 实现 HandlerInterceptor 并重写其中的 preHandle(请求处理前被调用) 和 afterCompletio(整个请求结束后被调用)。
1.2 preHandle 中获取到用户信息存入ThreadLocal 中后放行。
1.3 afterCompletion 中在所有业务完成后清理用户信息。
2)想要拦截器生效,还需在配置中添加拦截器,在 config 包下定义 MvcConfig 配置类实现 WebMvcConfigurer 接口,在 addInterceptors 方法中添加拦截器,不配置默认拦截所有请求。
配置类想要生效需要被扫描到,但包不同无法扫描到。而想要被扫描到,需要在 resources 下的 META-INF 包下定义一个文件记录。
而在网关中引入了 common 的依赖 ,但我们不需要 MvcConfig 配置,且两者之间底层不同,网关底层是非阻塞式的、响应式编程,基于WebFlux,而非 SpringMvc,所以我们需要使用 @ConditionalOnClass 条件注解,在其中加入 SpringMvc 的核心 Api 作为条件进行排除,使之不在网关中生效。
3)OpenFeign 传递用户信息
使用 OpenFeign 中提供好的拦截器接口,之后所有由 OpenFeign 发起的请求都会先调用拦截器处理请求。
我们需要将其定义在所有微服务都引到的 api 模块中,又因为其功能较单一,我们可以将其写为匿名内部类,但因为我们将用户信息存入请求头中需要先获取,所以要在 pom 文件中引入 common 模块。
6.配置管理(扩展)
在微服务、网关等模块里,存在大量重复的配置,维护的成本高,且每一次配置变更都需重启微服务,网关的路由配置也是写死的,更改起来繁琐,因而引出了我们要学习的新技术——配置管理。
将配置统一分功能写入到配置管理中,在由微服务拉取配置,在配置变更时实时推送变更到微服务中。说是新技术,实则是我们此前学习过的 Nacos 中的配置管理组件,我们需要学习的只有功能的使用方法——共享配置、配置热更新、动态路由。
6.1 共享配置
6.1.1 配置到 Nocas 中
将 pom 配置文件中公共的配置写入到 Nacos 的配置管理中,例如 jdbc、MybatisPlus、日志、swgger、OpenFeign 等配置,配置后可点击详情查看。
我们使用 yaml 格式配置,在其中可以使用 ${ } 去动态的读取配置中的变量。像端口配置这种默认为 3306 的也可在其后加上 :3306 定义一个默认值,如果没有在项目中配置,默认端口就为 3306 。 同理别的配置也可以这样配置成动态的去读取,避免写死。
6.1.2 拉取共享配置
基于NacosConfig 拉取共享配置代替为服务的本地配置,但想要在服务启动时拉取 Nacos 的配置必须要知道 Nacos 的地址,而地址又配置在了 SpringBoot 的配置文件中,所以我们要用bootstrap.yaml 文件在其中配置相关信息,项目启动首先就会去读取其中的配置,获取到 Nacos 的地址,最后将 Nacos 的配置与 SpringBoot 中的配置合并。
1)引入依赖
<!--nacos配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
2)新建 bootstrap.yaml 文件
将 yaml 文件的配置复制到其中,删掉不需要的,并加上 config 配置 Nacos 配置。
file-extension 表示配置文件的后缀名;在 shared-configs 下配置共享配置。
- date-id:配置管理中配置的名字
配置好后,修改原有 application.yaml 配置文件中的内容,删除与 Nacos 中相同内容的配置,并在其中写好要读取的变量,例如数据库名称,swagger 标题等。
可以看到,启动后的日志中正在读取 Nacos 中的配置文件。
6.2 配置热更新
配置热更新后,当我们修改项目中的一些配置时,我们无需重启服务就可以使配置生效。
前提条件:
1)Nacos 要有一个与微服务名称有关的配置文件
例: 微服务名称-项目profile(例:dev,可选填). 文件名后缀
spring.applicaition.name-spring.profiles.active.file-extension
但我们会发现,这些已经在之前的 bootstrap 文件中写好,它会自动读取。
2)微服务中要以特定的方式读取需要热更新的配置属性
1.Properties 读取(推荐)
2. @Value 注解读取
案例:将购物车的上限更改为读取配置文件的属性,交给 Nacos 管理,实现热更新。
1.编写 Properties 类,在其中定义一个变量 maxItems。
@ConfigurationProperties(prefix = "hm.cart") 作用是将其与配置文件中前缀为 hm.cart 的属性绑定在一起,加上 @Component 使其生效。
2.在业务层注入 CartProperties 将业务中的固定的数 10 修改为从中读取数据。
2.在 Nacos 中配置
起名遵守前提条件一:微服务名称-项目profile(例:dev,可选填). 文件名后缀
配置内容遵循 @ConfigurationProperties 注解中的描述
配置完成热部署后我们将 maxItems 设置为 2 测试
测试成功
6.3动态路由
我们知道,路由是写死在网关的配置文件中,加载后保存在一个路由表的缓存中,以后会直接读取缓存,提高处理速度,但这样我们修改配置文件中路由后想要生效就只能重启网关,想要解决这个问题,就需要我们利用 Nacos 实现动态路由。
6.1 将路由配置保存到 Nacos 中,可以在变更时推送给网关。
我们使用 json 格式来配置,是为了更方便的解析配置信息,对 json 格式更加熟悉,可以使用工具类进行解析。
6.2 实现更新网关中的路由配置信息。
6.2.1 监听 Nacos 配置解析
需要先读取一次配置,在监听配置的变化,而 alibaba 包中的自动装配里提供了一个已经装配好的 Bean,NacosConfigManager ,只需将其注入,调用其方法就可获取 ConfigService,省去了官方文档中第一步的操作。
而 CongfigService 提供了getConfigAndSignListener 方法可以先拉取配置再注册监听器将步骤二中的两次操作合二为一,最终简化代码。
6.2.2 引入依赖并配置
<!--统一配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--加载bootstrap-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
新建 bootstrap 配置文件配置相关信息
以及将 application.yaml 文件中删除路由信息及无关配置。
最后再创建监听器类,自定义一个初始化路由监听器的方法,在其上加上 @PostConstruct 注解使其在 Bean 初始化之后执行,注入NacosConfigManager 调用其方法先拉取配置,再添加监听器,参数 dataId、group 与 Nacos 配置管理中的一一对应,将其定义为变量传入。新建的监听器中,getExecutor 用来定义一个线程池,但我们的逻辑简单不需要用到。receiveConfigInfo 方法表示当配置变更时要干什么,所以我们需要更新到路由表。在最后我们要将第一次读取到的配置更新到路由表中。
而更新路由表需要用到新的 api 名为 RouteDefinitionWrite ,将其注入到类中,调用其中 save 和 delete 方法可以更新路由表的内容。我们先试用 JSONUtil 工具包中的方法将传来的 json格式信息转为 RouteDefinition 类型,然后根据 Id 遍历删除旧的路由表,开始时 Id 为 null ,在第一次读取后利用定义的 Set 集合容器记录下来,便于下一次更新时删除。之后遍历更新 save 路由信息,需传入SpringBoot 提供的一个响应式编程的容器,可以用 Mono 容器的 just 方法将数据装入,之后跟 subscribe 方法表示订阅容器中的消息,只在其有消息后才去处理。
将项目启动后,添加 Nacos 网关路由的配置,无需重启,即可读取到配置信息,成功访问到网址内容,并在控制台输出监听到的路由配置信息。
有什么问题欢迎指出。
【完结】
标签:网关,服务,请求,SpringCloud,配置,笔记,cloud,黑马,路由 From: https://blog.csdn.net/t46464949/article/details/142030334