Spring Cloud Gateway 原理介绍和应用

Spring Cloud Gateway 原理介绍和应用

1 简介

在微服务架构下,我们一般会把相对独立的业务或功能划分为不同的服务,不同服务之间相互隔离,独立部署。这些微服务由服务注册中心集中管理。对于来自外部的用户请求来说,在处理请求的核心业务逻辑之前,一般还需要对请求做一些预处理,如权限校验、监控统计、流量限制等。如果外部请求直接到达微服务,那么所有的服务都需要各自负责处理这些功能,会造成这部分功能逻辑在各个服务重复出现,增加了未来对这些基础功能的维护升级的成本。

所以在微服务环境下,我们还需要一个网关组件来作为请求入口。一些基础的请求预处理的逻辑可以统一实现在网关这一层,这样业务服务只需要专注于处理业务逻辑即可。另外,引入网关作为统一请求入口之后,还可以利用网关来实现一些其他的功能,比如服务保护、灰度发布等。

本文主要介绍 Java 实现的作为网关的开源组件 Spring Cloud Gateway。Spring Cloud Gateway 是 Spring Cloud 微服务生态下的网关组件。在 Spring Cloud Gateway 发布之前,Spring Cloud 使用的是由 Netflix 开源的 Zuul 1 作为网关组件。Zuul 1 是基于传统的 Servlet API 开发的,使用了阻塞的网络模型,每个请求需要分配专门的线程处理,所以资源开销比较大,在高并发的情况下需要创建大量的线程来处理请求,线程数目会成为系统的瓶颈。作为取代 Spring Cloud Zuul 的组件,Spring Cloud Gateway 网络层使用了基于非阻塞的 Netty,解决了线程数瓶颈从而提升了系统性能。

Spring Cloud Gateway 是基于 Spring 5 和 Spring Boot 2 搭建的,本质上是一个 Spring Boot 应用。在详细介绍其基本原理之前,先看一下通常而言,可以由微服务网关提供的功能。

2 微服务网关的功能

本文开头介绍了为什么在微服务环境下会需要一个网关组件,它作为请求入口,可以对微服务环境起到增强和保护的作用。一些比较常见的微服务网关的功能包括但不限于

  • 请求路由:根据请求本身的属性把请求转发到不同的微服务是微服务网关的基本功能之一。业务运维人员需要针对不同的服务配置路由,使网关能够根据请求的 header、路径、参数、协议等属性将其转发到对应的服务。
  • 服务发现:网关是微服务环境的请求入口。支持服务发现能使网关在转发请求到目标服务时充分利用服务注册中心动态管理服务实例的优势,在配置路由转发的目标地址时也会更加方便。
  • 修改请求响应:网关在收到外部请求,将其转发到目标服务之前,可以根据需求对请求进行修改,比如果更改请求 header、参数等。类似地,也可以在获取到业务服务响应之后,返回给用户前对响应进行修改。
  • 权限校验:某些业务场景在处理用户请求时需要先对用户进行权限校验,这部分逻辑也可以由网关来负责。请求在到达网关时,由网关根据请求要访问的业务接口先对用户鉴权,只有校验通过的请求才会转发到对应的服务,而校验不通过的请求会被网关直接拒绝。这样做能够把拒绝无效请求这一步提前到网关这一层,减少无效的流量进入到业务服务。
  • 限流熔断:网关可以通过添加限流、熔断等机制来对业务服务起保护作用,提升系统整体的可用性。根据业务服务的吞吐量,网关可以限制转发到该服务的请求数量,超出限制的请求直接拒绝或降级,这样可以避免因为过多的请求导致业务服务负载过高的情况。当业务服务异常时,还可以通过熔断的方式到达快速失败的效果。
  • 请求重试:对于一些幂等的请求,当网关转发目标服务失败时,可以在网关层做自动重试。对于一些多实例部署服务,重试时还可以考虑把请求转发到不同的实例,以提高请求成功的概率。
  • 响应缓存:当用户请求获取的是一些静态的或更新不频繁的数据时,一段时间内多次请求获取到的数据很可能是一样的。对于这种情况可以将响应缓存起来。这样用户请求可以直接在网关层得到响应数据,无需再去访问业务服务,减轻业务服务的负担。
  • 响应聚合:某些情况下用户请求要获取的响应内容可能会来自于多个业务服务。网关作为业务服务的调用方,可以把多个服务的响应整合起来,再一并返回给用户。
  • 监控统计:因为网关是请求入口,所以在网关这一层可以方便地对外部的访问请求做监控和统计,同时还可以对业务服务的响应做监控,方便发现异常情况。
  • 灰度流量:网关可以用来做服务流量的灰度切换。比如某个业务服务上线了新版本,那可以在网关这一层按照灰度策略,把一部分请求流量切换到新版本服务上,以达到验证新版本业务服务的功能和性能的效果。
  • 异常响应处理:对于业务服务返回的异常响应,可以在网关层在返回给用户之前做转换处理。这样可以把一些业务侧返回的异常细节隐藏,转换成用户友好的错误提示返回。

