(一).网关介绍
1.前置问题
当前,我们的生产是介绍openfeign的环境。


上图分别是product-service和order-service中的方法。这就有问题,当前所有微服务的接口都是直接对外暴露的,可以直接通过外部访问。为了保证对外服务的安全性,服务端实现的微服务接口通常都带有一定的权限校验机制。由于使用了微服务,原本一个应用的多个模块拆分成了多个应用,我们不得不实现多次校验逻辑。当这套逻辑需要修改时,我们需要修改多个应用,所以会很麻烦。
针对于上述问题,一个常用的解决方案就是使用API网关。
2.什么是API网关
API网关(简称网关)也是一个服务,通常是后端服务的唯一入口。它的定义类似设计模式中的门面模式。它就类似整个微服务架构的门面,所有的外部客户端访问,都需要经过它来进行调度和过滤。

网关的核心功能:
(1).权限控制:作为微服务的入口,对用户进行权限校验,如果校验失败则进行拦截
(2).动态路由:一切请求先经过网关,但是网关不处理业务,而是根据狗中规则,把请求转发到某个微服务
(3).负载均衡:当路由的目标服务有多个时,还需要做负载均衡
**(4).限流:**请求两两过高时,按照网管中配置微服务能够接收的流量进行放行,避免服务压力过大
3.常见网关的实现
(1).Zuul
Zuul是Netflix公司开源的一个API网关组件,是Spring Cloud Netflix子项目的核心组件之一,它可以和Eureka,Ribbon,Hystrix等组件配合使用
(2).Spring Cloud Gateway
Spring Cloud Gateway 是 Spring Cloud 的一个全新的API网关项目,基于Spring + SpringBoot等技术开发,目的是为了替换掉Zuul。
(二).Spring Cloud Gateway
1.使用Gateway
(1).创建网关项目
API网关也是一个服务,所以需要创建一个网关项目

(2).引入网关相关依赖
XML
<dependencies>
<!--⽹关-->
<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>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
(3).写启动类

