Spring Cloud Gateway 网关的使用
在之前的学习中,所有的微服务接口都是对外开放的,这就意味着用户可以直接访问,为了保证对外服务的安全性,服务端实现的微服务接口都带有一定的权限校验机制,但是由于使用了微服务,就需要每一个服务都进行一个校验,当校验逻辑需要修改时,又得修改多个应用,增加了开发负担,一个解决方式就是引入 API 网关,类似整个微服务架构的门面,所有的外部客户端都需要经过它来进行调度和过滤,相当于前台,需要办理什么业务,经过前台的确认之后会引导用户去对应的服务台
作为网关,具有以下的几个核心功能:
- 权限控制:对用户进行权限校验,如果校验失败就进行拦截
- 动态路由:一切请求先经过网关,但网关不处理业务,而是根据某种规则,把请求发送到某个微服务
- 负载均衡:当路由的目标服务有多个时,进行负载均衡
- 限流:当请求流量过高时,按照网关中配置微服务能够接受的流量进行放行,避免服务压力过大
Gateway
1. Gateway 的使用
首先需要创建一个单独的 Gateway 的服务,除了引入 gateway 的依赖之外,还需要引入 nacos 和 loadbalancer 的依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<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>
然后就需要对网关进行配置:
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes: #网关路由配置
- id: order-service #自定义路由规则id
uri: lb://order-service/ #目标服务地址
predicates: #路由条件(满足条件才会被通行)
- Path=/order/**
来解释一下上面的配置:
由于需要用到 Nacos 的服务发现功能,所以要把 Nacos 的配置也添加上,之后就是网关的路由配置,自定义一个路由规则 id 作为唯一标识符,方便后续对该规则进行管理和引用,然后就是目标服务地址, lb 表示使用负载均衡器将请求转发到名为 order-service 的服务,并设置了路由条件,请求的路径必须以 /order/ 开头
配置好之后启动服务,此时就可以通过网关服务来获取资源了
关于多个服务和多个路由条件的配置:
routes: #网关路由配置
- id: order-service #自定义路由规则id
uri: lb://order-service/ #目标服务地址
predicates: #路由条件(满足条件才会被通行)
- Path=/order/**,/feign/**
- id: product-service
uri: lb://product-service
predicates:
- Path=/product/**
2. Route Predicate Factories
在上面的配置中,使用 predicates 来配置路由条件,其中写的规则只是字符串形式,这些字符串会被 Route Predicate Factory (路由断言工厂,也称为路由谓词工厂)读取并处理,转变为路由判断条件
https://docs.spring.io/spring-cloud-gateway/reference/spring-cloudgateway/request-predicates-factories.html
也就是这 12 种基本的实现:
来演示一下 After 的使用,如果不符合条件就会不能够访问
predicates: #路由条件(满足条件才会被通行)
- Path=/order/**,/feign/**
- After=2026-01-12T18:10:21.024727900+08:00[Asia/Shanghai]
3. Gateway Filter Factories
Predicate 是决定了请求由哪一个路由处理,Filter 过滤器是在请求处理前后做一些的逻辑的处理
Filter 又分为 Pre 和 Post 两种类型:
Pre 类型过滤器:路由处理之前执行,也就是请求转发给后端服务之前,例如对请求进行限流,添加额外的请求头信息等
Post 类型过滤器:请求执行完成后,将结果返回给客户端之前执行,例如修改响应体的主体内容,对响应路径进行重写等
Spring Cloud Gateway 中内置了很多的过滤器,用于拦截和处理 web 请求,根据作用范围可以分为 GatewayFilter (作用到单个路由或者一个分组的路由上),GlobalFilter(作用到所有的路由上)
来演示一下 AddRequestParameter 用法:
接下来再访问接口,即使 id 不传值也通过过滤器处理之后就被赋值了
除了 AddRequestParameter,观望中还提供了很多其它的过滤器
AddRequestHeader GatewayFilter Factory :: Spring Cloud Gateway
RequestRateLimiter 可以对通过网关的请求进行限流操作,采用的是令牌桶的算法
关于限流的算法有以下几种:
- 固定窗口:将时间划分为固定大小的窗口,每个窗口有一个计数器,用于记录在该窗口内允许通过的请求数量,每通过一个请求计数器就加一,当计数器达到设定的阈值就不允许请求通过,缺点也是非常明显,比如设定 10 分钟可以通过 10000 个请求,前 9 分钟都没有请求,最后 1 分钟处理 10000 个请求也符合要求,显然是不合理的
- 滑动窗口:同样将时间划分为多个小的时间窗口,但不是按固定的大窗口重置计数器,而是将窗口滑动,这也就解决了固定窗口出现的问题,不过需要记录多个小窗口的信息,性能开销比较大
- 漏桶算法:就像一个漏斗型的水桶,当请求到达时,会先进入漏桶,如果漏桶未满,则允许请求通过;如果漏桶已满,则拒绝请求,桶以恒定的速率出水,代表处理请求的速率,无论流入的速率如何,流出的速率始终保持一致,这也就导致了不能够处理突发的大流量
- 令牌桶算法:在漏桶的基础上,令牌桶中会以一定的速率生成令牌,当流量小的时候,桶中就会不断积累令牌,当遇到突发的大流量,就可以直接拿着这些令牌通过,剩余的就排队等待令牌的生成,排队等待这种处理方式,还可以结合其他策略,比如当排队队列过长时,为了避免资源过度占用,可能会对部分请求进行拒绝;或者根据业务的优先级,优先处理高优先级请求等。
再来看下一个过滤器:
Retry 就是根据当前返回的状态码来进行重试,可以设置状态码和重试次数
在上面的配置中都是使用 filter 进行配置的,如果使用 Default Filters 配置的话就是对全部路由生效
来使用 default-filters 配置一下 retry,然后把状态码设置为 502
之后就重新请求了 3 次
GlobalFilter 是全局过滤器,会应用到所有的路由请求上,全局过滤器通常用于实现安全性,性能监控和日志记录等相关的全局功能
关于负载均衡,在之前的使用中就已经用到了,也就是 GlobalFilter
下面看一下 Metrics 怎么使用,首先需要引入下面的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然后再开启 metrics 的配置
然后再添加一下配置,用来监控详细的信息,在 /actuator 下可以查到所有监控的链接和信息
过滤器的执行顺序:
当一个项目中,既有 GatewayFilter 又有 GlobalFilter 时,请求路由后,网关会把当前项目中的 GatewayFilter 和 GlobalFilter 合并到一个 过滤器链中,并进行排序,依次执行过滤器
每一个过滤器都必须指定一个 int 类型的 order 值,默认值为 0 ,来表示过滤器的优先级,order 值越小,优先级越高,执行顺序越靠前
Filter 通过实现 Order 接口或者添加@Order
注解来指定 order 值,Spring Cloud Gateway 提供的 Filter 由 Spring 指定,用户也可以自定义 Filter,如果过滤器的 order 值一样,会按照 defaultFilter > GatewayFilter > GlobalFilter 的顺序执行
4. 自定义过滤器
4.1. 自定义 GlobalFilter
全局过滤器需要实现 GlobalFilter,Ordered 接口,然后重写 filter 和 getOrder 方法
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//Pre执行逻辑
log.info("Pre Global Filter...");
return chain.filter(exchange).then(Mono.fromRunnable(()->{
//Post执行逻辑
log.info("Post Global Filter...");
}));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; //设置优先级
}
}
关于 Mono 方法参数的说明:
之后再去发起请求,自定义的全局过滤器已经生效了
4.2. 自定义 GatewayFilter
如果需要自定义可配置的 GatewayFilter,就需要创建一个过滤器工厂,根据读取到的配置来构造对象
定义一个类用来存储从配置文件中读取到的配置信息
@Data
public class CustomConfig {
private String name;
}
创建一个过滤器工厂 CustomGatewayFilterFactory 继承 AbstractGatewayFilterFactory 类,然后再实现 Ordered 接口,并添加过滤器的逻辑
@Slf4j
@Component
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomConfig> implements Ordered {
public CustomGatewayFilterFactory() {
super(CustomConfig.class);
}
@Override
public GatewayFilter apply(CustomConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//Pre类型
log.info("Pre Filter,config{}",config);
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("Post Filter,config{}",config);
}));
}
};
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
配置文件中把自定义的过滤器名称添加上,然后还需要添加传入的参数,这里过滤器的名称取的是 CustomGatewayFilterFactory 除去 GatewayFilterFactory,按照规范自定义过滤器工厂需要以 GatewayFilterFactory 为后缀
之后再启动服务,自定义的 GatewayFilter 也生效了,并且在指定优先级相同的条件下,先执行 GatewayFilter 再执行 GlobalFilter