SCG GlobalFilter

234 阅读7分钟

         上一篇的 demo 比较详细地展示了 SCG 内置的功能,包括各种 Predicate 和 Gateway Filter 的配置使用,以及如何去定制自己的功能。看上去似乎比较完整,其实不然,对 SCG 正常运行来说还有不可或缺的一部分: GlobalFilter,一种不需要特别配置就自动对全局所有路由都生效的Filter,正因为这些全局Filter的默默执行,才能让我们的网关自动的解析path、调用upstream服务、收集metrics、把upstream的结果写入downstream的response,在有负载均衡客户端(ribbon)时自动执行 LB 策略等等。本文就来为大家揭开SCG GlobalFilter 的神秘面纱,剖析其运行机制,以及我们可以通过它可以定制什么样的能力。

          SCG 中有两个Fitler接口:GatewayFilter 和 GlobalFilter。GatewayFilter 是由各种 xxxGatewayFilterFactory 创建的,上一篇demo中我们已经配置和使用了十几个这样的 Filter。GlobalFilter 是 SCG 内置的全局 Filter,只要在应用启动时实例化了对应的 Bean,就会自动对所有Route都起作用。“SCG如何接管webflux请求”一文曾经分析过,当一个路由被命中之后( 即 RoutePredicateHanderMapping lookup 到一个 route),SCG的webHandler( 即FilteringWebHandler )开始创建并执行Filter链。代码:

@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);
	// TODO: needed or cached?
	AnnotationAwareOrderComparator.sort(combined);

	if (logger.isDebugEnabled()) {
		logger.debug("Sorted gatewayFilterFactories: " + combined);
	}

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

        代码中清晰的看到 combined 列表是 GlobalFilters 加上本次请求命中的 Route 上配置的所有 GatewayFitlers, 然后根据order值排序,之后依次执行。这样说也许还不够直观,那么我们承接上一篇文章中的的demo,继续运行application-full.yml,调用触发 f-route, 这是一个没有负载均衡客户端的场景,他的filter 链是这样的:

画个清晰点的类图:

          Filter从小到大排序,先执行 Order <= 0 所有 GlobalFilter, 然后执行 0 < Order < 10000 的 GatewayFilter,最后执行 Order >= 10000 的 GlobalFilter。这里也隐含了这样一条规律:所有 GatewayFilter 的 order 值为我们在yml里配置的顺序,从1开始递增,范围在0 到10000之间(当然这是因为我们也不可能为一个路由配置大于1w个Filter)。暂且不论中间0~10000之间我们配置的filter,我们来剖析下 <=0 和 >= 10000的Global Filters。

         在剖析之前,还需要对SCG 的 Filter 做个补充说明,了解Netflix Zuul网关的朋友都知道,filter有前置和后置之分,SCG 的filter在类型上虽然没有区分前置和后置,但是实际执行过程中是有前置和后置区别的,具体说,就是在filter方法中,调用过滤链之前的代码属于前置执行部分,调用过滤链后的代码属于后置执行部分:

前置执行代码

chain.filter(exchange) 

后置执行代码

order越小的filter先执行,指的是执行其 ”前置执行代码“,而”后置执行代码“执行顺序和order相反,越大的order反而越先执行。

下面我们根据order顺序来逐个分析。

1. RemoveCachedBodyFilter

(exchange).doFinally(....);

         这是一个典型的后置过滤器,没有前置代码,调用完chain.filter之后执行doFinally的后置代码,其逻辑是remove掉上下文缓存的request body并且回收DataBuffer。因为他是order最小的最先执行的Filter,所以其后置代码反而是最后执行,这也是符合逻辑的,在所有filter逻辑执行完之后来清理缓存。

2. AdaptCachedBodyGlobalFilter

...//前置代码逻辑
chain.filter(mutated new exchange)

         这也是一个典型的前置过滤器,他会先执行前置代码,把缓存里的request body读取出来重新mutate一个exchange上下文,然后对这个新的exchange执行filter链。这里解释一下该filter的使用场景:我们知道对流是不能重复读取的,当我们因为逻辑需要以及在predicate阶段读取过request body了,读取者必然的需要把读取的body缓存在上下文里,因为后续还要使用。针对这个场景,该 filter 会把缓存中的 body 取出来然后 mutate 一个新的 exchange ,基于这个新的exchange执行后续过滤器链。这样,后续调用upstream的时才能正确发送request body。

3. NettyWriteResponseFilter

chain.filter(exchange)  
.doOnError(...)  
.then(...)  
.doOnCancel(...)

         这显然是一个后置过滤器,执行完filter链之后去执行后置代码,主要是把 NettyRoutingFilter 执行并获取到的 upstream 的 response 数据取出来,写到 downstream 的 response 里。很好理解,这个filter执行逻辑之前,过滤器链必须执行完 NettyRoutingFilter 的逻辑并且已经拿到了 upstream 的 response 数据才行。

