统一服务入口-Gateway
目录
网关介绍
问题引出
API 网关
核心功能
常见网关实现
Zuul
Spring Cloud Gateway
Spring Cloud Gateway
快速使用
创建网关项目
引入网关依赖
启动类
添加 Gateway 的路由配置
测试:
Route Predicate Factories
Predicate
Route Predicate Factories
代码示例
Gateway Filter Factories(网关过滤工厂)
GatewayFilter
快速使用:
GatewayFilter
GlobalFilter
快速使用
过滤器执行顺序
自定义过滤器
自定义 GatewayFilter
定义 GatewayFilter
自定义 GlobalFilter
服务部署
完!
网关介绍
问题引出
前面的课程中,我们通过 Eureka,Nacos 解决了服务注册,服务发现的问题,使用 Spring Cloud LoadBalanced 解决了负载均衡问题,使用 OpenFeign 解决了远程调用问题。
但是,当前的所有微服务接口都是直接对外暴露的,可以直接通过外部进行访问。为了保证对外服务的安全性,服务端实现的微服务接口通常带有一定的权限校验。
由于使用了微服务模块,原本一个应用的多个模块被拆分成了多个应用,导致我们需要进行多次逻辑校验。当逻辑校验发生变化时,就需要改变多个应用。
针对这个问题,常用的解决方式是使用 API 网关。
API 网关
API 网关(简称网关)也是一个服务,通常是后端服务的唯一入口。它的定义类似设计模式中的门面模式,可以理解为整个微服务架构的前台门面。所有访问微服务的外部客户端,都需要经过它来进行调度和过滤。

核心功能
1. 权限控制:作为微服务的入口,对用户进行权限校验,如果权限校验失败则进行拦截。
2. 动态路由:一切请求先经过网关,但网关不对业务进行处理,而是根据某种规则,把请求转发到某个微服务。
3. 负载均衡:当路由的目标服务有多个的时候,还需要做负载均衡。
4. 限流:请求流量过高的时候,按照网关中配置的微服务能够接收的流量进行放行,避免服务压力过大。
常见网关实现
实现网关的方式较多,技术方案较成熟,有很多开源产品,例如:Nginx,Kong,,Zuul,Spring Cloud Gateway 等。
Zuul

Spring Cloud Gateway

Spring Cloud Gateway
快速使用
在代码使用中,了解网关的功能作用。
创建网关项目
可以直接在前面的 openfeign 项目中继续完善。

引入网关依赖

启动类

添加 Gateway 的路由配置
在 application.yml 文件中,添加如下配置

