【微服务】网关 - Gateway(上)(day7)

server/2024/10/19 18:35:24/

概述

引入

在前几个系列中,使用Eureka、Consul、Nacos解决了服务注册、服务发现的问题;使用SpringCloudLoadBalancer解决了负载均衡的问题;使用OpenFeign解决了远程调用的问题。

但是现在所有的微服务接口都是直接对外暴露的,可以直接通过外部访问。为了保证对外服务的安全性,服务端实现的接口都带有一定的权限校验机制。由于使用了微服务,我们不得不实现多次校验逻辑,当需要修改时,我们需要修改多个应用,加重了开发人员的负担。

对于这个问题,SpringCloud开发了网关组件。网关组件是后端服务的唯一入口,所有的请求访问后端接口时,都需要先到网关,然后网关进行校验和分发。

最开始,SpringCloud整合的网关组件是Netflix公司的Zuul组件,由于该公司宣布组件进入维护状态,不再进行新特性的开发。所以SpringCloud就自研了一款网关组件来替换Zuul,新组件的名称就是SpringCloudGateway。官方出具的测试报告表明Gateway比Zuul要快许多。

功能

权限控制:作为微服务的入口,对用户进行权限校验,如果校验失败则进行拦截。

动态路由:一切请求先经过网关,网关根据某种规则,把请求转发到对应的微服务上。

负载均衡:当路由的服务实例有多个时,网关还要进行负载均衡的处理。

限流:请求流量过高时,网关还能按照配置进行限流处理,防止服务压力过大。

网关还有许多的功能,这里就不过多进行介绍。

三大核心

路由:路由是构建网关的基本模块(毕竟网关的关键作用就是将客户端发来的所有请求按照规则发送到对应的微服务模块上),路由是由ID、目标URI、一系列的断言过滤器组成的,如果断言为真则匹配该路由。

断言断言其实就是一系列的规则,如果请求满足规则,才会进行路由。

过滤:表示GatewayFilter的实例,使用过滤器,可以在请求被路由之前或之后对请求进行修改。

总的来说,网关的大致服务流程就是:客户端先向网关发出请求,其次网关会根据断言找到符合的请求,然后网关会去执行过滤器,最后才会发送到服务中进行访问。访问完成之后,会再次执行过滤器的操作,然后返回到客户端。

代码案例

在此案例中,我们使用上一文章中建立的商品模块和订单模块配合这篇文章中新的网关模块一起进行测试,从而验证网关的作用。

建模块

写pom依赖

网关服务也要注册到服务中心中,因此引入nacos依赖;网关服务肯定要引入自己的依赖;网关服务还能负载均衡,因此引入负载均衡的依赖。

<?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.wbz</groupId><artifactId>spring-cloud-test</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>cloud-gateway-9527</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><!--注册中心--><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-gateway</artifactId></dependency><!--负载均衡--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency></dependencies></project>

写yml文件