上面列举了一些常见的微服务网关的功能。有一部分功能 Spring Cloud Gateway 本身已经实现好了,但仍有一部分功能是 Spring Cloud Gateway 本身还未提供的,比如响应缓存、权限校验、响应聚合、异常响应处理等。这些功能需要自行根据需求,通过 Spring Cloud Gateway 暴露的基础接口实现。

3 Spring Cloud Gateway 基本原理

Spring Cloud Gateway 使用了 Spring WebFlux 非阻塞网络框架,网络层默认使用了高性能非阻塞的 Netty Server,解决了 Spring Cloud Zuul 因为阻塞的线程模型带来的性能下降的问题。

Gateway 本身是一个 Spring Boot 应用,它处理请求是逻辑是根据配置的路由对请求进行预处理和转发。Gateway 有几个比较核心的概念:

  • Route: 一个 Route 由路由 ID,转发 URI,多个 Predicates 以及多个 Filters 构成。Gateway 上可以配置多个 Routes。处理请求时会按优先级排序,找到第一个满足所有 Predicates 的 Route。
  • Predicate: 表示路由的匹配条件,可以用来匹配请求的各种属性,如请求路径、方法、header 等。一个 Route 可以包含多个子 Predicates,多个子 Predicates 最终会合并成一个。
  • Filter: 过滤器包括了处理请求和响应的逻辑,可以分为 pre 和 post 两个阶段。多个 Filter 在 pre 阶段会按优先级高到低顺序执行,post 阶段则是反向执行。Gateway 包括两类 Filter
    • 全局 Filter: 每种全局 Filter 全局只会有一个实例,会对所有的 Route 都生效。
    • 路由 Filter: 路由 Filter 是针对 Route 进行配置的,不同的 Route 可以使用不同的参数,因此会创建不同的实例。

下图展示了 Spring Cloud Gateway 的基本工作原理,过程比较简单。

Gateway 在启动时会创建 Netty Server,由它接收来自 Client 的请求。收到请求后根据路由的匹配条件找到第一个满足条件的路由,然后请求在被该路由配置的过滤器处理后由 Netty Client 转到目标服务。服务返回响应后会再次被过滤器处理,最后返回给 Client。

下面基于 Spring Cloud Gateway 2.2.2.RELEASE 版本,详细介绍一下 Gateway 的实现原理。

3.1 请求路由原理

Gateway 使用了 Spring WebFlux 框架,该框架处理请求的入口在类 DispatcherHandler 。它会根据提供的 HandlerMapping 来获取处理请求的 Handler 方法。Gateway 应用对 HandlerMapping 的实现是 RoutePredicateHandlerMapping

下图展示了 Gateway 接收请求的过程。

  1. 进来的请求由 DispatcherHandler 处理。
  2. DispatcherHandler 根据 RoutePredicateHandlerMapping 获取 Handler 方法。
  3. RoutePredicateHandlerMapping 依赖 RouteLocator 获取所有路由配置并根据匹配条件打到请求匹配的路由。
  4. RoutePredicateHandlerMapping 把请求交给 FilteringWebHandler 处理。
  5. FilteringWebHandler 从请求匹配的路由获取对应的路由 Filter,并和全局 Filter 合并构造 GatewayFilterChain,请求最终由 GatewayFilterChain 里的 Filter 按顺序处理。

可以看到 Gateway 路由不同的请求的关键在于从 RouteLocator 获取路由配置。

3.2 路由配置