4. ForwardPathFilter

...//前置代码逻辑
chain.filter(mutated new exchange)

       这显然是一个前置过滤器,先执行前置代码,逻辑就是对于一个 forward 类型路由,该 filter 会将客户端的 http 请求转换为一个 forward 类型的 path 并 mutate 一个新的 exchange。

5. GatewayMetricsFilter

Sample sample = Timer.start(meterRegistry);
return chain.filter(exchange)
.doOnSuccess(aVoid -> endTimerRespectingCommit(exchange, sample))    
.doOnError(throwable -> endTimerRespectingCommit(exchange, sample));

前后置代码都有,前置代码是启动一个 Timer.Sample 记录开始,后置代码( doOnSuccess 或 doOnError )逻辑是结束Timer.Sample并且记录 metrics( 依赖 io.micrometer)。补充一点,收集 metrics 是在 upstream 调用完成之后,但是返回结果写入 downstream 之前。

-- 到此,<=0 的global filter都执行,轮到配置在route上 GatewayFilter 执行。

6. RouteToRequestUrlFilter

...//前置代码逻辑
chain.filter(exchange)

显然是一个前置过滤器,其前置代码逻辑主要是用来计算request url,他会route配置uri的schema、host、port去覆盖当前请求uri中对应的部分,然后得到一个 mergedUrl 设置到 exchange 属性 GATEWAY_REQUEST_URL_ATTR 中。

7. NoLoadBalancerClientFilter

...//前置代码逻辑
chain.filter(exchange)

也是一个前置过滤器,主要是确保判断 route 的配置中不是 lb 类型的,否则就抛出异常.

8. WebsocketRoutingFilter

        这应该算是一个前置过滤器,当 GATEWAY_REQUEST_URL_ATTR 属性值是 websocket 类型时,该 filter 会请求交给 webSocketService 处理请求,GATEWAY_REQUEST_URL_ATTR 这个属性值其实就是之前 RouteToRequestUrlFilter merge过的 requestUrl, ws 或者 wss 配置在 route 的 uri 中,代表了该 route 路由的 upstream 是需要用 websocket 发起请求的。

9. NettyRoutingFilter

...//前置代码逻辑
chain.filter(exchange)

一个前置过滤器,代码稍微复杂一点,神雕会在下一篇详细分析该filter。本文这里只需要知道,该 filter 是 SCG 默认的像 http(s) 类型的 upstream 发起请求的过滤器。其前置代码逻辑:构建http 请求头,解析请求 url(包括path variables),通过 netty http client 向 upstream 发起请求,并且把 upstream 返回的设置到 exchange 的 CLIENT_RESPONSE_ATTR 属性中。当然他还会处理 upstream 返回结果的 headers,并且结合 Route 配置的 header设置,一起设置到返回给 downstream 的 response 中,以及设置 response 的 status。

10. ForwardRoutingFilter

       前置过滤器,主要是针对 forward 类型的请求,该过滤器会把请求交还给 webflux 的 DispatcherHandler 处理,也就是直接由网关的 api 处理,而不是向 upstream 发起请求。如果不是 forward 类型的,则直接继续 chain.filter(exchange)

所以,总结一下,这些 GlobalFilter 的执行顺序是这样的:

代码执行逻辑顺序从编号1到10.

另外,在有LB客户端场景中,SCG 还提供了:

  • LoadBalancerServiceInstanceCookieFilter(order = 10151)

  • ReactiveLoadBalancerClientFilter(order = 10150)

以及,和 NettyRoutingFilter/NettyWriteResponseFilter 类似功能的一对 Filter

  • WebClientHttpRoutingFilter(order = Integer.MAX_VALUE)

  • WebClientWriteResponseFilter (order = -1)

以上就是 SCG 提供的 GlobalFilter,功能强大,非常有学习和借鉴的价值。

        实际项目中,我们往往要针对不同的 upstream 实现私有的 protocol 能力,比如国内调用比较多dubbo,或者说要集成公司内部自己实现的api protocal(很多公司都有,神雕的公司就有自己统一实现的xxx api),此时我们了解各个内置Filter执行的时机就非常必要,同时 NettyRoutingFilter 或者 WebClientHttpRoutingFilter 也是很好的借鉴案例,无论是其实现思路,还是代码风格。再比如,实际项目中我们也会有记录自己的 metrics 的需求,无论是因为对不同业务指标的需求、还是对不同 metrics 组件的支持,此时借鉴 GatewayMetricsFilter 的实现方式,在 GatewayMetricsFilter 前置执行之前开始,在upstream 结果写入到 downstream 的response 之前结束并记录。

这将会在后续的文章继续深入分享。