(4).配置文件
在application.yml文件中,添加配置
XML
server:
port: 5050
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: #服务器nacos地址
namespace: 68576334-aeee-446f-a704-c59fd9207ecf #设置命名空间 服务管理的命名空间,并不是配置管理的命名空间
gateway:
routes: #网关路由相关配置
- id: product-service #路由规则id,随便起,不重复即可
uri: lb://product-service/** #目标服务地址,lb表示"负载均衡"
predicates: #路由条件,当满足条件的时候,网关应用会把请求跳转到目标服务地址
- Path=/product/** #把符合Path规则的一切请求,都代理到uri参数指定的地址
- id: order-service
uri: lb://order-service/**
predicates:
- Path=/order/**
(5).运行测试

可以发现,访问的时候,使用的是gateway的端口号,是可以访问成功的
2.Route Predicate Factories
(1).Predicate
Ⅰ.概念

Pridicate是Java 8 提供的一个函数式编程接口,它接收一个参数并返回一个布尔值,用于条件过滤,请求参数的校验

下面通过一个例子来看
①.定义一个Predicate

②.使用这个Predicate

Ⅱ.Predicate的其他写法
A.内置函数(内部类)

B.lambda写法

Ⅲ.Predicate的其他方法
|----------------------------------------------------------|-----------------------------------------------------------|
| 方法 | 说明 |
| boolean test (T t) | 判断条件,可以理解为 条件A 根据逻辑返回布尔值 |
| Predicate<T> and (Predicate <? super T> other) | 条件A && 条件B 当前Predicate的test && other的test方法,相当于进行两次判断 |
| default Predicate<T> negate() | !条件A 对当前判断进行 "!"操作,即取非操作 |
| default Predicate<?> or(Predicate <? super T> other) | 条件A || 条件B 当前Predicate的test方法 || other 的test方法 |
下面进行具体的演示



(2).Route Predicate Factories
Route Predicate Factories,路由断言工厂,也称为路由谓词(函数)工厂,在Spring Cloud Gateway中,Predicate提供了路由规则的匹配机制
我们在配置文件中写的断言规则只是字符串,这些字符串会被Route Predicate Factory读取并操作,转变为路由判断条件。

例如,前面写的Path,就是通过Path属性来匹配URL前缀是 /product的请求
这个规则是由 PathRoutePredicateFactory 这个类实现的

Spring Cloud Gateway 默认提供了很多 Route Predicate Factory,这些Predicate 会分别匹配HTTP请求的不同属性,并且多个Predicate可以通过and逻辑进行组合
|------------|--------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
| 名称 | 说明 | 示例 |
| After | 这个工厂需要一个日期时间(ZonedFateTime对象), 匹配指定日期之后的请求 | predicates: - After=2017-01-20T17:42:47.789-07:00America/Denver |
| Before | 匹配指定日期之前的请求 | predicates: - Before=2017-01-20T17:42:47.789-07:00America/Denver |
| Between | 匹配两个指定时间之间的请求,datetime2的参数必须在datetime1之后 | predicates: - Between=2017-01-20T17:42:47.789-07:00America/Denver , 2017-02-20T17:42:47.789-07:00America/Denver |
| Cookie | 请求中包含指定Cookie,且该Cookie值符合指定的正则表达式 | predicates: - Cookie=chocolate , ch.p |
| Header | 请求中包含指定Header,且该Header值符合指定的正则表达式 | predicates: - Header=X-Request-id , \d+ |
| Host | 请求必须是访问某个host(根据请求中的Host字段进行匹配) | predicates: - Host=**.somehost.org,**.anotherhost.or g |
| Method | 匹配指定的请求方式 | predicates: - Method=GET,POST |
| Path | 匹配指定规则的路径 | predicates: - Path=/order/** |
| RemoteAddr | 请求者的IP必须为指定范围 | predicates: - RemoteAddr=192.168.1.1*24 |
下面通过一个示例来演示


可以看到,是可以访问成功的


可以看到,是访问不到的
3.Gateway Filter Factories
Gateway Filter Factories,网关过滤工厂。Predicate决定了请求由哪一个路由处理,如果在请求前后需要加一些逻辑,这个就是Filter(过滤器)的作用范围了。
Filter 分为两种类型:Pre类型和Post类型
Pre类型过滤器:路由处理之前执行(请求转发到后端服务之前执行),在Pre类型过滤器中可以做鉴权,限流等等。
Post类型过滤器:请求执行完成后,将结果返回给客户端之前执行

Spring Cloud Gateway中内置了很多Filter,用于拦截和链式处理web请求,比如权限校验,访问超时等设定。
Spring Cloud Gateway从作用范围上,把Filter分为GatewayFilter 和GlobalFilter
GatewayFilter:应用到单个路由或者一个分组的路由上
GlobalFilter:应用到所有的路由上,也就是对所有的请求生效
(1).GatewayFilter
A.概念
GatewayFilter和Predicate类型,都是在配置文件application.yml中配置,每个过滤器的逻辑都是固定的。
例如,AddRequestParameterGatewayFilterFactory 只需要在配置文件中写 AddRequestParameter,就可以为所有的请求添加一个参数。
B.使用
下面,通过一个例子来看
Ⅰ.在application.yml中添加filter

这个filter只添加在了order-service路由下,因此支队order-service路由生效,也就是对 /product/**的请求生效。
Ⅱ.接收参数并打印
在order-service服务中接收请求的参数,并打印出来

C.GatewayFilter说明
Spring Cloud Gateway提供的Filter非常多,下面列出一些常见过滤器的说明
①.AddRequestHeader,为当前请求添加Header
- AddRequestHeader=X-Request-red, blue
参数: Header的名称及值
②.AddRequestParam,为当前请求添加请求参数- AddRequestParameter=red, blue
参数: 参数的名称及值
③.AddResponseHeader,为响应结果添加Header- AddResponseHeader=X-Response-Red, Blue
参数: Header的名称及值
④.RemoveRequestHeader,从当前请求删除某个Header- RemoveRequestHeader=X-Request-Foo
参数: Header的名称
⑤.RemoveResponseHeader,从响应结果中删除某个Header- RemoveResponseHeader=X-Response-Foo
参数: Header的名称
⑥.RequestRateLimiter,为当前网关的所有请求执行限流过滤,如果被限流,默认会相应HTTP -429-TooManyRequests。默认提供了RedisRateLimiter的限流实现,采用令牌桶算法实现限流功能。
filters:- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1redis-rate-limiter.replenishRate指的是令牌填充速度,即每秒周允许多少个请求(不丢弃任何请求)
redis-rate-limiter.burstCapacity指的是令牌桶容量,即每秒钟用户最大能够执行的请求数量(不丢弃任何请求),如果将此值设置为0,则将会阻止所有请求。
redis-rate-limiter.requestedTokens指的是每次请求占用几个令牌,默认为1
关于限流,使用的限流算法进行的实现,具体有四种限流算法
假设,现在的流量限制为1000次/分钟
a.固定窗口
但是,这种算法也有明显的缺点
如果说在59秒~60秒之间突然来了1000个请求,然后60~61秒之间突然又来了1000个请求,此时相当于2秒钟来了2000个请求,但是流量限制为1000次/分钟,此时就可能会把服务器搞挂掉
b.滑动窗口
可以看到,滑动窗口可以解决"固定窗口"存在的问题。但是滑动窗口并不能从根本上解决,如果使用滑动窗口,那么我们需要设置滑动窗口滑动的间隔
c.漏桶算法
但是,这种算法,也会出现"固定窗口"的问题,如果一下子来了1000个请求,此时全部会被放在桶里,但是消费者还是按照固定的速率进行处理
d.令牌桶算法
现在,如果在来请求之前,桶里已经放了1000个令牌,那么当这1000个请求突然来的时候,每个请求都拿着一个令牌,然后处理,不会出现应激流量处理不好的问题
⑦.Retry,针对不同的响应进行重试,当后端服务不可用时,网关会根据配置参数来发起重试请求
filters:
- name: Retry
args:
retries: 3
statuses: BAD_REQUESTretries:重试次数,默认为3
status:HTTP请求返回的状态码,针对指定状态码进行重试,对应org.springframework.http.HttpStatus
⑧.RequestSize,设置允许接收最大请求包的大小,如果请求包大小超过设置的值,则返回413 Payload TooLarge。请求包大小,单位为字节,默认为5M
filters:
- name: RequestSize
args:
maxSize: 5000000
⑨.默认过滤器
添加一个fliter并将其应用于所有路由,这个属性需要一个filter的列表。



可以看到,在上面的图片中,我就是将gateway中配置了默认过滤器,然后当状态为BAD_REQUEST,对应的状态码为400,的时候,就要重试3次。然后我在getDetail()方法中,将状态码改成了400,然后运行程序,进行访问,发现,确实重试了3次。
(2).GlobalFilter
A.概念
GlobalFilter是Spring Cloud Gateway中的全局过滤器,它和GatewayFilter的作用域是相同的。GlobalFilter会应用到所有的路由请求上,全局过滤器通常用于实现与安全性,性能监控和日志操作等相关的全局功能。
Spring Cloud Gateway内置的全局过滤器也有很多,例如
Gateway Metrics Filter:网关指标,提供监控指标
Forward Routing Filter:用于本地forward,请求不转发到下游服务器
LoadBalancer Client Filter:针对下游服务,实现负载均衡
B.使用
①.添加依赖
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
②.添加配置


③.运行测试

可以看到,所有的监控信息链接都显示出来了

4.过滤器执行顺序
如果一个项目中,既有GatewayFilter,又有GlobalFilter,那么当请求路由后,网关会把当前项目中的GatewayFilter和GlobalFilter合并到一个过滤器链(集合)中,并进行排序,依次执行过滤器

对于每一个过滤器,都必须要指定一个int类型的order值,默认为0,表示该过滤的优先级,order值越小,优先级越高,执行顺序越靠前。
Filter通过实现Order接口或者添加@Order注解来指定order值
Spring Cloud Gateway提供的Filter由Spring指定,用户也可以自定义Filter,由用户指定
当过滤器的order值一样的时候,会按照 defaultFilter > GatewayFilter > GlobalFilter
5.自定义过滤器
(1).自定义GatewayFilter
自定义GatewayFilter,需要去实现对应的接口GatewayFilterFactory,SpringBoot默认帮我们实现的抽象类是AbstractGatewayFilterFactory,我们可以直接使用
Ⅰ.定义GatewayFilter
java
package org.com.spring;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomConfig> implements Ordered {
public CustomGatewayFilterFactory() {
super(CustomConfig.class);
}
@Override
public GatewayFilter apply(CustomConfig config) {
return new GatewayFilter() {
/**
* ServerWebExchange HTTP请求-响应交互契约,提供了对HTTP 请求 和 响应 的比例
*
* GatewayFilterChain 过滤器链
*
* Mono Reactor的核心类,数据流发布者,Mono最多只能触发一个事件,可以把Mono用在异步完成任务时,发出通知
*
* chain.filter(exchange) 执行请求
*
* Mono.fromRunnable() 创建一个包含Runnable元素的数据流
*
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//执行步骤 : Pre类型 -> 执行请求 -> Post请求
log.info("Pre Filter ,config : {}",config);
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("Post Filter....");
}));
}
};
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; //设置较低的优先级
}
}
针对这个Filter的配置,使用CustomConfig定义
java
package org.com.spring;
import lombok.Data;
@Data
public class CustomConfig {
//和yml的配置对应
private String name;
}
注意:
①.类名统一以GatewayFilterFactory结尾 ,因为在默认情况下,过滤器的name会采用该定义类的前缀,这里的name=Custom(yml配置中使用)
②.apply方法中,同时包含了Pre和Post过滤,then方法中是请求执行结束之后处理的
③.CustomConfig是一个配置类,该类只有一个属性name,和yml的配置对应
④.该类需要交给Spring管理,所以需要添加@Component
⑤.getOrder表示该过滤器的优先级,值越大,优先级越低
Ⅱ.配置过滤器

Ⅲ.运行测试


可以看到,自定义的GatewayFilter起作用了
(2).自定义GlobalFilter
GlobalFilter的实现比较简单,不需要额外的配置,只需要实现GlobalFilter接口,自动会过滤所有的Filter
java
package org.com.spring;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("Pre Global Filter");
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("Post Global Filter");
}));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}

可以看到,当GatewayFilter和GlobalFilter过滤器order一样时,会先执行GatewayFilter