路由是 Gateway 的核心构件,不同的路由根据匹配条件可以处理不同类型的请求,并转发到对应的目标服务。一个路由由以下几个属性组成

  • Id: 路由 ID。
  • Uri: 转发请求的目标地址。
  • Order: 顺序(优先级)。
  • Predicate: 匹配条件。多个 Predicates 会合并成一个聚合的条件。
  • Filters: 路由过滤器。这些过滤器最终会和全局过滤器一起排序处理匹配成功的请求。
  • Metadata: 额外的元数据。

Spring Cloud Gateway 本身提供了很多 Predicate 和 Filter 的实现,一些基本的功能可以通过这些现成的 Predicate 和 Filter 配置实现。这些 Gateway 本身提供的 Predicate 和 Filter 在官方文档上有详细的介绍,本文不会一一赘述。

3.2.1 RouteLocator

Gateway 通过接口 RouteLocator 接口来获取路由配置,RouteLocator 有不同的实现,对应了不同的定义路由的方式。

1
2
3
4
5
public interface RouteLocator {

Flux<Route> getRoutes();

}

一种定义路由的方式是通过 RouteLocatorBuilder 提供的 DSL API。比如下面代码定义了两个路由,第一个路由包含 host 和 path 两个匹配条件,以及一个添加响应 header 的 filter,请求转发的目标地址是 http://foo.org:80 。第二个路由只包含 path 匹配条件,有一个添加请求 header 的 filter 和一个添加请求参数的 filter,转发地址是 http://bar.org:80,另外还有一项 metadata。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.host("**.abc.org").and().path("/image/png")
.filters(f ->
f.addResponseHeader("X-TestHeader", "foobar"))
.uri("http://foo.org:80")
)
.route(r -> r.path("/image/webp")
.filters(f -> f
.addRequestHeader("X-TestHeader", "baz")
.addRequestParameter("test-param", "value"))
.uri("http://bar.org:80")
.metadata("key", "value")
)
.build();
}

RouteLocator 另一种常用的实现是 RouteDefinitionRouteLocator 。这种实现依赖于 RouteDefinitionLocator 来提供 RouteDefinition ,再由 RouteDefinition 构造路由。RouteDefinitionRouteLocator 下文会再介绍。

多个 RouteLocator 可以同时使用,这些实例提供的路由最终会由 CompositeRouteLocator 整合,再由 CachingRouteLocator 缓存。他们之间的关系如下图。

CachingRouteLocator 会把下层的 RouteLocator 返回的路由缓存起来,后续直接返回。同时监听 RefreshRoutesEvent 来刷新缓存的路由。

3.2.2 RouteDefinitionLocator

RouteDefinitionRoute 的区别在于 RouteDefinition 是一种便于序列化的对象。它定义了一个路由应该包含哪些匹配条件和过滤器,以及这些匹配条件和过滤器使用的参数。在使用时 Gateway 会根据 RouteDefinition 对象提供的定义构造出 Route 对象。

RouteDefinitionLocator 是一个提供 RouteDefinition 的接口。

1
2
3
4
5
public interface RouteDefinitionLocator {

Flux<RouteDefinition> getRouteDefinitions();

}

比较常用的方式是通过 PropertiesRouteDefinitionLocator 实现解析 Spring boot 配置中的路由配置,如

1
2
3
4
5
6
7
8
9
10
11
12
spring:
cloud:
gateway:
routes:
- id: example_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
- Method=GET,POST
filters:
- AddRequestParameter=red, blue
- AddResponseHeader=X-Header, foo

这个路由包含了两个匹配条件,Host 和 Method,对应 HostRoutePredicateFactoryMethodRoutePredicateFactory 。另外还有两个路由 Filter,AddRequestParameterAddResponseHeader ,对应 AddRequestParameterGatewayFilterFactoryAddResponseHeaderGatewayFilterFactory 。Gateway 在构造 Route 对象时会根据 RouteDefinition 配置的匹配条件名和过滤器名找到对应的 Factory 获取对应的匹配条件和过滤器实例。

RouteLocator 类似,如有需要可以配置多个 RouteDefinitionLocator。多个 RouteDefinitionLocator 最后会被 CompositeRouteDefinitionLocator 整合。最终完整的和路由配置相关的类之间的关系如下图

3.3 匹配条件

路由的匹配条件可以针对请求的各种条件做匹配,匹配成功的路由负责处理该请求。路由有优先级,匹配时会把路由按优先级高到低排序找到第一个匹配成功的路由。所以在配置时一般匹配条件越是具体苛刻的路由应该配置更高的优先级。

