SpringCloudGateway一次請求呼叫原始碼解析

一  前言

最近透過深入學習Spring Cloud Gateway發現這個框架的架構設計非常簡單、有效,很多元件的設計都非常值得學習,本文就Spring Cloud Gateway做一個簡單的介紹,以及針對一次請求Spring Cloud Gateway的處理流程做一個較為詳細的分析。

二  簡介

Spring Cloud Gateway 即Spring官方推出的一款API閘道器,該框架包含了Spring5、SpringBoot2、Project Reactor,其中底層通訊框架用的netty。Spring Cloud Gateway在推出之初的時候,Netflix公司已經推出了類似功能的API閘道器框架ZUUL,但ZUUL有一個缺點是通訊方式是阻塞的,雖然後來升級到了非阻塞式的ZUUL2,但是由於Spring Cloud Gateway已經推出一段時間,同時自身也面臨資料少、維護性較差的因素沒有被廣泛應用。

1  關鍵術語

在使用Spring Cloud Gateway的時候需要理解三個模組,即

Route:

即一套路由規則,是集URI、predicate、filter等屬性的一個元資料類。

Predicate:

這是Java8函數語言程式設計的一個方法,這裡可以看做是滿足什麼條件的時候,route規則進行生效。

Filter:

filter可以認為是Spring Cloud Gateway最核心的模組,熔斷、安全、邏輯執行、網路呼叫都是filter來完成的,其中又細分為gateway filter和global filter,區別在於是具體一個route規則生效還是所有route規則都生效。
可以先上一段程式碼來看看:
@RequestMapping("/paramTest")public Object paramTest(@RequestParam Map<String,Object> param) {return param.get("name"); }@Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder) {return builder.routes() .route("path_route", r -> r.path("/get") .filters(f -> f.addRequestParameter("name", "value")) .uri("forward:///paramTest")) .build(); }
.route方法代表的就是一個路由規則;
.path方法代表的就是一個predicate,背後的現實是PathRoutePredicateFactory,在這段程式碼的含義即當路徑包含/get的時候,當前規則生效。
.filters方法的意思即給當前路由規則新增一個增加請求引數的filter,每次請求都對引數裡新增 name:value 的鍵值對;
.uri 方法的含義即最終路由到哪裡去,這裡的forward字首會將請求交給spring mvc的DispatcherHandler進行路由,進行本機的邏輯呼叫,除了forward以外還可以使用http、https字首進行http呼叫,lb字首可以在配置註冊中心後進行rpc呼叫。
上圖是Spring Cloud Gateway官方文件給出的一個工作原理圖,Spring Cloud Gateway 接收到請求後進行路由規則的匹配,然後交給web handler 進行處理,web handler 會執行一系列的filter邏輯。

三  流程分析

1  接受請求

Spring Cloud Gateway的底層框架是netty,接受請求的關鍵類是ReactorHttpHandlerAdapter,做的事情很簡單,就是將netty的請求、響應轉為http的請求、響應並交給一個http handler執行後面的邏輯,下圖為該類的原始碼僅保留核心邏輯。
@Overridepublic Mono<Void> apply(HttpServerRequest request, HttpServerResponse response){ NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(response.alloc()); ServerHttpRequest adaptedRequest; ServerHttpResponse adaptedResponse;//轉換請求try { adaptedRequest = new ReactorServerHttpRequest(request, bufferFactory); adaptedResponse = new ReactorServerHttpResponse(response, bufferFactory); }catch (URISyntaxException ex) {if (logger.isWarnEnabled()) { ... } ...returnthis.httpHandler.handle(adaptedRequest, adaptedResponse) .doOnError(ex -> logger.warn("Handling completed with error: " + ex.getMessage())) .doOnSuccess(aVoid -> logger.debug("Handling completed with success")); }

2  WEB過濾器鏈

http handler做的事情第一是將request 和 response轉為一個exchange,這個exchange非常核心,是各個filter之間引數流轉的載體,該類包含request、response、attributes(擴充套件欄位),接著做的事情就是web filter鏈的執行,其中的邏輯主要是監控。
其中WebfilterChainParoxy 又會引出新的一條filter鏈,主要是安全、日誌、認證相關的邏輯,由此可見Spring Cloud Gateway的過濾器設計是層層巢狀,擴充套件性很強。

3  尋找路由規則

核心類是RoutePredicateHandlerMapping,邏輯也非常簡單,就是把所有的route規則的predicate遍歷一遍看哪個predicate能夠命中,核心程式碼是:
returnthis.routeLocator.getRoutes() .filter(route -> { ...return route.getPredicate().test(exchange); })
因為我這裡用的是path進行過濾,所以背後的邏輯是PathRoutePredicateFactory來完成的,除了PathRoutePredicateFactory還有很多predicate規則。

這些路由規則都能從官方文件上找到影子。

4  核心過濾器鏈執行

找到路由規則後下一步就是執行了,這裡的核心類是FilteringWebHandler,其中的原始碼為:
@Overridepublic 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); logger.debug("Sorted gatewayFilterFactories: "+ combined);returnnew DefaultGatewayFilterChain(combined).filter(exchange);}
做的事情很簡單:
  1. 獲取route級別的過濾器
  2. 獲取全域性過濾器
  3. 兩種過濾器放在一起並根據order進行排序
  4. 執行過濾器鏈

