

Zuul 1.0 : Netflix开源的网关,使用Java开发,基于Servlet架构构建,便于二次开发。因为基于Servlet内部延迟严重,并发场景不友好,一个线程只能处理一次连接请求。
Zuul 2.0 : 采用Netty实现异步非阻塞编程模型,一个CPU一个线程,能够处理所有的请求和响应,请求响应的生命周期通过事件和回调进行处理,减少线程数量,开销较小

GateWay : 是Spring Cloud的一个全新的API网关项目,替换Zuul开发的网关服务,基于Spring5.0 + SpringBoot2.0 + WebFlux(基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)等技术开发,性能高于Zuul
Nginx+lua : 性能要比上面的强很多,使用Nginx的反向代码和负载均衡实现对API服务器的负载均衡以及高可用,lua作为一款脚本语言,可以编写一些简单的逻辑,但是无法嵌入到微服务架构中
Kong : 基于OpenResty(Nginx + Lua模块)编写的高可用、易扩展的,性能高效且稳定,支持多个可用插件(限流、鉴权)等,开箱即可用,只支持HTTP协议,且二次开发扩展难,缺乏更易用的管理和配置方式
基本概念
(1)路由(Route)是GateWay中最基本的组件之一,表示一个具体的路由信息载体,主要由下面几个部分组成:
1) id:路由唯一标识,区别于其他的route
2) url: 路由指向的目的地URL,客户端请求最终被转发到的微服务
3) order: 用于多个Route之间的排序,数值越小越靠前,匹配优先级越高
4) predicate:断言的作用是进行条件判断,只有断言为true,才执行路由
5) filter: 过滤器用于修改请求和响应信息

核心概念:
Gateway Client 向 Spring Cloud Gateway 发送请求HttpWebHandlerAdapter 进行提取组装成网关上下文DispatcherHandler ,它负责将请求分发给 RoutePredicateHandlerMappingRoutePredicateHandlerMapping 负责路由查找,并根据路由断言判断路由是否可用FilteringWebHandler 创建过滤器链并调用Fliter 链运行请求,Filter 被虚线分隔的原因是Filter可以在发送代理请求之前(pre)和之后(post)运行逻辑Response 返回到 Gateway 客户端Filter过滤器:
当用户发出请求达到 GateWay 之后,会通过一些匹配条件,定位到真正的服务节点,并且在这个转发过程前后,进行一些细粒度的控制,其中 Predicate(断言) 是我们的匹配条件,Filter 是一个拦截器,有了这两点,再加上URL,就可以实现一个具体的路由,核心思想:路由转发+执行过滤器链
这个过程就好比考试,我们考试首先要找到对应的考场,我们需要知道考场的地址和名称(id和url),然后我们进入考场之前会有考官查看我们的准考证是否匹配(断言),如果匹配才会进入考场,我们进入考场之后,(路由之前)会进行身份的登记和考试的科目,填写考试信息,当我们考试完成之后(路由之后)会进行签字交卷,走出考场,这个就类似我们的过滤器

Route(路由) :构建网关的基础模块,由ID、目标URL、过滤器等组成
Predicate(断言) :开发人员可以匹配HTTP请求中的内容(请求头和请求参数),如果请求断言匹配贼进行路由
Filter(过滤) :GateWayFilter的实例,使用过滤器,可以在请求被路由之前或者之后对请求进行修改
micro-service-cloud-gateway-9527

注意版本依赖关系