Spring Cloud Gateway 本身已经提供了很多常用的 Predicate 实现,常用的匹配规则都可以直接配置使用,比如 Host、Header、请求参数、请求方法等。需要自定义匹配规则时可以实现接口 RoutePredicateFactory

1
2
3
4
5
6
7
8
@FunctionalInterface
public interface RoutePredicateFactory<C> extends ShortcutConfigurable, Configurable<C> {
// ...

Predicate<ServerWebExchange> apply(C config);

// ...
}

3.4 过滤器

过滤器包含了 Gateway 处理请求和响应的核心逻辑。一些功能比较基础通用的过滤器 Spring Cloud Gateway 项目本身已经实现提供了,可以直接配置使用。Spring Cloud Gateway 过滤器分为两类,全局 Filter 和路由 Filter。

全局 Filter 顾名思义就是会自动对所有的路由都生效的过滤器,有些功能比如全局日志、监控等就比较适合设计成全局过滤器。这类过滤器对应的是 GlobalFilter 接口。需要自定义的全局过滤器时只要实现这个接口并注册成 Bean 即可。

1
2
3
4
5
public interface GlobalFilter {

Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);

}

路由 Filter 是针对某个路由进行配置的。有些功能需要根据具体路由的需求使用不同的参数配置,比如熔断、增删 header 等。这类功能就需要以路由 Filter 的形式实现。要自定义路由 Filter 需要实现接口 GatewayFilterFactory

1
2
3
4
5
6
7
8
@FunctionalInterface
public interface GatewayFilterFactory<C> extends ShortcutConfigurable, Configurable<C> {
// ...

GatewayFilter apply(C config);

// ...
}

由于不同的路由在使用这类 Filter 时可能会使用不同的配置,所以这类 Filter 需要对应的 Factory。接口方法 apply 根据传入的配置再生成 Filter 实例。

这两类 Filter 在处理请求之前会先放一起排序。Filter 的顺序可以通过添加 Spring 的 Order 注解或者通过实现 Ordered 接口声明。Gateway Filter 处理请求和响应相关的核心代码在 FilteringWebHandler 类。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class FilteringWebHandler implements WebHandler {

protected static final Log logger = LogFactory.getLog(FilteringWebHandler.class);

private final List<GatewayFilter> globalFilters;

public FilteringWebHandler(List<GlobalFilter> globalFilters) {
this.globalFilters = loadFilters(globalFilters);
}

private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
return filters.stream().map(filter -> {
GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter);
if (filter instanceof Ordered) {
int order = ((Ordered) filter).getOrder();
return new OrderedGatewayFilter(gatewayFilter, order);
}
return gatewayFilter;
}).collect(Collectors.toList());
}

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
List<GatewayFilter> gatewayFilters = route.getFilters();

List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
combined.addAll(gatewayFilters);

AnnotationAwareOrderComparator.sort(combined);

// ...

return new DefaultGatewayFilterChain(combined).filter(exchange);
}

private static class DefaultGatewayFilterChain implements GatewayFilterChain {

private final int index;

private final List<GatewayFilter> filters;

// ...

@Override
public Mono<Void> filter(ServerWebExchange exchange) {
return Mono.defer(() -> {
if (this.index < filters.size()) {
GatewayFilter filter = filters.get(this.index);
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this,
this.index + 1);
return filter.filter(exchange, chain);
}
else {
return Mono.empty();
}
});
}

}

private static class GatewayFilterAdapter implements GatewayFilter {

private final GlobalFilter delegate;

GatewayFilterAdapter(GlobalFilter delegate) {
this.delegate = delegate;
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return this.delegate.filter(exchange, chain);
}

// ...
}

}

可以看到 FilteringWebHandler 会把所有的 GlobalFilter 实例加载进来并且使用 GatewayFilterAdapter 适配成 GatewayFilter 。在 handle 方法处理请求时,会把适配后的 GlobalFilter 以及路由本身的 GatewayFilter 全并在一个 List 里,然后按 Order 排序。排序完之后会构造一个 GatewayFilterChain ,由 GatewayFilterChainfilter 方法触发这些 Filter 的执行。

3.5 转发地址

