
一 前言
最近透過深入學習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規則都生效。
可以先上一段程式碼來看看:
public Object paramTest( Map<String,Object> param) {
return param.get("name");
}
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方法代表的就是一個路由規則;
.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執行後面的邏輯,下圖為該類的原始碼僅保留核心邏輯。
public 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,其中的原始碼為:
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);
logger.debug("Sorted gatewayFilterFactories: "+ combined);
returnnew DefaultGatewayFilterChain(combined).filter(exchange);
}
做的事情很簡單:
-
獲取route級別的過濾器 -
獲取全域性過濾器 -
兩種過濾器放在一起並根據order進行排序 -
執行過濾器鏈

因為我的配置裡包含了一個新增請求引數的邏輯,所以紅線箭頭處就是我配置的gateway filter名為 AddRequestParameterGatewayFilterFactory,其餘全是Gloabl Filter,這些過濾器的功能主要是url解析,請求轉發,響應回寫等邏輯,因為我們這裡用的是forward schema,所以請求轉發會由ForwardRoutingFilter進行執行。
5 請求轉發
ForwardRoutingFilter做的事情也很簡單,直接複用了spring mvc的能力,將請求提交給dispatcherHandler進行處理,dispatcherHandler會根據path字首找到需要目標處理器執行邏輯。
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);
}
returnthis.dispatcherHandler.handle(exchange);
}
6 響應回寫
響應回寫的核心類是NettyWriteResponseFilter,但是大家可以注意到執行器鏈中NettyWriteResponseFilter的排序是在最前面的,按道理這種響應處理的類應該是在靠後才對,這裡的設計比較巧妙。大家可以看到chain.filter(exchange).then(),意思就是執行到我的時候直接跳過下一個,等後面的過濾器都執行完後才執行這段邏輯,這種行為控制的方法值得學習。
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));
}));
}
四 總結
整體讀完Spring Cloud Gateway請求流程程式碼後,有幾點感受:
-
過濾器是Spring Cloud Gateway最核心的設計,甚至於可以誇張說Spring Cloud Gateway是一個過濾器鏈執行框架而不是一個API閘道器,因為API閘道器實際的請求轉發、請求響應回寫都是在過濾器中做的,這些是Spring Cloud Gateway感知不到的邏輯。 -
Spring Cloud Gateway路由規則獲取的模組具備最佳化的空間,因為是迴圈遍歷進行獲取的,如果每個route規則較多,predicate規則較複雜,就可以考慮用map進行優化了,當日route規則,predicate規則也不會很複雜,兼顧到程式碼的可讀性,當前方式也沒有什麼問題。 -
作為API閘道器框架,內建了非常多的過濾器,如果有過濾器的解除安裝功能可能會更好,使用者可用根據實際情況解除安裝不必要的功能,背後減少的邏輯開銷,在呼叫量極大的API閘道器場景,收益也會很可觀。
乘風者之星來啦!發文享阿里內推機會和50W流量曝光!
點選閱讀原文檢視詳情!

關鍵詞
功能
方法
API閘道器
就是
程式碼