server:port: 9527spring:application:name: cloud-gateway-9527cloud:nacos:discovery:server-addr: 127.0.0.1:8848service: ${spring.application.name}gateway:routes:- id: cloud-consumer-order-open-feign-84uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/order/**

gateway中配置文件的简单介绍:

id,类似于MySQL的主键,没有固定的规则但是要求唯一,建议使用服务名。

uri,路由匹配之后要路由去的地址,可以使用IP+端口号的格式,如果想要负载均衡,那就使用lb://服务名的格式。

 predicates,断言,下面有一系列的规则,匹配之后才可以进行路由。

改主启动类

@SpringBootApplication
public class GatewayApplication9527 {public static void main(String[] args) {SpringApplication.run(GatewayApplication9527.class, args);}}

把下述三个模块启动之后,去Nacos中心观察状态:

在浏览器中输入URL:127.0.0.1:9527/order/query/1,只要出现如下画面,就表示网关模块搭建成功。大家还可以输入127.0.0.1:9527/product/query/1001,发现报错,原因就是因为在网关中只配置了对订单服务的配置文件,并没有配置对商品服务的配置文件。

断言

断言其实就是一系列的规则,请求在匹配规则成功之后才会去进行路由。在SpringCloudGateway中,存在RoutePredicateFactories,即路由断言工厂。我们配置文件中的规则,最终会被路由断言工厂读取并处理,转变为路由判断的条件。SpringCloudGateway提供了许多已经写好的路由断言工厂,这些断言会匹配HTTP请求的不同属性,并且多个断言可以通过and逻辑进行组合。当然也可以自定义一个自己想要的路由断言工厂。

配置语法

一共有两种配置语法,第一种是快捷配置方式,由过滤器名开头,后面跟等号,如果有参数的话使用逗号分隔;第二种是完全展开的配置方式,name表示过滤器名或参数名,args表示要配置的参数。

spring:cloud:gateway:routes:- id: cloud-consumer-order-open-feign-84uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/order/test1/**,/order/test2/{id}- id: cloud-provider-product-open-feign-8401uri: lb://cloud-provider-product-open-feign-8401predicates:- name: Pathargs:name[0]: /product/test1name[1]: /product/test2

常用断言

AfterRoutePredicate

表示设置一个时间,当真实时间超过该时间才可以进行访问。

时间的格式是使用ZoneDateTime类的格式,可以自己使用main函数来获取一个时间:

public class ZonedDateTimeTest {public static void main(String[] args) {ZonedDateTime now = ZonedDateTime.now();System.out.println(now);// 2024-10-08T08:43:57.324919400+08:00[Asia/Shanghai]}}

如下两个代码,写一个配置文件和测试代码对After断言工厂进行测试,先使用上述main函数测试出一个ZoneDateTime类型的时间,然后写入配置文件中进行测试。输入URL:127.0.0.1:9527/predicate/after,出现最下面的结果就表示配置成功;然后再把时间改换成2025年,发现没有成功访问,表示after断言工厂的作用就是当前时间必须要大于配置时间,那么才能进行路由。 

spring:cloud:gateway:routes:- id: 1uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/predicate/after- After=2024-10-08T08:43:57.324919400+08:00[Asia/Shanghai]
/*** 关于断言的测试*/@Slf4j
@RestController
@RequestMapping("/predicate")
public class PredicateController {@GetMapping("/after")public String after() {log.info("进入断言测试的After断言工厂");ZonedDateTime zonedDateTime = ZonedDateTime.now();return zonedDateTime.toString();}}

BeforeRoutePredicate

表示真实时间必须在配置的时间之前才可以进行访问。

如下是对Before断言工厂的测试,表示时间要在2024年10月8日之前。

/*** 关于断言的测试*/@Slf4j
@RestController
@RequestMapping("/predicate")
public class PredicateController {@GetMapping("/before")public String before() {log.info("进入断言测试的Before断言工厂");String time = ZonedDateTime.now().toString();log.info("当前时间:" + time);return time;}}
spring:cloud:gateway:routes:- id: 2uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/predicate/before- Before=2024-10-08T09:04:31.397228500+08:00[Asia/Shanghai]

BetweenRoutePredicate

表示当前时间要在配置的两个时间之间,符合既要又要的条件。

如下是对Between断言工厂的测试,表示时间要在2024.10.8和2025.10.8之间,才能路由到接口。

/*** 关于断言的测试*/@Slf4j
@RestController
@RequestMapping("/predicate")
public class PredicateController {@GetMapping("/between")public String between() {log.info("进入断言测试的Between断言工厂");ZonedDateTime now = ZonedDateTime.now();log.info("当前时间:" + now.toString());return now.toString();}}
spring:cloud:gateway:routes:- id: 3uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/predicate/between- Between=2024-10-08T09:04:31.397228500+08:00[Asia/Shanghai],2025-10-08T09:04:31.397228500+08:00[Asia/Shanghai]

CookieRoutePredicate

表示在请求中必须要写Cookie,并且Cookie的格式必须和配置文件中相同才行。在配置文件中,需要两个参数,一个是Cookie的名称,一个是Cookie的值,并且值可以使用正则表达式。

如下是对Cookie断言工厂的测试,表示发送的请求必须要携带Cookie,并且值为myCookie=test才可以路由到服务进行访问。