因為我的配置裡包含了一個新增請求引數的邏輯,所以紅線箭頭處就是我配置的gateway filter名為 AddRequestParameterGatewayFilterFactory,其餘全是Gloabl Filter,這些過濾器的功能主要是url解析,請求轉發,響應回寫等邏輯,因為我們這裡用的是forward schema,所以請求轉發會由ForwardRoutingFilter進行執行。

5  請求轉發

ForwardRoutingFilter做的事情也很簡單,直接複用了spring mvc的能力,將請求提交給dispatcherHandler進行處理,dispatcherHandler會根據path字首找到需要目標處理器執行邏輯。
@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); String scheme = requestUrl.getScheme();if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {return chain.filter(exchange); } setAlreadyRouted(exchange);//TODO: translate url?if (log.isTraceEnabled()) { log.trace("Forwarding to URI: "+requestUrl); }returnthis.dispatcherHandler.handle(exchange);}

6  響應回寫

響應回寫的核心類是NettyWriteResponseFilter,但是大家可以注意到執行器鏈中NettyWriteResponseFilter的排序是在最前面的,按道理這種響應處理的類應該是在靠後才對,這裡的設計比較巧妙。大家可以看到chain.filter(exchange).then(),意思就是執行到我的時候直接跳過下一個,等後面的過濾器都執行完後才執行這段邏輯,這種行為控制的方法值得學習。
@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added// until the WebHandler is runreturn chain.filter(exchange).then(Mono.defer(() -> { HttpClientResponse clientResponse = exchange.getAttribute(CLIENT_RESPONSE_ATTR);if (clientResponse == null) {return Mono.empty(); } log.trace("NettyWriteResponseFilter start"); ServerHttpResponse response = exchange.getResponse(); NettyDataBufferFactory factory = (NettyDataBufferFactory) response.bufferFactory();//TODO: what if it's not nettyfinal Flux<NettyDataBuffer> body = clientResponse.receive() .retain() //TODO: needed? .map(factory::wrap); MediaType contentType = response.getHeaders().getContentType();return (isStreamingMediaType(contentType) ? response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body)); }));}

四  總結

整體讀完Spring Cloud Gateway請求流程程式碼後,有幾點感受:
  1. 過濾器是Spring Cloud Gateway最核心的設計,甚至於可以誇張說Spring Cloud Gateway是一個過濾器鏈執行框架而不是一個API閘道器,因為API閘道器實際的請求轉發、請求響應回寫都是在過濾器中做的,這些是Spring Cloud Gateway感知不到的邏輯。
  2. Spring Cloud Gateway路由規則獲取的模組具備最佳化的空間,因為是迴圈遍歷進行獲取的,如果每個route規則較多,predicate規則較複雜,就可以考慮用map進行優化了,當日route規則,predicate規則也不會很複雜,兼顧到程式碼的可讀性,當前方式也沒有什麼問題。
  3. 作為API閘道器框架,內建了非常多的過濾器,如果有過濾器的解除安裝功能可能會更好,使用者可用根據實際情況解除安裝不必要的功能,背後減少的邏輯開銷,在呼叫量極大的API閘道器場景,收益也會很可觀。

乘風者之星來啦!發文享阿里內推機會和50W流量曝光!

點選閱讀原文檢視詳情!


相關文章