父类pom引用:
- <spring-cloud-gateway-varsion>Hoxton.SR5</spring-cloud-gateway-varsion>
-
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>${spring-cloud-gateway-varsion}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
子类POM引用:
- <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>
- <version>2.2.5.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </dependency>
- </dependencies>
yml配置
(1)nacos 配置
- server:
- port: 9527
- spring:
- application:
- name: cloud-gateway-service
- cloud:
- nacos:
- discovery:
- server-addr: localhost:8848
- gateway:
- discovery:
- locator:
- enabled: false #开启注册中心路由功能
- routes: # 路由
- - id: nacos-provider #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
- uri: http://localhost:9001/nacos-provider # 匹配提供服务的路由地址 lb://表示开启负载均衡
- predicates: # 断言
- - Path=/mxn/** # 断言,路径相匹配进行路由
(2) eureka注册中心 配置(本案例使用)
- server:
- port: 9527 #端口号
- spring:
- application:
- name: microServiceCloudGateway
- cloud:
- gateway: #网关路由配置
- discovery:
- locator:
- enabled: true
- routes:
- #将 micro-service-cloud-provider-dept-8001 提供的服务隐藏起来,不暴露给客户端,只给客户端暴露 API 网关的地址 9527
- - id: provider_dept_list_routh #路由 id,没有固定规则,但唯一,建议与服务名对应
- #uri: http://localhost:8001 #匹配后提供服务的路由地址
- uri: lb://CLOUD-PAYMENT-SERVICE
- predicates:
- #以下是断言条件,必选全部符合条件
- - Path=/payment/lb/** #断言,路径匹配 注意:Path 中 P 为大写
- - Method=GET #只能时 GET 请求时,才能访问
- eureka:
- instance:
- instance-id: micro-service-cloud-gateway-9527
- hostname: micro-service-cloud-gateway
- client:
- fetch-registry: true
- register-with-eureka: true
- service-url:
- defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

在上述方法中我们是通过YML去完成的配置,GateWay还提供了另外一种配置方式,就是通过代码的方式进行配置,@Bean 注入一个 RouteLocator

- public class GatewayConfig {
-
- /*
- 配置了一个id为path_mxn的路由规则
- 当访问地址http://localhost:9999/mxn/**
- 就会转发到http://localhost:9001/nacos-provider/mxn/任何地址
- */
- @Bean
- public RouteLocator gateWayConfigInfo(RouteLocatorBuilder routeLocatorBuilder){
- // 构建多个路由routes
- RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
- // 具体路由地址
- routes.route("path_mxn",
- // r -> r.path("/payment/get/**").uri("http://localhost:8001/")).build();
- r -> r.path("/payment/get/**").uri("lb://CLOUD-PAYMENT-SERVICE")).build();
- // 返回所有路由规则
- return routes.build();
- }
- }

注意:GateWay负载均衡
在上述的讲解中,我们已经掌握了 GateWay 的一些基本配置和两种使用方式, GateWay 如何实现负载均衡 我们只需要在gateway 中uri修改为: lb://CLOUD-PAYMENT-SERVICE就可以显示负载均衡。
在这一篇中我们来研究一下 断言
我们可以理解为:当满足条件后才会进行转发路由,如果是多个,那么多个条件需要同时满足
在官方提供的断言种类有11种(最新的有12种类型):
测试一个:After

如果在时间段之前访问则404

