梦入琼楼寒有月,行过石树冻无烟

Spring cloud Gateway


Gateway 是 Spring cloud 官方所推出的第二代网关(API Gateway)框架,在为服务系统中,网关的主要作用就是当用户调用的所有微服务,都要经过网关。

在为服务中,除于不同微服务可能会带来不同的开发语言和协议,因此需要通过网关来处理服务的调用

网关统一向外部系统提供 REST API,在 Spring cloud 中,使用 Zuul、Gateway 等作为网关可以实现出动态路由、监控、回退、安全功能等。

特点

Gateway 基于 Spring Framework 5、Project Reactor 、Spring boot 2.0 构建,可以匹配任何请求属性的路由,能编写谓词(Predicates)以及过滤器(Filters)等。

谓词主要是用于确定给定输入的 true 或 false,在网关中谓词和过滤器也可以用于特定的路由。

过滤器可以重写数据,但由于 Gateway 是异步的,如果需要对响应的 body 进行修改需要使用 writeWith() 所提供的 GlobalFilter、GatewayFilter 类型。

其中 GlobalFilter 是对所有路由有效的,而 GatewayFilter 类型仅对指定范围生效

除此之外 Gateway 还支持路径重写、动态路由和集成了 Hystrix 断路器以及 DiscoveryClient 的集成和限流。

工作流程

首先 HTTP/HTTPS 强求 Spring cloud Gateway,DispatcherHandler 接受请求,并通过 RoutePredicateHandlerMapping 进行路由匹配,如果网关路由与请求的路由进行匹配 则将请求发送到 FilteringWebHandler,如果不匹配,那么则将请求发送给 DispatcherHandler 进行处理。

最后 FilteringWebHandler 通过自定义的过滤器来发送请求,这会将请求转发到具体的服务中,最后处理结果给用户。

谓词接口和谓词工厂

谓词(Predicate),在 Java 8 中引入的一个函数式接口,主要用于接受输入参数并返回布尔值的结果,Spring cloud Gateway 通过 Predicate 接口来判断当前路由是否满足指定条件。

谓词工厂(Route Predicate Factories)主要作用就是当符合谓词条件就使用该路由进行匹配,否则就忽略。

Spring cloud Gateway 的路由规则是由 RouteDefinitionLocator 类进行管理,默认情况下使用 Spring Boot 的 @ConfigurationProperties 机制来加载属性。

实现

Java API


可以通过最简单的 Java API 的方式来构建路由,主要通过 @Bean 来实现一个自定义的 RouteLocator 类来实现 自定义路由转发规则。在此之前,需要通过添加 gateway 依赖来完成:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

由于 Spring cloud gateway 使用的是 Netty+WebFlux 实现的,因此不需要引入 Web 模块依赖。

之后在启动类添加自定义 RouteLocator 类即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

/**
* 将请求转发给 baidu.com
*
* @author kunlun
* @date 2021/7/8
*/
@SpringBootApplication
public class DemoApplication {

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

@Bean
public RouteLocator routeLocator (RouteLocatorBuilder builder) {
return builder.routes().
route("guonei", r -> r.path("/guonei/**")
.uri("http://news.baidu.com/")).build();
}

}

在上述的过程中,route 主要将与 guonei 路由想匹配的通过 Spring cloud Gateway 转发到了 http://news.baidu.com/guonei 中,因此访问 http://localhost:8081/guonei 就是访问 http://news.baidu.com/guonei