配置字段说明:
1. id:路由规则的唯一标识符,用于区分不同的路由规则
2. uri:指定目标服务的地址。
lb:// 前缀表示使用负载均衡
product-service 是目标服务在注册中心的服务名称
3. predicates(断言):定义路由匹配条件,当请求满足所有断言条件时,路由才会到对应的 uri
Path 断言:
基于路径的匹配。 /product/** 表示匹配所有以 /product 开头的请求
工作流程:
1. 客户端请求对应 ip
2. 网关匹配到 product-service 路由规则
3. 从 Nacos 获取 product-service 的服务实例列表
4. 通过负载均衡选择一个实例
5. 将请求转发到选中的实例
测试:
启动 API 网关服务
1. 通过网关服务访问 product-service
127.0.0.1:10030/product/1001

此时的 url 符合我们刚刚在 yml 文件中配置的 Path 断言:/product/** 规则,路由转发到 product-service:http://product-service/product/1001
访问后,我们可以观察网关服务的日志,看到网关服务从 Nacos 获取服务列表:

2. 通过网关服务访问 order-service
127.0.0.1:10030/order/1
url 服务 yml 文件中配置的 Path 断言:/order/** 规则,路由转发到 order-service:http://order-service/product/1001
Route Predicate Factories
Predicate
Predicate 是 Java 8 提供的一个函数式编程接口,接收一个参数返回一个布尔值,用于条件过滤,请求参数的校验。

测试使用:



Predicate 的其他写法:
1. 内置函数(匿名内部类):

2. lambda 写法
有匿名内部类的写法,就也可以简化为 lambda 写法

再补充: s -> s.isEmpty() 也可以写成 String::isEmpty;
String::isEmpty 是一种直接方法引用。
对于一种”把接收到的参数直接当作调用者来执行某个方法“的情况,可以用方法引用来实现进一步的简写。
String::isEmpty 的工作流程:
这行代码 Predicate<String> predicate3 = String::isEmpty; 的意思是:
1. :: 是方法引用的操作符
2. String 是类名
3. isEmpty 是该类的一个实例方法
编译器理解:

Predicate 的其他方法:


组合使用示例:
List<String> list = Arrays.asList("Java", "Predicate", "Test", "API");// 1. 创建一个 "条件A" (Predicate A)
Predicate<String> lengthCheck = s -> s.length() > 4;// 2. 创建一个 "条件B" (Predicate B)
Predicate<String> startsWithJ = s -> s.startsWith("J");// 3. 在 filter 中使用
// 筛选出长度大于4的
list.stream().filter(lengthCheck).forEach(System.out::println);
// 输出: Predicate// 4. 组合使用:筛选出长度大于4,且以J开头的 (A && B)
list.stream().filter(lengthCheck.and(startsWithJ)).forEach(System.out::println);
// 输出: (无) // 5. 组合使用:筛选出长度大于4,或以J开头的 (A || B)
list.stream().filter(lengthCheck.or(startsWithJ)).forEach(System.out::println);
// 输出: Java, Predicate
静态方法:isEqual
签名:static <T> Predicate<T> isEqual(Object targetRef)
这是一个静态工厂方法,返回一个 Predicate。这个 Predicate 的作用是判断传入的参数是否等于 targetRef。让我们不必自己动手写 Lambda 表达式,而是直接调用一个方法来得到一个“现成的” Predicate 实例。

Route Predicate Factories
路由断言工厂,在 Spring Cloud Gateway 中,Predicate 提供了路由规则的匹配机制。
我们在 yml 配置文件中的断言规则只是字符串,这些字符串会被 Route Predicate Factory 读取并处理,转变为路由判断的条件。
例如,前面举例中的快速使用,Path = /product/**,就是通过 Path 属性来匹配 URL 前缀是 /product 的请求~~
这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory 实现的。
Spring Cloud Gateway 默认提供了很多 Route Predicate Factory,这些 Predicate 会分别匹配 HTTP 请求的不同属性,并且多个 Predicate 可以通过 and 逻辑进行组合。

代码示例
1. 添加 predicate 规则:

2. 测试
访问:127.0.0.1:10030/order/1
返回 404
3. 修改时间为 2024-01-01,再次访问
127.0.0.1:10030/order/1
正常返回结果
Gateway Filter Factories(网关过滤工厂)
前面的 Predicate 决定了请求由哪一个路由进行处理,如果在请求处理前后需要增加一些逻辑,这就是 Filter(过滤器)的作用范围。
Filter 分为两种类型:Pre 类型和 Post 类型。
Pre 类型过滤器:路由处理之前执行(请求转发到后端服务之前执行),在 Pre 类型过滤器中可以做鉴权,限流等~
Post 类型过滤器:请求执行完毕后,将结果返回给客户端之前执行。

Spring Cloud Gateway 中内置了很多 Filter,用于拦截和链式处理 web 请求。比如权限校验,访问超时等等功能。
Spring Cloud Gateway 从作用范围上,可以将 Filter 分为 GatewayFilter,GlobalFilter
GateFilter:应用到单个路由或者一个分组的路由上
GlobalFilter:应用到所有路由上,也就是对所有的请求生效
GatewayFilter
GatewayFilter 和 Predicate 类似,都是在 yml 配置文件中进行配置。
每个过滤器的逻辑都是固定的,比如 AddRequestParameterGatewayFilterFactory 只需要在配置文件中写 AddRequestParameter,就可以为所有的请求添加一个参数。
快速使用:
在 application.yml 中添加 filter

在 product-service 中接收参数并进行打印

测试
重启 gateway 和 product-service 服务,访问请求,观察日志
127.0.0.1:10030/product/1001
控制台打印日志,接收到参数:zzz
GatewayFilter
Spring Cloud Gateway 提供了的 Filter 非常多,官方参考文档:qGatewayFilter Factories

前面的 filter 添加在指定路由下,所以只对当前路由生效,若需要对全部路由生效,可以使用
spring.cloud.gateway.default-filters
这个属性需要一个 filter 的列表

GlobalFilter
GlobalFilter 是 Spring Cloud Gateway 中的全局过滤器,它和 GatewayFilter 的作用是相同的。
GlobalFilter 通常用到所有的路由请求上,全局过滤器通常用于实现与安全性,性能监控,日志记录等相关的全局功能。
Spring Cloud Gateway 内置的全局过滤器也有很多,比如:
GatewayMetricsFilter: 网关指标,提供监控指标
ForwardRoutingFilter: 用于本地forword,请求不转发到下游服务器
LoadBalancerClientFilter: 针对下游服务,实现负载均衡.
官方文档:GlobalFiter
快速使用
1. 引入依赖

2. 添加配置

配置讲解:




3. 测试
访问:127.0.0.1:10030/actuator 显示所有监控的信息链接

过滤器执行顺序
一个项目中,既有 GatewayFilter,又有 GlobalFilter 时,执行先后顺序:
请求路由后,网关会把当前项目中的 GatewayFilter 和 GlobalFilter 合并到一个过滤器链(集合)中,并进行排序,一次执行过滤器。

每一个过滤器都必须有一个 int 类型的 order 值,默认值为 0,表示该过滤的优先级。
order 越小,优先级越高,执行顺序就越靠前。
Filter 通过实现 Order 接口,或者添加 @Order 注解来指定 order 的值。
Spring Cloud Gateway 提供的 Filter 由 Spring 指定。用户也可以自定义 Filter,由用户自行指定。
当过滤器的 order 值一样的时候,会按照:defaultFilter -> GatewayFilter -> GlobalFilter 的顺序执行
自定义过滤器
Spring Cloud Gateway 提供了过滤器的扩展,可以根据实际业务自定义过滤爱情,同样也是支持 GatewayFilter 和 GlobalFilter 两种
自定义 GatewayFilter
需要实现对应的接口 GatewayFilterFactory,SpringBoot 默认帮我们实现的抽象类是 AbstractGatewayFilterFactory,可以直接使用
定义 GatewayFilter
CustomGatewayFilter 继承 AbstractGatewayFilterFactory,实现 Ordered 接口

AbstractGatewayFilterFactory 需要一个配置类,来接收 yaml 传来的参数,它包含了过滤器所需的配置属性。在Spring Cloud Gateway 中,配置文件中的配置项慧自动映射到这个类中。例如:
CustomConfig 中有一个 name 属性,name 的值可以从 yaml 配置文件中获取并传递到过滤器中。


实现对应的方法,过滤器也需要 Spring 容器帮我们管理:

此外,还需要补充一个 CustomGatewayFilterFactory 的构造方法

在这个构造方法中,调用父类 AbstractGatewayFilterFactory 的构造方法,并传入了 CustomConfig.class 作为参数。

使用这个构造方法,可以使得 Spring Cloud Gateway 能够为过滤器解析配置,告诉 Spring 在 Yaml 文件中如何映射到自定义的配置类。
配置优先级:

然后实现 apply 方法,返回值为 GatewayFilter,new 一个 GatewayFilter 匿名内部类

在 apply 方法中,需要定义自定义过滤器的核心逻辑。apply 方法接收一个 CustomConfig 参数,表示该过滤器的配置。

ServerWebExchange:HTTP 请求-相应交互的契约,提供对 HTTP 请求和相应的访问,服务器端请求属性,请求示例,响应实例类等,类似 Context
其中有如下方法等:

GatewayFilterChain,过滤器链,用于调用下一个过滤器,chain.filter(exchange) 就可以执行过滤器链中的其他过滤器。
Mono:Reactor 核心类,数据流发布者,Mono 最多只触发一个时间,可以把 Mono 用在异步任务完成时发出通知。
Mono.fromRunnable:创建一个包含 Runnable 元素的数据流。then 方法表示过滤链执行完毕后进行的操作。
补充说明:
1. 类名统一以 GatewayFilterFactory 结尾。因为默认情况下,过滤器中的 name 会采用该定义类的前缀。这里的 name = Custom(yml 配置文件中使用)
2. apply 方法中,同时包含 Pre 和 Post 过滤,then 方法中是请求执行结束之后处理的。
3. CustomConfig 是一个配置类,该类只有一个属性 name,和 yml 的配置对应。
4. 该类需要交给 Spring 管理,所以需要加 @Service 注解。
5. getOrder 表示该过滤器的优先级,值越大,优先级越低。
配置过滤器

测试:
重启服务,访问接口,观察日志。
127.0.0.1:10030/product/1001

自定义 GlobalFilter
GlobalFilter 实现比较简单,不需要额外的配置,只需要实现 GlobalFiler 接口,自动会过滤所有的 Filter

重启服务,访问接口,观察日志:

服务部署
1. 修改数据库,Nacos 相关配置
2. 对三个微服务进行打包
3. 上传 jar 包到 Linux 服务器
4. 启动 Nacos (启动前最好将 data 数据删除掉)
5. 启动服务

测试,符合预期~