spring:cloud:gateway:routes:- id: 4uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/predicate/cookie- Cookie=myCookie, test
/*** 关于断言的测试*/@Slf4j
@RestController
@RequestMapping("/predicate")
public class PredicateController {@GetMapping("/cookie")public String cookie(HttpServletRequest request) {return request.getHeader("Cookie");}}

HeaderRoutePredicate

和Cookie断言工厂类似,Header断言也需要一个属性名和一个属性值才能进行路由,并且都要放在Header头中。

 如下是对Header断言工厂的测试,表示必须在请求头中加一个username的属性,并且值为wbz,才可以路由到服务进行访问。

spring:cloud:gateway:routes:- id: 5uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/predicate/header- Header=username, wbz
@Slf4j
@RestController
@RequestMapping("/predicate")
public class PredicateController {@GetMapping("/header")public String header(HttpServletRequest request) {return request.getHeader("username");}}

HostRoutePredicate

@Slf4j
@RestController
@RequestMapping("/predicate")
public class PredicateController {@GetMapping("/host")public String host(HttpServletRequest request) {return request.getHeader("host");}}
spring:cloud:gateway:routes:- id: 6uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/predicate/host- Host=com.wbz.java

MethodRoutePredicate

方法断言工厂表示对请求的方法类型进行限制。

如下是对Method断言工厂的测试,当请求类型为POST或者GET时,可以进行路由,反之其他请求类型不可用。

@Slf4j
@RestController
@RequestMapping("/predicate")
public class PredicateController {@RequestMapping("/method")public String method(HttpServletRequest request) {return request.getMethod();}}
spring:cloud:gateway:routes:- id: 7uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/predicate/method- Method=GET,POST

PathRoutePredicate

Path断言工厂前面一直在用,表示请求满足一定格式才能进行路由。

如下是对Path断言工厂的测试,表示当请求路径为/predicate/path时,才能进行路由。

spring:cloud:gateway:routes:- id: 8uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/predicate/path
@Slf4j
@RestController
@RequestMapping("/predicate")
public class PredicateController {@GetMapping("/path")public String path(HttpServletRequest request) {return request.getRequestURI();}}

QueryRoutePredicate

Query路由断言工厂表示在请求中存在该参数以及对应值才能进行路由,参数是确定的,对应值可以使用正则表达式。

如下是Query断言工厂的测试,表示必须要有参数password,并且密码是wbz才能进行路由。

spring:cloud:gateway:routes:- id: 9uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/predicate/query- Query=password, wbz
@Slf4j
@RestController
@RequestMapping("/predicate")
public class PredicateController {@GetMapping("/query")public String query(@RequestParam String password) {return password;}}

自定义断言

如果想天马行空就就能自定义断言出来,这显然是不现实的,但是我们最大的技能就是模仿。所以我们可以打开任意一个常用断言模仿来做。

1. 自定义断言类,该类需要以RoutePredicateFactory结尾,前面部分自定义。

2. 继承AbstractRoutePredicateFactory抽象类或者RoutePredicateFactory接口。

3. 实现一个静态内部类,类中的内容就是我们要写在配置文件中的内容。

4. 重写apply方法,当配置文件中写了断言之后,就是依靠该方法来判断是否进行路由。

5. 重写构造方法并写入super。

自定义断言工厂的名称是My,然后当请求参数中含有type时,并且对应的值为配置文件中对应的值时,就可以进行路由。

@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {public MyRoutePredicateFactory() {super(MyRoutePredicateFactory.Config.class);}@Overridepublic Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) {return new Predicate<ServerWebExchange>() {@Overridepublic boolean test(ServerWebExchange serverWebExchange) {String type = serverWebExchange.getRequest().getQueryParams().getFirst("type");if(type == null) {return false;}if(type.equals(config.getType())) {return true;}return false;}};}@Validatedpublic static class Config {private String type;public String getType() {return type;}public void setType(String type) {this.type = type;}}}

注意,该类写好之后不能使用简单方法写配置文件,所以我们需要再加入一个方法:

@Overridepublic List<String> shortcutFieldOrder() {return Collections.singletonList("type");}

 测试代码:

如下My断言工厂的测试代码,表示当请求中带有参数名为type,值为admin时,就能进行路由。

