Gateway的介绍和使用

项目地址:https://spring.io/projects/spring-cloud-gateway

Gateway提供了一个用于在 Spring WebFlux 之上构建 API 网关。

Spring Cloud Gateway 底层使用了高性能的通信框架Netty。

特征:

  • 基于 Spring Framework 5、Project Reactor 和 Spring Boot 2.0

  • 能够匹配任何请求属性的路由。

  • 谓词和过滤器特定于路由。

  • 断路器集成。

  • Spring Cloud DiscoveryClient 集成

  • 易于编写谓词和过滤器

  • 请求速率限制

  • 路径重写

网关在用户端与微服务之间建立了一道屏障,通过 API 网关为微服务访问提供了统一的访问入口,所有用户端的请求被 API 网关拦截并在此基础上可以实现额外功能,例如:

  • 针对所有请求进行统一鉴权、熔断、限流、日志等前置处理,让微服务专注自己的业务。
  • 统一调用风格,通常 API 网关对外提供 RESTful 风格 URL 接口。用户传入请求后,由 API 网关负责转换为后端服务需要的 RESTful、RPC、WebService 等方式,这样便大幅度简化用户的接入难度。
  • 更好的安全性,在通过 API 网关鉴权后,可以控制不同角色用户访问后端服务的权利,实现了服务更细粒度的权限控制。
  • API 网关是用户端访问 API 的唯一入口,从用户的角度来说只需关注 API 网关暴露哪些接口,至于后端服务的处理细节,用户是不需要知道的。从这方面讲,微服务架构通过引入 API 网关,将用户端与微服务的具体实现进行了解耦。

Gateway项目搭建

第一步,利用 Spring Initializr 向导创建 Gateway 工程。

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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-gateway</artifactId>
</dependency>

