Spring Cloud Gateway一次請求調(diào)用源碼解析

一 前言
最近通過深入學(xué)習(xí)Spring Cloud Gateway發(fā)現(xiàn)這個框架的架構(gòu)設(shè)計非常簡單、有效,很多組件的設(shè)計都非常值得學(xué)習(xí),本文就Spring Cloud Gateway做一個簡單的介紹,以及針對一次請求Spring Cloud Gateway的處理流程做一個較為詳細的分析。
二 簡介
Spring Cloud Gateway 即Spring官方推出的一款A(yù)PI網(wǎng)關(guān),該框架包含了Spring5、SpringBoot2、Project Reactor,其中底層通信框架用的netty。Spring Cloud Gateway在推出之初的時候,Netflix公司已經(jīng)推出了類似功能的API網(wǎng)關(guān)框架ZUUL,但ZUUL有一個缺點是通信方式是阻塞的,雖然后來升級到了非阻塞式的ZUUL2,但是由于Spring Cloud Gateway已經(jīng)推出一段時間,同時自身也面臨資料少、維護性較差的因素沒有被廣泛應(yīng)用。
1 關(guān)鍵術(shù)語
在使用Spring Cloud Gateway的時候需要理解三個模塊,即
Route:
即一套路由規(guī)則,是集URI、predicate、filter等屬性的一個元數(shù)據(jù)類。
Predicate:
這是Java8函數(shù)式編程的一個方法,這里可以看做是滿足什么條件的時候,route規(guī)則進行生效。
Filter:
filter可以認為是Spring Cloud Gateway最核心的模塊,熔斷、安全、邏輯執(zhí)行、網(wǎng)絡(luò)調(diào)用都是filter來完成的,其中又細分為gateway filter和global filter,區(qū)別在于是具體一個route規(guī)則生效還是所有route規(guī)則都生效。
可以先上一段代碼來看看:
@RequestMapping("/paramTest")
?public Object paramTest(@RequestParam Map<String,Object> param) { ? ? ?return param.get("name");
?}
?@Bean
?public 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方法代表的就是一個路由規(guī)則;
path方法代表的就是一個predicate,背后的現(xiàn)實是PathRoutePredicateFactory,在這段代碼的含義即當(dāng)路徑包含/get的時候,當(dāng)前規(guī)則生效。
filters方法的意思即給當(dāng)前路由規(guī)則添加一個增加請求參數(shù)的filter,每次請求都對參數(shù)里添加 name:value 的鍵值對;
uri 方法的含義即最終路由到哪里去,這里的forward前綴會將請求交給spring mvc的DispatcherHandler進行路由,進行本機的邏輯調(diào)用,除了forward以外還可以使用http、https前綴進行http調(diào)用,lb前綴可以在配置注冊中心后進行rpc調(diào)用。