spring:cloud:gateway:routes:- id: 10uri: lb://cloud-consumer-order-open-feign-84predicates:- Path=/predicate/my- name: Myargs:type: admin
@Slf4j
@RestController
@RequestMapping("/predicate")
public class PredicateController {@GetMapping("/my")public String my(@RequestParam String type) {return type;}}

 经过自定义断言的书写之后,让我对前面的知识又有了一定的理解。也就是配置文件的短格式是上述写的没错,但是长格式呢?通过阅读源码,我发现了配置文件中对应的key值只要和源码中的对应相同才能配置成功,否则就会报错。

例如Query的完整格式如何配置,我们只需要看源码就能得出。如下图,Query需要如下两个参数,所以我们只需要在args中把key配置成源码想要的,value配置成我们想要的即可。


http://www.ppmy.cn/server/131799.html

相关文章

计算机网络(五)—— 运输层

1. 运输层概述 1.1 课后练习 2. 运输层端口、复用与分用的概念 2.1 课后练习 3. UDP和TCP的对比 3.1 总结 3.2 课后练习 1. 运输层概述 ■ 之前的计算机网络体系结构中的物理层、数据链路层以及网络层它们共同解决了将主机通过异构网…

使用 Docker 部署前端项目:Vue 和 React 结合 Nginx 实现静态文件托管

使用 Docker 部署前端项目&#xff1a;Vue 和 React 结合 Nginx 实现静态文件托管 Web 开发中&#xff0c;将前端项目&#xff08;例如 Vue 或 React 应用&#xff09;打包后通过 Docker 容器和 Nginx 部署是非常常见的方式。它不仅简化了部署流程&#xff0c;还能确保在不同环…

深度学习 size 属性

使用示例 import mxnet as mx# 创建一个 2D 数组 arr mx.nd.array([[1, 2, 3], [4, 5, 6]]) print(arr.size) # 输出: 6&#xff0c;因为数组中有 6 个元素# 创建一个 3D 数组 arr3d mx.nd.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) print(arr3d.size) # 输出: 8&…

JAVA分组GroupBy后原有的顺序会打乱,如何按原顺序进行分组

实体类Test public class Test{private String id;private String xh; }按xh排序后的testlist&#xff0c; 直接进行分组的方法&#xff1a; Map<String,List<Test>> groupbyGradeList testlist.stream().collect(Collectors.groupingBy(Test::getId)); 这样按id…

【回顾原生JDBC手动管理事务以及两种方式实现Spring编程式事务】

文章目录 一.关于事务1.事务概念2.事务四个基本特性3. 事务的生命周期4.事务的隔离级别5.事务的应用场景 二.回顾原生JDBC手动管理事务三.Spring编程式事务1.使用 TransactionTemplate 进行编程式事务管理2.使用 PlatformTransactionManager 进行编程式事务管理 四.编程式事务的…

【C++网络编程】(一)Linux平台下TCP客户/服务端程序

文章目录 Linux平台下TCP客户/服务端程序服务端客户端相关头文件介绍 Linux平台下TCP客户/服务端程序 图片来源&#xff1a;https://subingwen.cn/linux/socket/ 下面实现一个Linux平台下TCP客户/服务端程序&#xff1a;客户端向服务器发送&#xff1a;“你好&#xff0c;服务…

你用过最好用的AI工具有哪些?探寻用户心中的最爱与最佳

随着人工智能技术的飞速发展&#xff0c;AI 工具如雨后春笋般涌现&#xff0c;广泛应用于各个领域。在 10 月 8 日至 10 月 27 日这段时间里&#xff0c;我们深入探讨了人们在使用 AI 工具时的偏好和体验&#xff0c;旨在揭示那些最受用户喜爱以及被认为最好用的 AI 工具&#…

无人机之视觉技术篇

一、视觉传感器的类型 摄像头&#xff1a; 最常见的视觉传感器&#xff0c;能够捕捉可见光图像和视频。 通过单目、双目或多目摄像头的组合&#xff0c;无人机能够实现立体视觉&#xff0c;从而估算距离、深度&#xff0c;并进行物体识别和追踪。 红外传感器&#xff1a; …