第二步,修改application.yml配置信息。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 应用名称
spring:
application:
name: gateway-service
cloud:
# Nacos认证信息
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
nacos:
discovery:
username: nacos
password: nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
server-addr: 47.106.172.60:8848
# 注册到 nacos 的指定 namespace,默认为 public
namespace: public
# 让gateway通过nacos实现自动路由转发,能够通过nacos注册的服务名进行转发,http://网关服务地址/服务名称/uri
gateway:
discovery:
locator:
enabled: true
#路由规则配置
routes:
# 路由规则Id,路由唯一标识
# 第一个路由配置,service-a路由规则
- id: service-a
# lb开头代表基于gateway的负载均衡策略选择实例
uri: lb://provider-service
# 谓词配置
predicates:
# Path路径谓词,表示客户端URI如果以/a/开头便会转发到service-a实例
- Path=/a/**
# After生效时间谓词,2021年12月20日后该路由才能在网关对外暴露
- After=2021-12-20T00:00:00.000+08:00[Asia/Shanghai]
# 过滤器配置
filters:
#忽略掉第一层前缀进行转发
- StripPrefix=1
# 为响应头附加X-Response=Blue
- AddResponseHeader=X-Response,Blue
#涉及过滤器参数时,采用name-args的完整写法
- name: Retry #name是内置的过滤器名
args: #参数部分使用args说明
retries: 3
status: 503

# 第二个路由配置
- id: service-b
uri: lb://consumer-service
predicates:
- Path=/b/*
filters:
- StripPrefix=1

#对外暴露actuator所有监控指标,便于监控系统收集跟踪
management:
endpoints:
web:
exposure:
include: '*'

# 应用服务 WEB 访问端口
server:
port: 7000

通过网关访问其他服务:

1
http://网关IP:端口/微服务id/URI

路由转发流程:

  1. Gateway、provider-service,consumer-service 这些都是微服务实例,在启动时向 Nacos 注册登记;
  2. 用户端向 Gateway 发起请求,请求地址 http://localhost/service-a/serviceName;
  3. Gateway 网关实例收到请求,解析其中第二部分 service-a,即微服务 Id,第三部分 URI 为“/serviceName”。之后向 Nacos 查询 service-a 可用实例列表;
  4. Nacos 返回可用微服务service-a实例信息;
  5. Spring Cloud Gateway 内置 Ribbon,根据默认轮询策略将请求转发至其中一个实例,转发的完整 URL 将附加用户的 URI。
  6. service-a 实例处理后返回 JSON 响应数据给 Gateway;
  7. Gateway 返回给用户端,完成一次完整的请求路由转发过程。

Route(路由)

路由(Route)是指一个完整的网关地址映射与处理过程。一个完整的路由包含两部分配置:谓词(Predicate)与过滤器(Filter)。

路由配置模板:

1
2
3
4
5
6
7
8
9
10
11
12
spring:
gateway:
discovery:
locator:
enabled: false #不再需要Gateway路由转发
routes:
- id: xxx #路由规则id
uri: lb://微服务id #路由转发至哪个微服务
predicates:
//具体的谓词
filters:
//具体的过滤器

Predicate(谓词)

谓词决定前端应用发来的请求要被转发到哪个微服务上,可以匹配HTTP请求的任何内容,Headers或Parameters。

  • After 代表在指定时点后路由规则生效。
1
2
predicates:
- After=2020-10-04T00:00:00.000+08:00
  • Before 代表在指定时点前路由规则生效。
1
2
predicates:
- Before=2020-01-20T17:42:47.789-07:00[America/Denver]
  • Between 代表在该时间范围内的请求
1
2
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
  • Cookie 代表请求包含cookie,并且cookie的值符合value的规则
1
2
predicates:
- Cookie=chocolate, value

value是正则表达式。

  • Host 代表匹配对应的host
1
2
predicates:
- Host=**.somehost.org,**.anotherhost.org
  • Method 代表匹配对应的请求方式
1
2
predicates:
- Method=GET,POST
  • Path 代表 URI 符合映射规则时生效。
1
2
predicates:
- Path=/b/**
  • Query 代表匹配所有含有请求参数param且它的值符合正则表达式value的请求,value可以省略,请求参数含有param即可
1
2
predicates:
- Query=value
  • Header 代表包含指定请求头时生效。
1
2
predicates:
- Header=X-Request-Id, value

如果请求具有名为 X-Request-Id 的 Header,value是正则表达式,其值与\d+正则表达式匹配(具有一个或多个数字的值),则该路由匹配。

  • Method 代表要求 HTTP 方法符合规定时生效。
1
2
predicates:
- Method=GET
  • RemoteAddr 代表匹配指定的Ip
1
2
predicates:
- RemoteAddr=192.168.1.1/24
  • Weight 匹配权重
1
2
3
4
5
6
7
8
9
10
11
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
# 高权重
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
# 低权重
- Weight=group1, 2

参数 groupweight(int) ,group声明组,weight表示在该组内的权重

Filter(过滤器)

转发过程中请求、响应数据被网关如何加工处理。

  • AddRequestParameter 是对所有匹配的请求添加一个查询参数。
1
2
filters:
- AddRequestParameter=foo,bar #在请求参数中追加foo=bar
  • AddResponseHeader 会对所有匹配的请求,在返回结果给客户端之前,在 Header 中添加响应的数据。
1
2
3
#在Response中添加Header头,key=X-Response-Foo,Value=Bar。
filters:
- AddResponseHeader=X-Response,Blue
  • Retry 为重试过滤器,当后端服务不可用时,网关会根据配置参数来发起重试请求。
1
2
3
4
5
6
filters:
#涉及过滤器参数时,采用name-args的完整写法
- name: Retry #name是内置的过滤器名
args: #参数部分使用args说明
retries: 3
status: 503

Gateway 的执行原理与自定义过滤器

Spring Cloud Gateway Diagram

  1. Spring Cloud Gateway 启动时基于 Netty Server 监听指定的端口(该端口可以通过 server.port 属性自定义)。当前端应用发送一个请求到网关时,进入 Gateway Handler Mapping 处理过程,网关会根据当前 Gateway 所配置的谓词(Predicate)来决定是由哪个微服务进行处理。
  2. 确定微服务后,请求向后进入 Gateway Web Handler 处理过程,该过程中 Gateway 根据过滤器(Filters)配置,将请求按前后顺序依次交给 Filter 过滤链进行前置(Pre)处理,前置处理通常是对请求进行前置检查,例如:判断是否包含某个指定请求头、检查请求的 IP 来源是否合法、请求包含的参数是否正确等。
  3. 当过滤链前置(Pre)处理完毕后,请求会被 Gateway 转发到真正的微服务实例进行处理,微服务处理后会返回响应数据,这些响应数据会按原路径返回被 Gateway 配置的过滤链进行后置处理(Post),后置处理通常是对响应进行额外处理,例如:将处理过程写入日志、为响应附加额外的响应头或者流量监控等。

自定义全局过滤器

在 Spring Cloud Gateway 中,自定义过滤器分为两种,全局过滤器与局部过滤器。两者唯一的区别是:全局过滤器默认应用在所有路由(Route)上,而局部过滤器可以为指定的路由绑定。

例子:统计请求耗时的过Filter

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
39
40
41
42
43
44
45
46
47
48
49
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

//自动实例化并被Spring IOC容器管理
@Component
//全局过滤器必须实现两个接口:GlobalFilter、Ordered
//GlobalFilter是全局过滤器接口,实现类要实现filter()方法进行功能扩展
//Ordered接口用于排序,通过实现getOrder()方法返回整数代表执行当前过滤器的前后顺序
public class ElapsedFilter implements GlobalFilter, Ordered {

//基于slf4j.Logger实现日志输出
private static final Logger logger = LoggerFactory.getLogger(ElapsedFilter.class);
//起始时间属性名
private static final String ELAPSED_TIME_BEGIN = "elapsedTimeBegin";

/**
* 实现filter()方法记录处理时间
* @param exchange 用于获取与当前请求、响应相关的数据,以及设置过滤器间传递的上下文数据
* @param chain Gateway过滤器链对象
* @return Mono对应一个异步任务,因为Gateway是基于Netty Server异步处理的,Mono对就代表异步处理完毕的情况。
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//Pre前置处理部分
//在请求到达时,往ServerWebExchange上下文环境中放入了一个属性elapsedTimeBegin,保存请求执行前的时间戳
exchange.getAttributes().put(ELAPSED_TIME_BEGIN, System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(ELAPSED_TIME_BEGIN);
if (Objects.nonNull(startTime))
logger.info(exchange.getRequest().getRemoteAddress() + " | " + exchange.getRequest().getPath() + " | " + (System.currentTimeMillis() - startTime) + "ms" );
})
);
}

//设置为最高优先级,最先执行ElapsedFilter过滤器
//return Ordered.LOWEST_PRECEDENCE; 代表设置为最低优先级
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}