配置路由时一般还需要指定请求的转发目标服务。请求到在 Gateway 后经过过滤器处理,之后会被转发到配置好的目标服务。一种配置目标服务的方式直接配置 IP 地址或域名。

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: example_route
uri: http://example.org
filters:
# ...

作为微服务网关,Spring Cloud Gateway 本身也支持接入 Eureka 服务注册中心,并从中获取服务列表作为转请求的目标服务。

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: service-a-route
uri: lb://service-a
predicates:
- Path=/service/**

其中 service-a 是服务注册到 Eureka 注册中心的服务名。URI 以 lb:// 开头的路由会被 Gateway 自带的 ReactiveLoadBalancerClientFilter 处理,这个 Filter 会识别 URI 中的服务名,并为其创建一个 ReactorLoadBalancer,然后负载均衡策略从对应服务的实例中获取其中一个实例作为请求转发目标。

ReactorLoadBalancer 需要一个 ServiceInstanceListSupplier 为其提供服务列表。ServiceInstanceListSupplier 有多种实现。

  • FixedServiceInstanceListSupplier : 每次返回固定的服务实例列表。
  • DiscoveryClientServiceInstanceListSupplier : 从 DiscoveryClient 动态获取服务的实例列表。
  • HealthCheckServiceInstanceListSupplier : 依赖一个被代理的 Supplier 实现,为其返回的服务实例执行主动健康检查,只返回健康的实例。
  • ZonePreferenceServiceInstanceListSupplier : 依赖一个被代理的 Supplier 实现,只返回同可用区的服务实例,当可用区下没有实例时,会返回全部实例。
  • CachingServiceInstanceListSupplier : 依赖一个被代理的 Supplier 实现,将代理的 Supplier 返回的实例缓存起来并定时刷新缓存。

默认情况下,通过 Eureka 服务注册方式获取的 ServiceInstanceListSupplier 结构如下图。

4 问题和优化

Spring Cloud Gateway 本身的功能可以满足我们大部分的需求,但仍然存在一些问题需要优化和改造。首先,作为一个基础的网关组件,它需要有较高的可用性。所以在修改路由配置或添加新的过滤器功能时,我们期望 Gateway 能够实现热加载,因为如果每次修改配置时都重启,那么会对可用性产生影响。其次 Spring Cloud Gateway 项目本身没有提供方便管理各种配置的工具,如果能有可视化的管理工具那么使用起来会更加直观方便。

路由动态配置

上文有介绍过一种配置路由的方法是通过 RouteLocatorBuilder 的 API 以写代码的方法定义。但是这种方式每次需要更新路由配置时都要重新编译项目上线。Gateway 作为微服务后端请求的入口,每次修改路由配置都要重新部署上线会降低系统的可用性,这种方式是不能接受的。另一种通过 Spring 配置文件定义路由的方式虽然可以利用 Spring 配置刷新的机制来实现动态更新路由的功能,但是通过配置文件来定义路由这种方式本身不便于路由配置的管理。

为了达到路由动态更新路由配置的目的,我们实现了一个通过监听一个更新事件,来动态更新路由配置的 RouteDefinitionLocator 。同时我们修改了定义 RouteDefinition 的地方,由默认的 Spring 配置文件改成了数据库,然后通过定时监控数据库里的路由更新来触发更新事件。

上图是 RouteDefintion 动态更新的过程。RouteDefinitionMonitor 模块负责定时检查保存在数据库里的 RouteDefinition ,检查到有更新时会触发更新事件,DynamicRouteDefinitionLocator 会监听这个事件并更新。这样上层的 RouteLocator 就能获取到最新的 RouteDefinition

因为 RouteDefinitionMonitor 是定时检查的(默认 30 秒),所以 RouteDefinition 的更新并不是实时的,会有一定的延迟。但我们其实并不需要 RouteDefinition 实时生效,所以这个延迟是可以接受的。

通过这种方式我们可以随时更改路由配置,Gateway 在经过有限的延迟之后就会加载到新的路由配置,无需重启。

匹配规则和过滤器热加载

Spring Cloud Gateway 本身提供了很多现成的匹配规则和过滤器实现,大部分常见的需求都能利用这些已有的匹配规则和过滤器实现。但有时候还是会有需要自定义匹配规则和过滤器的需求。

上文提到过要想自定义匹配规则或过滤器只需要实现对应的接口即可。但常规的使用 Java 类的实现方式会要求重新编译上线,每当有新的实现或修改已经的实现代码时都需要重启 Gateway。为了解决这个问题,可以使用 Groovy 来实现自定义的匹配规则和过滤器,然后利用 JVM 对 Groovy 脚本的支持,使用 GroovyClassLoader 来解析这些 Groovy 实现的匹配规则和过滤器的类,再提供给 Gateway 相关的模块使用。

如上图所示。自定义的匹配规则以及两类 Filter 都可以通过 Groovy 来实现。不同类型的类放在各自的文件目录下,每个目录有对应的 Monitor 检查目录下的 Groovy 文件是否发生增删改。当发现更新时,GroovyInstanceFactory 会使用 GroovyClassLoader 把对应的 Groovy 文件解析成类,然后再构造对应的实例。RoutePredicateFactoryGatewayFilterFactory 实例维护在 RouteDefinitionRouteLocator 里,当这些实例发生改变时它需要负责更新。而 GlobalFilter 实例由 FilteringWebHandler 负责更新。

服务列表配置

Gateway 在处理请求时需要根据路由配置的目标服务,将请求转发出去。因为绝大部分的业务系统都已经接入了 Eureka 服务注册中心,所以在配置路由时利用 Gateway 本身对 Eureka 服务发现的支持就能满足大部分需求。但对于一些没有接入 Eureka 的历史服务,或者是基于外部系统搭建的服务,则还是不方便使用 Eureka 服务发现。Gateway 的路由转发 URI 支持直接配置 IP 地址或者 Host,但是只能配置一个。

这些服务没有办法利用服务发现来动态管理服务实例,所以我们需要一种手动管理这些表态服务列表的方式,并且对于多实例的服务能够做负载均衡。为此我们实现了 StaticLoadBalancerClientFilter ,它的功能与 Gateway 自己的 ReactiveLoadBalancerClientFilter 类似,区别在于它不是通过 Eureka 获取服务实例的,而是通过手动配置的服务实例信息来做服务实例发现的,这些手动配置的服务实例信息保存在数据库里。

上图展示了这两个 Filter 的区别和关系。StaticLoadBalancerClientFilter 先处理请求,它如果检查到请求的转发目标服务是手动配置的服务,那么会根据数据库里配置的服务实例列表,按负责均衡规则选择一个实例的地址替换掉请求路径。否则,请求会跳过 StaticLoadBalancerClientFilter 的核心逻辑,由后续的 ReactiveLoadBalancerClientFilter 处理。

另外,StaticLoadBalancerClientFilter 使用的 ServiceInstanceListSupplier 也有一些区别。我们知道注册到 Eureka 上的服务实例能通过心跳检查实例状态,异常实例不会转发请求,但手动配置的服务缺少这部分功能。所以在 ServiceInstanceListSupplier 的实现里添加了被动健康检查的机制,一段时间内连续请求失败的实例会被屏蔽,这段时间内 ServiceInstanceListSupplier 不会返回该实例。

配置管理界面

Spring Cloud Gateway 本身没有提供 Gateway 配置相关的管理工具。上面介绍的路由、静态服务信息等数据我们都是保存在数据库里的,因此需要一种方便管理这些配置数据的方式。于是我们添加了 Gateway Admin 模块,并为之开发了 UI 管理页面,通过可视化的管理页面可以更直观方便地对这些配置进行管理。

最后改造过后 Gateway 整体的模块图如下。

5 总结与展望

Spring Cloud Gateway 是 Spring Cloud 微服务生态中的 Gateway 组件。作为 Spring Cloud Zuul 的替代,Gateway 采用了性能的更高的 Netty 作为网络层服务器。Gateway 本身提供了很多常用的 Predicates 和 Filters 实现,能满足大部分常见需求。同时 Gateway 也支持 Spring Cloud 生态下其他组件如 Eureka 等的交互,使用起来非常方便。

本文介绍了 Spring Cloud Gateway 的基本工作原理,以及我们在使用中遇到的需求和根据这些需求对 Gateway 做的开发,主要包括服务实例信息和路由的动态配置、使用 Groovy 实现能动态加载匹配规则和过滤器、Admin UI 管理界面等。

另外由于需求优先级或和我们后端业务系统的兼容性等原因仍然有一些功能未在 Gateway 上实现的,比如用户认证、灰度发布等,这些功能会在未来根据需求情况继续完善。