共计 3518 个字符,预计需要花费 9 分钟才能阅读完成。
本文基于 Spring Cloud Gateway 2.1.1.RELEASE。
在讲 SCG 的 Filter 的排序问题之前得先比较一下 Spring Cloud Gateway 在对待 Filter 的方面与 Zuul2 有着哪些不同。
Filter 的 Scope
- SCG 采用的是 Global Filter 和 Route Filter 相结合的方式
- Zuul2 则都是 Global Filter
SCG 所谓 Route Filter 就是像下面这样的:
spring:
cloud:
gateway:
routes:
- id: tomcat_route
uri: http://tomcat:8080
predicates:
- Path=/tomcat/docs
filters:
- StripPrefix=1
- RemoveRequestHeader=X-Request-Foo
上面的 StripPrefix
和RemoveRequestHeader
就是 Route Filter,而 SCG 的 Global Filter 则是隐式的,无需显式配置,它们会在请求过来的时候被 SCG 调用。
也就是说你可以配置不同的 Route,然后为每个 Route 配置不同的 Route Filter,这一切都是在配置阶段就决定下来的。
而 Zuul2 则都是 Global Filter,因此你得运行时在每个 Filter 内部自己决定是否要干活,除此之外,发送到 Origin(被代理的服务)的 url 也得你自己设置,下面是一个例子(来自 Zuul2 Sample):
public class Routes extends HttpInboundSyncFilter {
@Override
public boolean shouldFilter(HttpRequestMessage httpRequestMessage) {
// ...
return true;
}
@Override
public HttpRequestMessage apply(HttpRequestMessage request) {
// ...
// Route healthchecks to the healthcheck endpoint.;
context.setEndpoint(ZuulEndPointRunner.PROXY_ENDPOINT_FILTER_NAME);
context.setRouteVIP("tomcat");
return request;
}
}
Filter 的角色
- 在 SCG 概念中只有一种 Filter(撇开 Global 和 Route 的区别),它用代码来区分 Pre Filter、Post Filter。在文档中还提到了 Routing Filter,其实也是 Pre Filter。
- Zuul2 在代码中显示得提供了 InboundFilter(负责进来的请求)、OutboundFilter(负责出去的响应)、ProxyEndpoint(负责请求到 Origin,串起 Inbound 和 Outbound)。
下面是 SCG 的 Pre Filter(裁剪自官方例子 12.2 Writing Custom GatewayFilter Factories):
public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Config config) {return (exchange, chain) -> {
// business logic
return chain.filter();};
}
}
Post Filter 的例子:
public class PostGatewayFilterFactory extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Config config) {return (exchange, chain) -> {return chain.filter(exchange).then(/* business logic */);
};
}
}
在 Zuul2 里,你则得分别实现 HttpInboundSyncFilter
和HttpOutboundSyncFilter
,ProxyEndpoint
不需要你自己实现。
SCG Filter 的问题
SCG 的优点很明显,它做了 Zuul2 不做的事情:
- 替你决定进来的请求转发到哪个 Origin。在 Zuul2 里这个交给你自己来实现。
- 在配置上就决定了这个 Route 会应用哪些 Filter。在 Zuul2 里这个交给你自己来判断。
但是随着对 SCG 的深入了解,发现了关于 Filter 的执行顺序存在一些坑,如果不了解清楚会容易出错。
Filter 的排序
前面讲了,SCG 在执行过程中 Global Filter 和 Route Filter 是一起执行的,那么它们的 order 是怎样的?
先来看看 Global Filter,你可以访问/actuator/gateway/globalfilters
(见文档)得到 Global Filter 的排序:
那么如果你写了一个自定义 Global Filter,那么它的 order 是什么呢?这个要看情况:
- 如果你的自定义 Global Filter 实现了
Ordered
接口或者写了@Order
注解,那么它的 order 就是它自己设定的值 - 否则,它就没有 order
关于这点可以看 FilteringWebHandler.java 的源代码。
再来看看 Route Filter,这也分两种情况:
- 如果 RouteFilter 实现了
Ordered
接口或者写了@Order
注解,那么它的 order 就是它自己设定的值。 - 否则,它的 order 则是从 1 开始,按照 Route 中定义的顺序依次排序。
关于这点可以看 RouteDefinitionRouteLocator.java 的源代码。
最后 SCG 把它们两个结合起来,做一个排序,对于没有 order 的 Filter,它的 order 则默认为Ordered.LOWEST_PRECEDENCE
。关于这点可以看 FilteringWebHandler.java 的源代码。
用一张图做总结:
Filter 的执行顺序
先看 SCG 文档 3. How It Works 中的这张图:
这张图大概告诉你了 SCG 的调用过程,可以看到经过了一堆 Filters,但是并没有告诉你 Filter 的执行顺序。然后在 SCG 的 6.1 Combined Global Filter and GatewayFilter Ordering 提到了:
As Spring Cloud Gateway distinguishes between “pre” and “post” phases for filter logic execution (see: How It Works), the filter with the highest precedence will be the first in the “pre”-phase and the last in the “post”-phase.
也就是说意思如果这个 Filter 是 Pre Filter,那么执行顺序和排序顺序相同,如果这个 Filter 是 Post Filter 则执行顺序和排序顺序相反。我整理了一下 SCG 自带 GlobalFilter 的执行顺序:
可以看到 GatewayMetricsFilter 既是 Pre Filter 也是 Post Filter。
总结
-
执行某个 Route 的时候,SCG 会将 Global Filter 和 Route Filter 结合起来并排序:
- 没有给 order 的 Global Filter 则保持 order 为 null 去排序
- 没有给 order 的 Route Filter 的 order 则从 1 开始,根据 Route 中定义的顺序给值
- 排序逻辑见 AnnotationAwareOrderComparator
- 对于 Pre Filter,执行顺序同排序顺序
- 对于 Post Filter,执行顺序与排序顺序相反
-
如果你要自定义 Global Filter,那么一般来说:
- 自定义的 Global Pre Filter 要在 Routing Filter 之前执行
- 自定义的 Global Post Filter 要在 Routing Filter 之后执行或者 NettyWriteResponseFilter 之后执行
-
如果你要自定义 Route Filter,那么一般来说:
- 自定义 Route Pre Filter 要在
ForwardPathFilter
和RouteToRequestUrlFilter
之间,而且不需要实现Ordered
接口或添加@Order
注解 - 自定义的 Route Post Filter 比较少见,放在 Routing Filter 或者 NettyWriteResponseFilter 之后执行
- 自定义 Route Pre Filter 要在