Categories
程式開發

使用 Spring cloud Gateway 构建微服务网关


使用 Spring cloud Gateway 构建微服务网关

Spring Cloud Gateway 是 Spring Cloud 的一个子项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

相关概念

Route(路由):网关的基本构件块,类似于 nginx 的 location 配置。由一个 ID、一个目标 URI、一组 Predicate 和一组 Filter 定义Predicate(断言):路由组成的一部分,主要负责路由的匹配,来决定此次请求是否匹配路由,我们可以使用它匹配来自 HTTP 请求的任何内容,比如路径、参数或者 header 信息等等Filter(过滤器):这个是 GatewayFilter 的实例,请求经过 Predicate 匹配路由之后执行 Filter,我们可以使用它修改请求和响应。

快速上手

Spring Cloud Gateway 网关路由有两种配置方式:

通过配置文件配置通过 @Bean 自定义 RouteLocator 去配置

这两种方式是等价的,建议使用配置文件配置。因为 Spring Cloud Gateway 使用响应式编程框架,学习曲线相对陡峭。

引入依赖:


1.8

Hoxton.SR1

org.springframework.cloud
spring-cloud-starter-netflix-eureka-client

org.springframework.cloud
spring-cloud-starter-gateway

Spring cloud gateway 是使用 netty+webflux 实现,因此不需要再引入 web 模块。

配置文件:

server:
port: 8088
eureka:
client:
serviceUrl:
# 注册中心地址
defaultZone: http://dev-crm-host001.ymt.io:8761/eureka
spring:
application:
name: test-gateway
cloud:
gateway:
routes:
- id: test1
uri: http://www.baidu.com
predicates:
- Path=/baidu/**
filters:
- StripPrefix=1

各字段含义如下:

id:我们自定义的路由 ID,保持唯一uri:目标服务地址predicates:路由条件,接受一个参数,返回一个布尔结果决定是否匹配。Gateway 为我们内置了多种路由条件,包括 Path、Cookie、Param、Header、Before、After 等等,开箱即用,当然我们也可以自己实现 predicatesfilters:过滤规则,当请求经过 predicate 匹配成功后,执行 filter,我们可以使用它修改请求和响应,示例表示目标服务收到的 path 将无第一级。

启动程序,当我们访问 localhost:8088/baidu 时,gateway 会根据我们配置的路由规则转发到 https://www.baidu.com

路由规则:Predicate 介绍

Spring Cloud Gateway 的功能很强大,我们仅仅通过 Predicates 的设计就可以看出来,前面我们只是使用了 predicates 进行了简单的条件匹配,Spring Cloud Gateway 内置了很多 Predicates 工厂,这些 Predicates 工厂通过不同的 HTTP 请求参数来匹配,多个 Predicates 工厂可以组合使用。

网上有一张图总结了 Spring Cloud 内置的集中 Predicate:

使用 Spring cloud Gateway 构建微服务网关 1

说白了 Predicate 就是实现了一组匹配规则,方便让请求过来找到对应的 Route 进行处理。接下来介绍一下内置几种 Predicate 的使用。

通过时间匹配

Predicate 支持设置一个时间,在请求进行转发的时候,可以通过判断在这个时间之前或者之后进行转发。比如我们现在设置只有在 2020 年 1 月 1 日才会转发到 https://www.baidu.com/,在这之前不进行转发,我就可以这样配置:

spring:
cloud:
gateway:
routes:
- id: time_route
uri: http://www.baidu.com
predicates:
- After=2020-01-01T01:01:01+08:00[Asia/Shanghai]

Spring 是通过 ZonedDateTime 来对时间进行的对比,ZonedDateTime 是 Java 8 中日期时间功能里,用于表示带时区的日期与时间信息的类,ZonedDateTime 支持通过时区来设置时间,中国的时区是:Asia/Shanghai。

Before Predicate 刚好相反,在某个时间之前的请求的请求都进行转发。我们把上面路由规则中的 After 改为 Before,如下:

spring:
cloud:
gateway:
routes:
- id: time_route
uri: http://www.baidu.com
predicates:
- Before=2020-01-01T01:01:01+08:00[Asia/Shanghai]

就表示在这个时间之前可以进行路由,在这时间之后停止路由。

除过在时间之前或者之后外,Gateway 还支持限制路由请求在某一个时间段范围内,可以使用 Between Predicate 来实现。

spring:
cloud:
gateway:
routes:
- id: after_route
uri: http://www.baidu.com
predicates:
- Between=2019-01-01T01:01:01+08:00[Asia/Shanghai], 2020-01-01T01:01:01+08:00[Asia/Shanghai]

通过 Cookie 匹配

Cookie Route Predicate 可以接收两个参数,一个是 Cookie name,一个是正则表达式。

spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: http://www.baidu.com
predicates:
- Cookie=name, ymt

通过 Header 属性匹配

Header Route Predicate 和 Cookie Route Predicate 一样,也是接收 2 个参数,一个 header 中属性名称和一个正则表达式。

spring:
cloud:
gateway:
routes:
- id: header_route
uri: http://www.baidu.com
predicates:
- Header=name, ymt

通过 Host 匹配

spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://www.baidu.com
predicates:
- Host=**.baidu.com

通过 Request Method 匹配

可以通过是 POST、GET、PUT、DELETE 等不同的请求方式来进行路由。

spring:
cloud:
gateway:
routes:
- id: method_route
uri: http://www.baidu.com
predicates:
- Method=GET

通过请求路径匹配

之前示例是使用的请求路径去匹配的。

spring:
cloud:
gateway:
routes:
- id: test1
uri: http://www.baidu.com
predicates:
- Path=/baidu/**
filters:
- StripPrefix=1

通过请求参数匹配

Query Route Predicate 支持传入两个参数,一个是属性名一个为属性值,属性值可以是正则表达式。

spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://www.baidu.com
predicates:
- Query=type,baidu

如上图所示,输入 localhost:8088/?type=baidu 会被转发到 https://www.baidu.com/?type=baidu

如果写成 – Query=type,这样配置,只要请求中包含 type 属性的参数即可匹配路由。

通过请求 ip 地址进行匹配

spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: http://www.baidu.com
predicates:
- RemoteAddr=192.168.1.1/24

组合使用

以上示例演示了单个 Predicate 的使用,Predicate 也可以放在一起组合使用。

spring:
cloud:
gateway:
routes:
- id: combination_route
uri: http://ityouknow.com
predicates:
- RemoteAddr=192.168.1.1/24
- Query=type,baidu
- Path=/baidu/**

各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。

一个请求满足多个路由的 Predicate 条件时,请求只会被首个成功匹配的路由转发

如何自己实现 Predicate

以上介绍了内置的 Predicate 的使用,但是在一些特殊的场景下,内置的 Predicate 可能满足不了我们的需求,这个时候我们就可以根据 Gateway 提供的接口实现自己的 Predicate。

需求背景

系统中有一些请求需要根据参数去转发,比如根据查询参数的某一个范围去转发,例如 /server?type=A,当 type 为 A、B、C 三个其中一个值时转发到 server1 服务,type 为 D、E、F 三个其中一个值时转发到 server2 服务。我们可以通过继承 org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory 实现

/**
* 工作流相关的断言
*
* @author zhangtao
* @since 2020-01-06 7:26 下午
*/
@Component
public class TypeRoutePredicateFactory extends AbstractRoutePredicateFactory {
public WorkFlowRoutePredicateFactory() {
super(WorkFlowRoutePredicateFactory.Config.class);
}

private static final String DATETIME_KEY = "type";

@Override
public List shortcutFieldOrder() {
return Collections.singletonList(DATETIME_KEY);
}

// 配置参数的类型,此时指定为List
@Override
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
}