上圖是Spring Cloud Gateway官方文檔給出的一個工作原理圖,Spring Cloud Gateway 接收到請求后進行路由規(guī)則的匹配,然后交給web handler 進行處理,web handler 會執(zhí)行一系列的filter邏輯。
三 流程分析
1 接受請求
Spring Cloud Gateway的底層框架是netty,接受請求的關(guān)鍵類是ReactorHttpHandlerAdapter,做的事情很簡單,就是將netty的請求、響應(yīng)轉(zhuǎn)為http的請求、響應(yīng)并交給一個http handler執(zhí)行后面的邏輯,下圖為該類的源碼僅保留核心邏輯。
? ?public Mono< Void> apply(HttpServerRequest request, HttpServerResponse response) {
? ? ? ?NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(response.alloc());
? ? ? ?ServerHttpRequest adaptedRequest;
? ? ? ?ServerHttpResponse adaptedResponse; ? ?//轉(zhuǎn)換請求
? ? ? ?try {
? ? ? ? ? ?adaptedRequest = new ReactorServerHttpRequest(request, bufferFactory);
? ? ? ? ? ?adaptedResponse = new ReactorServerHttpResponse(response, bufferFactory);
? ? ? ?} ? ? ? ?catch (URISyntaxException ex) { ? ? ? ? ? ?if (logger.isWarnEnabled()) {
? ? ? ? ? ?...
? ? ? ?}
? ? ? ?... ? ? ? ?return this.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轉(zhuǎn)為一個exchange,這個exchange非常核心,是各個filter之間參數(shù)流轉(zhuǎn)的載體,該類包含request、response、attributes(擴展字段),接著做的事情就是web filter鏈的執(zhí)行,其中的邏輯主要是監(jiān)控。

其中WebfilterChainParoxy 又會引出新的一條filter鏈,主要是安全、日志、認證相關(guān)的邏輯,由此可見Spring Cloud Gateway的過濾器設(shè)計是層層嵌套,擴展性很強。

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

這些路由規(guī)則都能從官方文檔上找到影子。

4 核心過濾器鏈執(zhí)行
找到路由規(guī)則后下一步就是執(zhí)行了,這里的核心類是FilteringWebHandler,其中的源碼為:

做的事情很簡單:
獲取route級別的過濾器
獲取全局過濾器
兩種過濾器放在一起并根據(jù)order進行排序
執(zhí)行過濾器鏈

因為我的配置里包含了一個添加請求參數(shù)的邏輯,所以紅線箭頭處就是我配置的gateway filter名為
AddRequestParameterGatewayFilterFactory,其余全是Gloabl Filter,這些過濾器的功能主要是url解析,請求轉(zhuǎn)發(fā),響應(yīng)回寫等邏輯,因為我們這里用的是forward schema,所以請求轉(zhuǎn)發(fā)會由ForwardRoutingFilter進行執(zhí)行。
5 請求轉(zhuǎn)發(fā)
ForwardRoutingFilter做的事情也很簡單,直接復(fù)用了spring mvc的能力,將請求提交給dispatcherHandler進行處理,dispatcherHandler會根據(jù)path前綴找到需要目標處理器執(zhí)行邏輯。
public 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);
? } ? return this.dispatcherHandler.handle(exchange);
}
6 響應(yīng)回寫
響應(yīng)回寫的核心類是NettyWriteResponseFilter,但是大家可以注意到執(zhí)行器鏈中NettyWriteResponseFilter的排序是在最前面的,按道理這種響應(yīng)處理的類應(yīng)該是在靠后才對,這里的設(shè)計比較巧妙。大家可以看到chain.filter(exchange).then(),意思就是執(zhí)行到我的時候直接跳過下一個,等后面的過濾器都執(zhí)行完后才執(zhí)行這段邏輯,這種行為控制的方法值得學(xué)習(xí)。
public Mono< Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ? // NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added
? // until the WebHandler is run
? return 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 netty
? ? ?final 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));
? }));
}
四 總結(jié)
整體讀完Spring Cloud Gateway請求流程代碼后,有幾點感受:
過濾器是Spring Cloud Gateway最核心的設(shè)計,甚至于可以夸張說Spring Cloud Gateway是一個過濾器鏈執(zhí)行框架而不是一個API網(wǎng)關(guān),因為API網(wǎng)關(guān)實際的請求轉(zhuǎn)發(fā)、請求響應(yīng)回寫都是在過濾器中做的,這些是Spring Cloud Gateway感知不到的邏輯。
Spring Cloud Gateway路由規(guī)則獲取的模塊具備優(yōu)化的空間,因為是循環(huán)遍歷進行獲取的,如果每個route規(guī)則較多,predicate規(guī)則較復(fù)雜,就可以考慮用map進行優(yōu)化了,當(dāng)日route規(guī)則,predicate規(guī)則也不會很復(fù)雜,兼顧到代碼的可讀性,當(dāng)前方式也沒有什么問題。
作為API網(wǎng)關(guān)框架,內(nèi)置了非常多的過濾器,如果有過濾器的卸載功能可能會更好,用戶可用根據(jù)實際情況卸載不必要的功能,背后減少的邏輯開銷,在調(diào)用量極大的API網(wǎng)關(guān)場景,收益也會很可觀。