Before
匹配ZonedDateTime类型的时间,表示匹配在指定日期时间之前的请求,之后的请求则拒绝404错误
- predicates: # 断言
- - Path=/mxn/** # 断言,路径相匹配进行路由
- # - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
- - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
Between
Between 可以匹配ZonedDateTime类型的时间,由两个ZonedDateTime参数组成,第一个参数为开始时间,第二参数为结束时间,逗号进行分隔,匹配在指定的开始时间与结束时间之内的请求,配置如下:
- predicates: # 断言
- - Path=/mxn/** # 断言,路径相匹配进行路由
- # - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
- # - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
- - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
Cookie
由两个参数组成,分别为name(Key)和regexp(正则表达式)(Value),匹配具有给定名称且其值与正则表达式匹配的Cookie。
路由规则会通过获取Cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果匹配不上则不执行。
- predicates: # 断言
- - Path=/mxn/** # 断言,路径相匹配进行路由
- # - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
- # - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
- # - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
- - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
Header
由两个参数组成,第一个参数为Header名称,第二参数为Header的Value值,指定名称的其值和正则表达式相匹配的Header的请求
- predicates: # 断言
- - Path=/mxn/** # 断言,路径相匹配进行路由
- # - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
- # - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
- # - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
- # - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
- - Header=headerName, \d+ # \d表示数字
Host
匹配当前请求是否来自于设置的主机。
- predicates: # 断言
- - Path=/mxn/** # 断言,路径相匹配进行路由
- # - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
- # - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
- # - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
- # - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
- # - Header=headerName, \d+ # \d表示数字
- - Host=**.muxiaonong.com #匹配当前的主机地址发出的请求
**Method **
可以设置一个或多个参数,匹配HTTP请求,比如POST,PUT,GET,DELETE
- predicates: # 断言
- - Path=/mxn/** # 断言,路径相匹配进行路由
- # - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
- # - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
- # - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
- # - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
- # - Header=headerName, \d+ # \d表示数字
- # - Host=**.muxiaonong.com #匹配当前的主机地址发出的请求
- - Method=POST,GET
Query
由两个参数组成,第一个为参数名称(必须),第二个为参数值(可选-正则表达式),匹配请求中是否包含第一个参数,如果有两个参数,则匹配请求中第一个参数的值是否符合第二个正则表达式。
- predicates: # 断言
- - Path=/mxn/** # 断言,路径相匹配进行路由
- # - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
- # - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
- # - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
- # - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
- # - Header=headerName, \d+ # \d表示数字
- # - Host=**.muxiaonong.com #匹配当前的主机地址发出的请求
- # - Method=POST,GET
- - Query=id,.+ # 匹配任意请求参数,这里如果需要匹配多个参数,可以写多个- Query=
RemoteAddr
参数由CIDR 表示法(IPv4 或 IPv6)字符串组成,也就是匹配的ID地址,配置如下:
- predicates: # 断言
- - Path=/mxn/** # 断言,路径相匹配进行路由
- # - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个时间之后的请求够可以进行通过,之前的则不能进行访问
- # - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
- # - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
- # - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)表示任意字母
- # - Header=headerName, \d+ # \d表示数字
- # - Host=**.muxiaonong.com #匹配当前的主机地址发出的请求
- # - Method=POST,GET
- # - Query=id,.+ # 匹配任意请求参数,这里如果需要匹配多个参数,可以写多个Query
- - RemoteAddr=192.168.1.1/24
需要两个参数group和weight(int)权重数值,实现了路由权重功能,表示将相同的请求根据权重跳转到不同的uri地址,要求group的名称必须一致
- routes: # 路由
- - id: weight_high #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
- uri: https://blog.csdn.net/qq_14996421
- predicates: # 断言
- - Weight=groupName,8
- - id: weight_low #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
- uri: https://juejin.cn/user/2700056290405815
- predicates: # 断言
- - Weight=groupName,2
路由过滤器允许修改传入的HTTP请求或者返回的HTTP响应,路由过滤器的范围是特定的路由.Spring Cloud GateWay 内置的Filter生命周期有两种:pre(业务逻辑之前)、post(业务逻辑之后)GateWay本身自带的Filter分为两种: GateWayFilter(单一)、GlobalFilter(全局)GateWay Filter提供了丰富的过滤器的使用,单一的有32种,全局的有9种,有兴趣的小伙伴可以了解一下。
官方参考网址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters
虽然Gateway给我们提供了丰富的内置Filter,但是实际项目中,自定义Filter的场景非常常见,因此单独介绍下自定义FIlter的使用。
想要实现GateWay自定义过滤器,那么我们需要实现GatewayFilter接口和Ordered接口
- @Slf4j
- @Component
- public class MyFilter implements Ordered, GlobalFilter {
- /**
- * @param exchange 可以拿到对应的request和response
- * @param chain 过滤器链
- * @return 是否放行
- */
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
- //获取第一个参数
- String id = exchange.getRequest().getQueryParams().getFirst("id");
- //打印当前时间
- log.info("MyFilter 当前请求时间为:"+new Date());
- //判断用户是否存在
- if(StringUtils.isEmpty(id)){
- log.info("用户名不存在,非法请求!");
- //如果username为空,返回状态码为407,需要代理身份验证
- exchange.getResponse().setStatusCode(HttpStatus.PROXY_AUTHENTICATION_REQUIRED);
- // 后置过滤器
- return exchange.getResponse().setComplete();
- }
- return chain.filter(exchange);
- }
-
- /**
- * 设定过滤器的优先级,值越小则优先级越高
- * @return
- */
- @Override
- public int getOrder() {
- return 0;
- }
- }
当我们访问http://localhost:9527/payment/get/2?i8d=1请求,没有携带ID参数,请求失败

当我们访问http://localhost:9527/payment/get/2?id=1请求,请求成功

到这里我们的GateWay就讲解完了,对于GateWay的核心点主要有三个Route\Predicate\Filter,我们搞懂了这三点,基本上对于GateWay的知识就掌握的差不多了,GateWay核心的流程就是:路由转发+执行过滤器链