其中 r.path 参数下的 /guonei/** 指的是这将会匹配包含所有任何后缀携带 /guonei 的路径模式,还需要注意的是 Spring cloud Gateway 支持两种的配置,上面所实现的是基于RouteLocator 的实现,还有 properties 配置文件的实现

application.yml / application.proerties

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
cloud:
gateway:
routes:
- id: guonei
uri: http://news.baidu.com/
predicates:
- Path=/guonei
application:
name: GatewayForward

server:
port: 8081

与 Java API 一样,访问 http://localhost:8081/guonei 就是访问 http://news.baidu.com/guonei,同样的实现了路由的功能。

路由谓词工厂或路由规则(RoutePredicateFactory)

在 Gateway 中,主要通过 RoutePredicateFactory(谓词工厂)来创建谓词(Predicate),因此也提供了很多种谓词工厂,这也是在上面所提到的:“谓词工厂(Route Predicate Factories)主要作用就是当符合谓词条件就使用该路由进行匹配,否则就忽略。”

通过谓词工厂,使得开发人员可以简单配置即可获得很多需要和想要的路由规则,及谓词(Predicate),这些路由规则会根据 HTTP 请求进行不同属性来匹配,其中最为主要的为下述几类:

RoutePredicateFactory type info
AfterRoute……(省略 PredicateFactory) datetime 请求时间满足在配置时间之后
BeforeRoute datetime 请求时间满足在配置时间之前
BetweenRoute datetime 请求时间满足在配置时间之前
CookieRoute Cookie 请求指定了 Cookie 正则匹配的指定值
HeaderRoute Header(数据头) 请求指定了请求上下文所匹配的指定值
CloudFoundryRouteServiceRoute Header 请求 Headers 是否包含了指定的名称
MethodRoute Method(方法) 请求的方法是否匹配配置的方法
PathRoute Path(路径) 请求路径是否匹配指定值
Query Queryparam(查询参数) 请求查询的查询参数与配置文件的相匹配
RemoteAddreRoute Remoteaddr(远程地址) 远程地址是否匹配指定值
HostRoute Host(主机) 请求主机是否匹配指定值

After

AfterRoutePredicateFactory(After路由谓词工厂)是一个 datetime(日期时间) 类型的为此,他主要表达的是如果请求在配置文件中 时间在配置文件之后 发生的请求允许。

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
cloud:
gateway:
routes:
- id: after
uri: http://news.baidu.com/
predicates:
- After=2022-01-20T18:06:06+08:00[Asia/Shanghai]
application:
name: GatewayForward

server:
port: 8081

After 的核心意思就是只能在配置规定后的时间进行访问,但只能通过 UTC + 时区的方式来添加规则,否则将直接报错。After 的主要作用就是在 2021 年 1 月 20 日之后才可进行访问,否则将会直接报错,直接访问 http://localhost:8081/ 即可。

Before

Before 也是属于 datetime 类型之一,其核心含义是当请求满足配置时间之前,而之前的 After 路由谓词工厂只是满足配置时间之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
cloud:
gateway:
routes:
- id: after
uri: http://news.baidu.com/
predicates:
- Before=2022-01-20T18:06:06+08:00[Asia/Shanghai]
application:
name: GatewayForward

server:
port: 8081

Between

Between 可以理解为是两个 UTC 时间之间的请求,因此他有两个参数,分别是 datetime1 以及 datetime2 这是一个 ZonedDateTime 对线,因此请求需要满足 datetione1 之后且 datetime2 之前的请求:

1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
gateway:
routes:
- id: after
uri: http://news.baidu.com/
predicates:
- Between=2019-01-20T18:06:06+08:00[Asia/Shanghai],2022-01-20T18:06:06+08:00[Asia/Shanghai]
application:
name: GatewayForward

如上面的 Between 路由规则是,在 2019-01-20T18:06:06+08:00[Asia/Shanghai] 之后,2022-01-20T18:06:06+08:00[Asia/Shanghai] 之前的请求将会进行匹配,否则将会忽视。

CookieRoutePredicateFactory 主要用于匹配指定值,虽然很多作者说直接可以一条参数走到低:mycookie,mycookievalue,但很遗憾并没有作用,因此只能通过全展开的方式来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
cloud:
gateway:
routes:
- id: after
uri: http://news.baidu.com/
predicates:
- name: Cookie
args:
name: meteor_login_token
regexp: ipQdtc89kEO7-JaBWLdgwPHzX9oDy6l-2qXmZ_OeHye
application:
name: GatewayForward

server:
port: 8081

在上述的 cookie 中,主要指定了 cookie 名字 meteor_login_token,以及 cookie_value ipQdtc89kEO7-JaBWLdgwPHzX9oDy6l-2qXmZ_OeHye,因此如果请求的 cookie 与配置文件相匹配则通过,否则将会忽略。

Header 即请求上下文的头部信息,当请求与配置的 Header 一致时进行转发,在官方文档中被称之为:X-Request-Id 与正则表达式合在一起进行匹配,但并没有什么作用。因此我们可以理解为 Header 请求头是根据上下文头部信息来指定服务器的域名和服务器正在侦听的 TCP 端口号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
gateway:
routes:
- id: after
uri: http://news.baidu.com/
predicates:
- Header=Host,localhost:8081

application:
name: GatewayForward

server:
port: 8081

当请求的上下文头部信息与配置文件相互匹配则允许请求,否则将会直接进行忽略。

Host

Host 路由谓词工厂与 Header 类似,或者说两者都可以实现其效果,但 Host 路由谓词工厂很明显比 Hedaer 更加的简单且好理解。因此他主要的作用就是匹配所请求的主机是否和配置文件相互一致,否则将不具备转发功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
gateway:
routes:
- id: after
uri: http://news.baidu.com/
predicates:
- Host=localhost:8081

application:
name: GatewayForward

server:
port: 8081

在上述配置文件中,请求的主机是 localhost:8081 因此与配置文件的信息相互匹配,可以通过网关使用路由进行跳转。

Method


Method 路由谓词工厂相比上述几个 header 类型的谓词工厂显得特别实用,他主要用指定访问资源以响应预检请求时允许的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
gateway:
routes:
- id: after
uri: http://news.baidu.com/
predicates:
- Method=GET,POST

application:
name: GatewayForward

server:
port: 8081

就比如上述的配置文件,我们允许了 GET,POST 方法请求,但不允许 PUT 方法请求,因此使用该方法请求将会直接被网关所进行拦截。

Query


Query 路由谓词工厂主要是当 请求查询的查询参数与配置文件的相匹配,就比如 localhost:8081?name=111 正好与配置文件中的 query=namename 相互匹配,则允许请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
gateway:
routes:
- id: after
uri: http://news.baidu.com/
predicates:
- Query=name

application:
name: GatewayForward

server:
port: 8081

RemoteAddre

RemoteAddre 与 Header 路由谓词工厂类似,简单理解就是他们的进阶版,他可以根据 CIDR 表示法进行匹配,也就是说当你写入规则是 192.168.0.1/24 时,你 192.168.0.104/24 也可以请求此接口,但如果你不是这个网段下的,则会被拒绝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: http://news.baidu.com
predicates:
- RemoteAddr=192.168.0.1/24

application:
name: GatewayForward

server:
port: 8081

localhost 是 127.0.0.1 的地址映射,因此在 RemoteAddr 路由谓词工厂规则中他会被直接拒绝。

Weight


Weight 是权重路由谓词工厂,当 Gateway 中有多个路由网关配置时可以发挥作用,他主要分为 组(group) 以及 权重(weight) 进行组合:

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
gateway:
routes:
- id: weight_one
uri: http://news.baidu.com
predicates:
- Weight=group1, 1
- id: weight_two
uri: http://baidu.com
predicates:
- Weight=group1, 8

就比如上述的配置中,weight_two 的权重比 weight_one 高,因此网关匹配的是 weight_two 的路由。

Gateway consul 服务转发


在正常的微服务架构中,都会选择依赖服务中心来进行注册,这通常需要对每个服务提供者进行单独的配置,Gateway 的出现爱你提供了默认转发的工作,当网关注册到服务中心后,网关则会代替服务中心来提供转发的服务

1
2
3
4
5
6
7
8
9
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

application.yml

application.yml 配置文件中,需要开启服务转发的选项,即 spring.cloud.gateway.discovery.locator.enabled 即可,之后在启动类中添加 @EnableDiscoveryClient 注解以此来实现服务发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
application:
name: Gateway Consul
cloud:
consul:
host: localhost
port: 8500
gateway:
discovery:
locator:
enabled: true
server:
port: 8515

过滤器(Filter)

过滤器的存在

过滤器之所以在网关中存在,是因为在微服务系统中,服务提供者非常的多,因此如果在每个服务中都有诸如鉴权、限流以及日志输出等则会占用服务的消耗(也就是说每个服务我都要写一遍这些功能,则会让开发很烦躁)因此可以直接通过网关来进行处理。

网关应可以处理鉴权、限流等工作

PRE and POST

PRE

根据请求的生命周期,在 Gateway 中,过滤器会分为 PRE、POST 两种,其中 PRE 代表 在路由执行之前的请求 执行该过滤,因此主要应用在参数校验、鉴权、流量监控、日志输出、协议转换功能。

POST

POST 主要是在 请求被路由跳到微服务之后所执行 的过滤器,因此这种过滤器可以实现出相应头(HTTP Header)以及收集统计信息和指标、响应转发、日志输出、流量监控等功能。

GatewayFilter and GlobalFilter

GatewayFilter (网关过滤器)

过滤器分为两类,其中网关过滤器(GatewayFilter)只会在 单个路由或者分组路由中。网关过滤器是一种 可以修改请求传入的 HTTP 请求或输出 HTTP 相应 的特定路由使用,Gateway 内置了 20 多种网关过滤器工厂来编写网关过滤器:

Id Name type Info
1 AddRequestHeader Request 用于在请求头中添加自定义的键值对
2 AddRequestParameter Request 用于在请求参数中添加请求参数的键值对
3 AddResponseHeader Response 用于在相应头中添加
4 Hystrix 断路器 用于将断路器引入网关路由中,可以让服务免受级联故障影响,并在故障时提供回退响应(使用时需要使用名为 HystrixCommand 的参数)
5 PrefixPath Path 用于使用简单的 Prefix
6 PreserveHostHeader Header 设置路由过滤器的请求属性,检查是否发送原主机头或由 HTTP 客户端确定主机头
7 RequestRateLimiter Request 用于确定当前请求是否继续(如果不允许则会返回 HTTP 429 - Too Many Request
8 RedirectTo Request 用于接受请求的状态和 URL 参数(该状态是一个重定向的 300 系列 HTTP 代码,类似与 301。但 URL 是 Location 头部的值)
9 RemoveNonProxyHeaders Headers 从转发的请求中删除请求头
10 RemoveRequestHeader Request 通过请求头名删除请求头
11 RemoveResponseHeader Response 通过响应头名删除响应头
12 RewritePath Path 通过 Java 正则表达式重写请求路径
13 SaveSession Session 在转发下游调用之前,强制执行保存 Session 的操作
14 SecureHeaders Headers 为响应头添加安全头
15 SetPath Path 该方法允许通过路径的模板段来操作请求路径,使用 Spring 的 URI 模板,支持多种匹配
16 SetResponseHeader Response 需要通过 Key-Value 对来设置响应头
17 SetStatus Response 用于设置请求响应状态,但需要 Status 参数(需要有效的 Spring HttpStatus,例如整形的 404 或枚举类型字符串 NOT_FOUND
18 StripPrefix Request 用于剥离前缀(需要 parts 参数,用于表明请求被发送到下游之前从请求路径中所剥离的元素数量)
19 Retry Request 主要用于重试,但需要 Retries、Statuses、Methods、Series 等参数
Retries:重试的次数
Statuses:重试的 HTTP 状态码(通过 org.springframework.http.HttpStatus 表示)
Methods:重试的 HTTP 状态码(通过 org.springframework.http.HttpMethod 表示)
Series:重试的状态码(通过 org.springframework.http.HttpStatus.Series 表示)
20 RequestSize Request 用于限制请求大小,当请求超过限制时启用,限制请求达到下游服务,该过滤器将 RequestSize 作为参数
AddRequestHeader(在请求头中添加自定义的键值对)

AddRequestHeader 即主要在请求头中来添加自定义的键值对,其主要将应用过滤器 AddRequestHeader=X-Request-Name,X-Request-Value 应用并添加至请求头中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
application:
name: Gateway
cloud:
gateway:
routes:
- id: /guonei
uri: http://news.baidu.com
predicates:
- Method=GET
filters:
- AddRequestHeader=X-Request-Name,X-Request-Value

server:
port: 8210

logging:
level:
ROOT: DEBUG
会将 ```X-Request-Name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

###### AddRequestParameter (添加请求参数)
![](https://49812933408852955071488026628034-1301075051.cos.ap-nanjing.myqcloud.com/20210713131345.png)

AddRequestParameter 主要的作用就是在请求头(Header)中添加请求参数的键值对,即将 ```RouteDefinition /guonei``` 过滤器 ```_genkey_0=KEY_NAME, _genkey_1=KEY_VALUE``` 应用在 ```AddRequestParameter``` (添加请求参数)中

```yml
spring:
application:
name: Gateway
cloud:
gateway:
routes:
- id: /guonei
uri: http://news.baidu.com
predicates:
- Method=GET
filters:
- AddRequestParameter=KEY_NAME,KEY_VALUE

server:
port: 8210

logging:
level:
ROOT: DEBUG

这回将 KEY_NAME=KEY_VALUE 添加到所有匹配请求的下游请求所查询的字符串中,AddRequestParameter 即添加请求参数会用于匹配路径或主机的 URI 变量。

AddResponseHeader (添加响应头)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
application:
name: Gateway
cloud:
gateway:
routes:
- id: /guonei
uri: http://news.baidu.com
predicates:
- Method=GET
filters:
- AddResponseHeader=X-Response-Name,X-Response-Value

server:
port: 8210

logging:
level:
ROOT: DEBUG

此刻 AddResponseHeader 会将 X-Response-Name=X-Response-Value 所添加至响应头中。

Hystrix


路由容错控制器的主要的运用场景就是在遇到超时、服务器端错误来发挥作用,通过 Gateway 来集成 Hystrix(含 resilience4j)通过过滤器来加入 fallbackUri 得以实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;

/**
* 容错控制器
*
* @author kunlun
* @date 2021/7/16
*/
@RestController
public class NotFoundController {


@RequestMapping(value = "/fallback")
public Mono<Map<String, String>> notFound() {
Map<String, String> stringMap = new HashMap<>();
stringMap.put("code", "503");
stringMap.put("data", "Current service is unavailable");
stringMap.put("gateway", "GatewayConfigurationFaultToleranceRouting");
return Mono.just(stringMap);
}
}