// 判断的逻辑,如果返回为true则匹配成功
@Override
public Predicate apply(WorkFlowRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
// 判断参数中是否有配置的type
List types = exchange.getRequest().getQueryParams().get("type");
if (types != null && types.size() == 1 && config.getType().contains(types.get(0))) {
return true;
}
return false;
}
};
}

@Validated
public static class Config {
private List type = new ArrayList();

public List getType() {
return type;
}
public void setType(List type) {
this.type = type;
}
}
}

配置文件:

spring:
cloud:
gateway:
routes:
- id: server1-route
uri: http://www.baidu.com
predicates:
# Type为,TypeRoutePredicateFactory类 RoutePredicateFactory前面的值
- Type=A,B,C
- id: server2-route
uri: https://cn.bing.com/
predicates:
# Type为,TypeRoutePredicateFactory类 RoutePredicateFactory前面的值
- Type=D,E,F

以上配置为,当请求参数中 type 的值为 A、B、C 其中一个时则匹配成功,通过 server1-route 路由转发到 http://www.baidu.com,当请求参数中 type 的值为 D、E、F 其中一个时则匹配成功,通过 server2-route 路由转发到 https://cn.bing.com/

总结

Spring Cloud Gateway 使用非常的灵活,可以根据不同的情况来进行路由分发,在实际项目中可以自由组合使用。同时 Spring Cloud Gateway 还有更多很酷的功能,比如 Filter、熔断和限流等。