.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
spring:
application:
name: GatewayConfigurationFaultToleranceRouting
cloud:
gateway:
routes:
- id: hey
uri: lb://service-provider/hey
predicates:
- Path=/hey
- Method=GET
filters:
- AddRequestParameter=name,key
# 服务熔断后使用的地址 @RequestMapping(value = "/fallback")
- name: CircuitBreaker
args:
name: fallback
fallbackUri: forward:/fallback
discovery:
locator:
# 允许服务发现
enabled: true
consul:
host: localhost
port: 8500
discovery:
service-name: service-provider

server:
port: 8210

需要注意的是由于我们这个是熔断,需要服务提供者的存在,因此你可以将服务提供者快速关闭来达到他短时间无法提供服务,之后网关才可认定这是由于服务出错导致的,如果直接关闭服务提供者那返回的只是 404

最后启动 consul 集群以及服务提供者,通过 GET 方式请求 http://localhost/hey 即可完成路由容错控制器的实现,当然你也可以直接通过 lb://service-provider 来进行配置你的服务路由。

GlobalFilter (全局过滤器)

另一种则为全局过滤器(GlobalFilter)这种过滤器与网关过滤器的区别是会 应用到所有路由中,在 Gateway 内置中,有九种全局过滤器:

Id Name Info
1 Forward Routing Filter 在 exchange 的 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性值中查找 URI(如果 URI 是 forward 协议,则将它用于 DispatcherHandler (调度程序处理程序) 处理请求)
2 LoadBalancerClient Filter 在 exchange 的 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 值中查找 URI,如果 URI 是 lb 协议,则会使用 Spring cloud LoadBalancerClient 将名称(lb://myservice 中的 myservice)解析为实际的主机和端口,并替换 URI 中相同属性。(除此过滤器还会查看 ServerWebExchangeU体力是。GATEWAY_SCHEME_PREFIX_ATTR 属性来判断他是否等于 lb
3 Netty Routing Filter 假设 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 中的 URI 使用的是 HTTP 或 HTTPS 协议,则运行 Netty Routing Filter。他用 Netty HttpClient 所发出的下游代理请求。相应应该放在 ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR 的 exchange 中以便在以后的过滤器中使用
4 Netty Write Response Filter ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR 值中存在 NettyHttpClientResponse 则会运行 Netty Write Response Filter。(他在所有其他过滤器完成后运行,并将代理相应写回到网关客户端的响应数据中)
5 RouteToRequestUriFilter 如果 ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR 值中存在 Route 对象,则会将路由到请求地址,他会根据请求 URI 创建一个新的 URI。新的 URI 则会位于 ServerWebExchangeUtils.GATEWAY_REQUEST_URI_ATTR 值中(假设 URI 具有协议前缀,如 lb:ws://serviceid 则会将从 URI 中所剥离 lb 协议,并防止在 ServerWebExchangeUtils.GATEWAY_SCHEMEPREFIX_ATTR 中以便稍后在过滤器中所使用)
6 Websocket Routing Filter 假设 ServerWebExhcangeUtils.GATEWAY_REQUEST_URL_ATTR 值中的 URI 是 ws 或 wss 协议,则运行 Websocket Routing Filter (利用 Spring Web Socket 底层代码将 WebSocket 请求转发到下游)
也可以通过 URI 前面添加 lb 来对 Websocket 来进行负载均衡,如 lb:ws://serviceid(需要注意的是如果在普通 HTTP 上使用 SockJS 来作为回退,则会匹配正常的 HTTP 路由以及 Websocket 路由)
7 Gateway Metrics Filter 使用需要添加 spring-boot-starter-actuator 依赖,默认会通过 spring.cloud.gateway.mertics.enable 设置为 false Gateway Metrics Filter 才会运行。该过滤器会添加一个名为 gateway.request 指标(Metrics),并包含四个属性:
routeId:路由器 ID
routeUri:API将会被路由转到 URI
outcome:由 HttpStatus.Series 所分类的结果
status:HTTP 请求返回给客户端的状态
8 Combined Global Filter and GatewayFilter Ordering(组合是全局过滤器和网管过滤器排序) 当请求进入并匹配到一个路由时, Filtering Web Handler 会将 GlobalFilter 的所有实例和 GatewayFilter 的所有路由特定实例添加到过滤器链中。(这个组合的过滤器链由 org.springframeworking.core.Ordered 接口的排序,并通过 gegOrder() 方法或注解 @Order 来设置)同时网关也会对过滤器逻辑执行的 “PRE” 和 ”POST“ 阶段来进行区分
9 Marking An Exchange As Routed (路由交换) 网关在路由 WEB 服务交换后(ServerWebExchange),会将 Gateway Already Routed 添加到 exchange 属性来将该交换标识为 ”路由”(一旦被请求标识为路由,则其他的路由过滤器会跳过该请求,也可以通过便携方法将交换标识为路由,或检查交换是否已经成为路由)

更多可以参考 https://cloud.spring.io/spring-cloud-gateway/reference/html/#the-addrequestheader-gatewayfilter-factory - Spring cloud gateway 官方文档

限流

限流的主要目的是防止过渡的消耗资源,从而来保障服务对所有用户的可用性,常见的限流措施如诸以限制并发数为目的的数据库连接池/线程池,以及瞬时并发数的限制,和限制每秒的平均速率都是最为常见的。而这些基本上都会在网关层上进行实现,如使用 nginx、zuul、gateway、openresty、kong 等方式进行限流。

以维基百科的话来说就是在计算机网络中,控制网络接口请求速率

如果没有进行限流的话,则有几率会被恶意提交,导致后续服务无法得到提供,从而达到拒绝服务攻击,而限流在高并发中,是一个必不可少的一个因素之一,通过限流发挥的作用从而保护用户的可用性,甚至防止一些恶意请求等问题的发生。

限流算法

计数器算法(固定窗口)

计数器算法是一个简单以及暴力的,他从第一个请求开始计数,在假设我们设置一个 QPS(每秒查询数为 100),一秒内请求超过了设定值,那么接下来的请求都会被拒绝,等过一秒后才可继续查询,这时会将计数恢复成0。

看似这个算法很好且可以解决一些简单的问题,但这个算法也存在的一些问题,如我在 100ms 内通过了 100 个请求,则后续的 900ms 内的请求都会被拒绝,因此这种现象也被称之为 “突刺现象”。虽然我们可以通过将单位时间设置的更短,但依旧无法解决核心的问题,这时就诞生了漏桶算法和令牌桶算法。

计数器算法(滑动窗口)

滑动窗口又是计数器算法的一个改进,主要解决了固定窗口上的 临界值 的问题,将一个计时器分为了诺干个小的计时器,这些计时器都是独立的,当请求时间大于第一个计时器最大时间时,那么将会向前平移一个计时器(同时将前一个计时器丢弃,然后将当前的计时器设置为第一个计时器)。

如上图中,窗口大小为 1s,那么每过 0.2ms就会删除前一个格子,从而向右移一个格子,滑动窗口最为主要的就是他的滑动,使得可以控制的更加细密,以此来解决临界值的问题,但滑动窗口和固定窗口都无法解决突刺现象这一问题。

漏桶算法

漏桶算法(Leaky Bucket)内主要有两个变量,分别为 桶的大小以及漏洞(孔的大小)的大小,可以将请求比喻水,当水倒入漏桶中,需要有一定的处理速度(孔的大小决定处理的速度),而桶的大小决定了可以容纳请求的大小,如果请求太大,则会导致漏桶装不下请求从而拒绝后续的请求。

令牌桶算法

令牌桶算法(Token Bucket)这种算法和我们在医院排队叫号比较相似,当请求到达的时候,会去令牌桶(由令牌工厂生产令牌)中取得一个令牌,之后等待响应。

这也达到了一定限流的目的,首先我们可以决定令牌工厂放入令牌的速率,也可以定时或者根据一些特定的规则来达到目的,从而达到增加/减少令牌的数量达到限流的效果。

限流的实现

Spring cloud Gateway 内置了限流工厂 RequestRateLimiterGatewayFilterFactory 底层使用 redis lua 脚本进行实现,因此需要添加 Consul、Gateway、Redis 的依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis-reactive -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

之后编写我们的限流配置类,主要通过名称、地址、以及 API 进行限流三种,也可以根据不同的状况自定义配置(还有一个跟均 CPU 进行限流的?)

LimitConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.example.demo.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

/**
* 限流配置文件,根据名称、地址、API
*
* @author kunlun
* @date 2021/7/23
*/
@Component
public class LimitConfig {

@Bean
KeyResolver userKeyResolver() {
return exchange ->
Mono.just(exchange.getRequest().getQueryParams().
getFirst("user"));
}
@Primary
@Bean
KeyResolver ipKeyResolver() {
return exchange ->
Mono.just(exchange.getRequest().getRemoteAddress().
getHostName());
}

@Bean
KeyResolver apiKeyResolver() {
return exchange ->
Mono.just(exchange.getRequest().getPath().
value());
}
}
application.yml

最后通过配置文件 application.yml 来链接 consul、redis 以及配置路由规则和全局过滤等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
spring:
application:
name: GatewayLimit
redis:
host: localhost
port: 6379
database: 1
password: toor
cloud:
gateway:
routes:
- id: hey
uri: lb://service-provider/hey
predicates:
- Path=/hey
- Method=GET
filters:
- name: RequestRateLimiter # 用于确定当前请求是否继续(如果不允许则会返回 429
args:
key-resolver: '#{@ipKeyResolver}'
redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充的平均速率
redis-rate-limiter.burstCapacity: 3 # 令牌桶总流量
discovery:
locator:
# 允许服务发现
enabled: true
consul:
host: localhost
port: 8500
discovery:
service-name: service-provider

server:
port: 8210

保证 redis、consul 等已经启动且可提供服务

端点

端点在 Spring cloud Gateway 中主要提供的作用就是方便开发或者运维人员进行调试,以此来获取过滤器列表、路由列表、路由信息、刷新路由信息以及添加\删除路由等操作,这依赖于 Spring boot Actuator ,需要注意的是这些端点都会在 /actuator/gateway 路径下,分别提供对应的服务:

Id Name Method Info Uri
1 globalfilters GET 展示所有全局过滤器 /actuator/gateway/globalfilters
2 routefilters GET 展示所有过滤器工厂 /actuator/gateway/routefilters
3 refresh POST 清空路由缓存 /actuator/gateway/refresh
4 routes GET 获取路由列表 /actuator/gateway/routes
5 routes{name} GET 获取指定的路由信息 /actuator/gateway/routes/{name}
6 routes{name} POST 添加一个路由 /actuator/gateway/routes/{name}
7 routes{name} DELETE 移出一个路由 /actuator/gateway/routes/{name}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis-reactive -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.5.2</version>
</dependency>

.

1
2
3
4
5
6
……
management:
endpoints:
web:
exposure:
include: '*'

开启后,可以访问 http://localhost:8210/actuator/gateway/routes/hey 来进行测试是否可以该路由的信息,如果没有返回则可能是 Actuator 配置上有一点点的问题

management 中,默认只开启了两个端点,分别为 health、info 两个 因此我们可以通过上述配置开启全部的断点访问,更详细的可以查阅 Actuator API: https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html

⬅️ Go back