SpringCloud升級之路2020.0.x版-41.

本系列代碼地址:??https://github.com/JoJoTec/spring-cloud-parent??

我們繼續分析上一節提到的 ??WebHandler??。加入 Spring Cloud Sleuth 以及 Prometheus 相關依賴之后, Spring Cloud Gateway 的處理流程如下所示:

SpringCloud升級之路2020.0.x版-41.

Spring Cloud Gateway 入口 -> WebFlux 的 DefaultWebFilterChain

Spring Cloud Gateway 是基于 Spring WebFlux 開發的異步響應式網關,異步響應式代碼比較難以理解和閱讀,我這里給大家分享一種方法去理解,通過這個流程來理解 Spring Cloud Gateway 的工作流程以及底層原理。其實可以理解為,上圖這個流程,就是拼出來一個完整的 Mono(或者 Flux)流,最后 subscribe 執行。

當收到一個請求的時候,會經過 ??org.springframework.web.server.handler.DefaultWebFilterChain??,這是 WebFilter 的調用鏈,這個鏈路包括三個 WebFilter:

  • ??org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter??:添加 Prometheus 相關依賴之后,會有這個 MetricsWebFilter,用于記錄請求處理耗時,采集相關指標。
  • ??org.springframework.cloud.sleuth.instrument.web.TraceWebFilter??:添加 Spring Cloud Sleuth 相關依賴之后,會有這個 TraceWebFilter。
  • ??org.springframework.cloud.gateway.handler.predicate.WeightCalculatorWebFilter??:Spring Cloud Gateway 路由權重相關配置功能相關實現類,這個我們這里不關心。

在這個 ??DefaultWebFilterChain?? 會形成這樣一個 Mono,我們依次將他們標記出來,首先是入口代碼 ??org.springframework.web.server.handler.DefaultWebFilterChain#filter??:

public Mono filter(ServerWebExchange exchange) {
return Mono.defer(() ->
// this.currentFilter != null 代表 WebFilter 鏈還沒有結束
// this.chain != null 代表 WebFilter 鏈不為空
this.currentFilter != null && this.chain != null ?
//在 WebFilter 鏈沒有結束的情況下,調用 WebFilter
invokeFilter(this.currentFilter, this.chain, exchange) :
//在 WebFilter 結束的情況下,調用 handler
this.handler.handle(exchange));
}

對于我們這里的 WebFilter 鏈的第一個 ??MetricsWebFilter??,假設啟用了對應的采集統計的話,這時候生成的 Mono 就是:

return Mono.defer(() ->
chain.filter(exchange).transformDeferred((call) -> {
long start = System.nanoTime();
return call
//成功時,記錄響應時間
.doOnSuccess((done) -> MetricsWebFilter.this.onSuccess(exchange, start))
//失敗時,記錄響應時間和異常
.doOnError((cause) -> MetricsWebFilter.this.onError(exchange, start, cause));
});
);

這里為了方便,我們對代碼做了簡化,由于我們要將整個鏈路的所有 Mono 和 Flux 拼接在一起行程完整鏈路,所以原本是 ??MetricsWebFilter??中的 ??onSuccess(exchange, start)??方法,被改成了 ??MetricsWebFilter.this.onSuccess(exchange, start)?? 這種偽代碼。

接著,根據??DefaultWebFilterChain?? 的源碼分析,??chain.filter(exchange)?? 會繼續 WebFilter 鏈路,到達下一個 WebFilter,即 ??TraceWebFilter??。經過 ??TraceWebFilter??,Mono 就會變成:

return Mono.defer(() ->
new MonoWebFilterTrace(source, chain.filter(exchange), TraceWebFilter.this.isTracePresent(), TraceWebFilter.this, TraceWebFilter.this.spanFromContextRetriever()).transformDeferred((call) -> {
//MetricsWebFilter 相關的處理,在前面的代碼中給出了,這里省略
});
);

可以看出,在 ??TraceWebFilter?? 中,整個內部 Mono (??chain.filter(exchange)?? 后續的結果)都被封裝成了一個 ??MonoWebFilterTrace??,這也是保持鏈路追蹤信息的關鍵實現。

繼續 WebFilter 鏈路,經過最后一個 WebFilter ??WeightCalculatorWebFilter??; 這個 WebFilter 我們不關心,里面對路由權重做了一些計算操作,我們這里直接忽略即可。這樣我們就走完了所有 WebFilter 鏈路,來到了最后的調用 ??DefaultWebFilterChain.this.handler??,這個 handler 就是 ??org.springframework.web.reactive.DispatcherHandler??。在 DispatcherHandler 中,我們會計算出路由并發送請求到符合條件的 GatewayFilter。經過 DispatcherHandler,Mono 會變成:

return Mono.defer(() ->
new MonoWebFilterTrace(source,
Flux.fromIterable(DispatcherHandler.this.handlerMappings) //讀取所有的 handlerMappings
.concatMap(mapping -> mapping.getHandler(exchange)) //按順序調用所有的 handlerMappings 的 getHandler 方法,如果有對應的 Handler 會返回,否則返回 Mono.empty();
.next() //找到第一個返回不是 Mono.empty() 的 Handler
.switchIfEmpty(DispatcherHandler.this.createNotFoundError()) //如果沒有返回不為 Mono.empty() 的 handlerMapping,則直接返回 404
.flatMap(handler -> DispatcherHandler.this.invokeHandler(exchange, handler)) //調用對應的 Handler
.flatMap(result -> DispatcherHandler.this.handleResult(exchange, result)), //處理結果
TraceWebFilter.this.isTracePresent(), TraceWebFilter.this, TraceWebFilter.this.spanFromContextRetriever()).transformDeferred((call) -> {
//MetricsWebFilter 相關的處理,在前面的代碼中給出了,這里省略
});
);

handlerMappings 包括:

  • ??org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndPointHandlerMapping??:由于我們項目中添加了 Actuator 相關依賴,所以這里有這個 HandlerMapping。Actuator 相關路徑映射,不是我們這里關心的。但是可以看出,Actuator 相關路徑優先于 Spring Cloud Gateway 配置路由
  • ??org.springframework.boot.actuate.endpoint.web.reactive.ControllerEndpointHandlerMapping??:由于我們項目中添加了 Actuator 相關依賴,所以這里有這個 HandlerMapping。使用 @ControllerEndpoint 或者 @RestControllerEndpoint 注解標注的 Actuator 相關路徑映射,不是我們這里關心的。
  • ??org.springframework.web.reactive.function.server.support.RouterFunctionMapping??:在 Spring-WebFlux 中,你可以定義很多不同的 RouterFunction 來控制路徑路由,但這也不是我們這里關心的。但是可以看出,自定義的 RouterFunction 會優先于 Spring Cloud Gateway 配置路由
  • ??org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping??:針對 @RequestMapping 注解的路徑的 HandlerMapping,不是我們這里關心的。但是可以看出,如果你在 Spring Cloud Gateway 中指定 RequestMapping 路徑,會優先于 Spring Cloud Gateway 配置路由
  • ??org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping??:這個是 Spring Cloud Gateway 的 HandlerMapping,會讀取 Spring Cloud Gateway 配置并生成路由。這個是我們這里要詳細分析的。

其實這些 handlerMappings,我們這里肯定走的是 ??RoutePredicateHandlerMapping?? 的相關邏輯,所以我們的 Mono 又可以簡化成:

return Mono.defer(() ->
new MonoWebFilterTrace(source,
RoutePredicateHandlerMapping.this.getHandler(exchange)
.switchIfEmpty(DispatcherHandler.this.createNotFoundError()) //如果沒有返回不為 Mono.empty() 的 handlerMapping,則直接返回 404
.flatMap(handler -> DispatcherHandler.this.invokeHandler(exchange, handler)) //調用對應的 Handler
.flatMap(result -> DispatcherHandler.this.handleResult(exchange, result)), //處理結果
TraceWebFilter.this.isTracePresent(), TraceWebFilter.this, TraceWebFilter.this.spanFromContextRetriever()).transformDeferred((call) -> {
//MetricsWebFilter 相關的處理,在前面的代碼中給出了,這里省略
});
);

我們來看 ??RoutePredicateHandlerMapping??,首先這些 handlerMapping 都是繼承了抽象類 ??org.springframework.web.reactive.handler.AbstractHandlerMapping??, 前面我們拼接的 Mono 里面的 getHandler 的實現其實就在這個抽象類中:

public Mono getHandler(ServerWebExchange exchange) {
//調用抽象方法 getHandlerInternal 獲取真正的 Handler
return getHandlerInternal(exchange).map(handler -> {
//這里針對 handler 做一些日志記錄
if (logger.isDebugEnabled()) {
logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
}
// 跨域處理
ServerHttpRequest request = exchange.getRequest();
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ?
this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
if (config != null) {
config.validateAllowCredentials();
}
if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
return NO_OP_HANDLER;
}
}
return handler;
});
}

可以看出,其實核心就是每個實現類的 ??getHandlerInternal(exchange)??方法,所以在我們拼接的 Mono 中,我們會忽略抽象類中的針對 handler 之后的 map 處理。

return Mono.defer(() ->
new MonoWebFilterTrace(source,
RoutePredicateHandlerMapping.this.getHandlerInternal(exchange)
.switchIfEmpty(DispatcherHandler.this.createNotFoundError()) //如果沒有返回不為 Mono.empty() 的 handlerMapping,則直接返回 404
.flatMap(handler -> DispatcherHandler.this.invokeHandler(exchange, handler)) //調用對應的 Handler
.flatMap(result -> DispatcherHandler.this.handleResult(exchange, result)), //處理結果
TraceWebFilter.this.isTracePresent(), TraceWebFilter.this, TraceWebFilter.this.spanFromContextRetriever()).transformDeferred((call) -> {
//MetricsWebFilter 相關的處理,在前面的代碼中給出了,這里省略
});
);

接下來經過 ??RoutePredicateHandlerMapping?? 的 ??getHandlerInternal(exchange)?? 方法,我們的 Mono 變成了:

return Mono.defer(() ->
new MonoWebFilterTrace(source,
RoutePredicateHandlerMapping.this.lookupRoute(exchange) //根據請求尋找路由
.flatMap((Function>) r -> {
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r); //將路由放入 Attributes 中,后面我們還會用到
return Mono.just(RoutePredicateHandlerMapping.this.webHandler); //返回 RoutePredicateHandlerMapping 的 FilteringWebHandler
}).switchIfEmpty( //如果為 Mono.empty(),也就是沒找到路由
Mono.empty() //返回 Mono.empty()
.then(Mono.fromRunnable(() -> { //返回 Mono.empty() 之后,記錄日志
if (logger.isTraceEnabled()) {
logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
}
})))
.switchIfEmpty(DispatcherHandler.this.createNotFoundError()) //如果沒有返回不為 Mono.empty() 的 handlerMapping,則直接返回 404
.flatMap(handler -> DispatcherHandler.this.invokeHandler(exchange, handler)) //調用對應的 Handler
.flatMap(result -> DispatcherHandler.this.handleResult(exchange, result)), //處理結果
TraceWebFilter.this.isTracePresent(), TraceWebFilter.this, TraceWebFilter.this.spanFromContextRetriever()).transformDeferred((call) -> {
//MetricsWebFilter 相關的處理,在前面的代碼中給出了,這里省略
});
);

??RoutePredicateHandlerMapping.this.lookupRoute(exchange)?? 根據請求尋找路由,這個我們就不詳細展開了,其實就是根據你的 Spring Cloud Gateway 配置,找到合適的路由。接下來我們來看調用對應的 Handler,即 FilteringWebHandler。??DispatcherHandler.this.invokeHandler(exchange, handler)?? 我們這里也不詳細展開,我們知道其實就是調用 Handler 的 handle 方法,即 FilteringWebHandler 的 handle 方法,所以 我們的 Mono 變成了:

return Mono.defer(() ->
new MonoWebFilterTrace(source,
RoutePredicateHandlerMapping.this.lookupRoute(exchange) //根據請求尋找路由
.flatMap((Function>) r -> {
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r); //將路由放入 Attributes 中,后面我們還會用到
return Mono.just(RoutePredicateHandlerMapping.this.webHandler); //返回 RoutePredicateHandlerMapping 的 FilteringWebHandler
}).switchIfEmpty( //如果為 Mono.empty(),也就是沒找到路由
Mono.empty()
.then(Mono.fromRunnable(() -> { //返回 Mono.empty() 之后,記錄日志
if (logger.isTraceEnabled()) {
logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
}
})))
.switchIfEmpty(DispatcherHandler.this.createNotFoundError()) //如果沒有返回不為 Mono.empty() 的 handlerMapping,則直接返回 404
.then(FilteringWebHandler.this.handle(exchange).then(Mono.empty())) //調用對應的 Handler
.flatMap(result -> DispatcherHandler.this.handleResult(exchange, result)), //處理結果
TraceWebFilter.this.isTracePresent(), TraceWebFilter.this, TraceWebFilter.this.spanFromContextRetriever()).transformDeferred((call) -> {
//MetricsWebFilter 相關的處理,在前面的代碼中給出了,這里省略
});
);

由于調用對應的 Handler,最后返回的是 ??Mono.empty()??,所以后面的 flatMap 其實不會執行了。所以我們可以將最后的處理結果這一步去掉。所以我們的 Mono 就變成了:

return Mono.defer(() ->
new MonoWebFilterTrace(source,
RoutePredicateHandlerMapping.this.lookupRoute(exchange) //根據請求尋找路由
.flatMap((Function>) r -> {
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r); //將路由放入 Attributes 中,后面我們還會用到
return Mono.just(RoutePredicateHandlerMapping.this.webHandler); //返回 RoutePredicateHandlerMapping 的 FilteringWebHandler
}).switchIfEmpty( //如果為 Mono.empty(),也就是沒找到路由
Mono.empty()
.then(Mono.fromRunnable(() -> { //返回 Mono.empty() 之后,記錄日志
if (logger.isTraceEnabled()) {
logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
}
})))
.switchIfEmpty(DispatcherHandler.this.createNotFoundError()) //如果沒有返回不為 Mono.empty() 的 handlerMapping,則直接返回 404
.then(FilteringWebHandler.this.handle(exchange).then(Mono.empty()))), //調用對應的 Handler
TraceWebFilter.this.isTracePresent(), TraceWebFilter.this, TraceWebFilter.this.spanFromContextRetriever()).transformDeferred((call) -> {
//MetricsWebFilter 相關的處理,在前面的代碼中給出了,這里省略
});
);

??FilteringWebHandler.this.handle(exchange)?? 其實就是從 Attributes 中取出路由,從路由中取出對應的 GatewayFilters,與全局 GatewayFilters 放到同一個 List 中,并按照這些 GatewayFilter 的順序排序(可以通過實現 ??org.springframework.core.Ordered?? 接口來制定順序),然后生成 ??DefaultGatewayFilterChain?? 即 GatewayFilter 鏈路。對應的源碼是:

public Mono handle(ServerWebExchange exchange) {
//從 Attributes 中取出路由,從路由中取出對應的 GatewayFilters
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
List gatewayFilters = route.getFilters();
//與全局 GatewayFilters 放到同一個 List 中
List combined = new ArrayList<>(this.globalFilters);
combined.addAll(gatewayFilters);
//按照這些 GatewayFilter 的順序排序(可以通過實現 `org.springframework.core.Ordered` 接口來制定順序)
AnnotationAwareOrderComparator.sort(combined);

if (logger.isDebugEnabled()) {
logger.debug("Sorted gatewayFilterFactories: " + combined);
}
//生成調用鏈
return new DefaultGatewayFilterChain(combined).filter(exchange);
}

這個 GatewayFilter 調用鏈和 WebFilter 調用鏈類似,參考 ??DefaultGatewayFilterChain?? 的源碼:

public Mono filter(ServerWebExchange exchange) {
return Mono.defer(() -> {
//如果鏈路沒有結束,則繼續鏈路
if (this.index < filters.size()) {
GatewayFilter filter = filters.get(this.index);
//這里將 index + 1,也就是調用鏈路中的下一個 GatewayFilter
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this, this.index + 1);
//每個 filter 中如果想要繼續鏈路,則會調用 chain.filter(exchange),這也是我們開發 GatewayFilter 的時候的使用方式
return filter.filter(exchange, chain);
}
else {
//到達末尾,鏈路結束
return Mono.empty(); // complete
}
});
}

所以,經過 ??DefaultGatewayFilterChain?? 后,我們的 Mono 就會變成:

return Mono.defer(() ->
new MonoWebFilterTrace(source,
RoutePredicateHandlerMapping.this.lookupRoute(exchange) //根據請求尋找路由
.flatMap((Function>) r -> {
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r); //將路由放入 Attributes 中,后面我們還會用到
return Mono.just(RoutePredicateHandlerMapping.this.webHandler); //返回 RoutePredicateHandlerMapping 的 FilteringWebHandler
}).switchIfEmpty( //如果為 Mono.empty(),也就是沒找到路由
Mono.empty()
.then(Mono.fromRunnable(() -> { //返回 Mono.empty() 之后,記錄日志
if (logger.isTraceEnabled()) {
logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
}
})))
.switchIfEmpty(DispatcherHandler.this.createNotFoundError()) //如果沒有返回不為 Mono.empty() 的 handlerMapping,則直接返回 404
.then(new DefaultGatewayFilterChain(combined).filter(exchange).then(Mono.empty()))), //調用對應的 Handler
TraceWebFilter.this.isTracePresent(), TraceWebFilter.this, TraceWebFilter.this.spanFromContextRetriever()).transformDeferred((call) -> {
//MetricsWebFilter 相關的處理,在前面的代碼中給出了,這里省略
});
);

再繼續展開 ??DefaultGatewayFilterChain?? 的鏈路調用,可以得到:

return Mono.defer(() ->
new MonoWebFilterTrace(source,
RoutePredicateHandlerMapping.this.lookupRoute(exchange) //根據請求尋找路由
.flatMap((Function>) r -> {
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r); //將路由放入 Attributes 中,后面我們還會用到
return Mono.just(RoutePredicateHandlerMapping.this.webHandler); //返回 RoutePredicateHandlerMapping 的 FilteringWebHandler
}).switchIfEmpty( //如果為 Mono.empty(),也就是沒找到路由
Mono.empty()
.then(Mono.fromRunnable(() -> { //返回 Mono.empty() 之后,記錄日志
if (logger.isTraceEnabled()) {
logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
}
})))
.switchIfEmpty(DispatcherHandler.this.createNotFoundError()) //如果沒有返回不為 Mono.empty() 的 handlerMapping,則直接返回 404
.then(
Mono.defer(() -> {
//如果鏈路沒有結束,則繼續鏈路
if (DefaultGatewayFilterChain.this.index < DefaultGatewayFilterChain.this.filters.size()) {
GatewayFilter filter = DefaultGatewayFilterChain.this.filters.get(DefaultGatewayFilterChain.this.index);
//這里將 index + 1,也就是調用鏈路中的下一個 GatewayFilter
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(DefaultGatewayFilterChain.this, DefaultGatewayFilterChain.this.index + 1);
//每個 filter 中如果想要繼續鏈路,則會調用 chain.filter(exchange),這也是我們開發 GatewayFilter 的時候的使用方式
return filter.filter(exchange, chain);
}
else {
return Mono.empty(); //鏈路完成
}
})
.then(Mono.empty()))
), //調用對應的 Handler
TraceWebFilter.this.isTracePresent(), TraceWebFilter.this, TraceWebFilter.this.spanFromContextRetriever()).transformDeferred((call) -> {
//MetricsWebFilter 相關的處理,在前面的代碼中給出了,這里省略
});
);

這樣,就形成了 Spring Cloud Gateway 針對路由請求的完整 Mono 調用鏈。

微信搜索“我的編程喵”關注公眾號,每日一刷,輕松提升技術,斬獲各種offer

SpringCloud升級之路2020.0.x版-41.

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/125259.html

相關文章

  • SpringCloud升級之路2020.0.x-41. SpringCloudGateway 基本

    摘要:在這里,會將上下文中載入的拼接成,然后調用其方法的,它是的處理請求業務的起點。添加相關依賴之后,會有這個。路由權重相關配置功能相關實現類,這個我們這里不關心。本系列代碼地址:https://github.com/JoJoTec/spring-cloud-parent我們繼續分析上一節提到的 WebHandler,經過將請求封裝成 ServerWebExchange 的 HttpWebHand...

    不知名網友 評論0 收藏0
  • SpringCloud升級之路2020.0.x-41. SpringCloudGateway 基本

    摘要:在這里,會將上下文中載入的拼接成,然后調用其方法的,它是的處理請求業務的起點。添加相關依賴之后,會有這個。路由權重相關配置功能相關實現類,這個我們這里不關心。 本系列代碼地址:??https://github.com/JoJoTec/spring-cloud-parent??我們繼續分析上一節提到的 ??WebHandle...

    番茄西紅柿 評論0 收藏2637
  • SpringCloud升級之路2020.0.x-41. SpringCloudGateway 基本

    摘要:將請求封裝成將請求封裝成的接口定義是但是最外層傳進來的參數是和,需要將他們封裝成,這個工作就是在中做的。其實主要任務就是將各種參數封裝成除了和本次請求相關的和,還有會話管理器,編碼解碼器配置,國際化配置還有用于擴展。本系列代碼地址:https://github.com/JoJoTec/spring-cloud-parent接下來,將進入我們升級之路的又一大模塊,即網關模塊。網關模塊我們廢棄了...

    不知名網友 評論0 收藏0
  • SpringCloud升級之路2020.0.x-41. SpringCloudGateway 基本

    摘要:添加相關依賴之后,會有這個。接著,根據的源碼分析,會繼續鏈路,到達下一個,即。在中,我們會計算出路由并發送請求到符合條件的。這個是的,會讀取配置并生成路由。本系列代碼地址:https://github.com/JoJoTec/spring-cloud-parent我們繼續分析上一節提到的 WebHandler。加入 Spring Cloud Sleuth 以及 Prometheus 相關依賴...

    不知名網友 評論0 收藏0
  • SpringCloud升級之路2020.0.x-37. 實現異步的客戶端封裝配置管理的意義與設計

    摘要:對于異步的請求,使用的是異步客戶端即。要實現的配置設計以及使用舉例要實現的配置設計以及使用舉例首先,我們要實現的,其包含三個重試重試的要在負載均衡之前,因為重試的時候,我們會從負載均衡器獲取另一個實例進行重試,而不是在同一個實例上重試多次。 本系列代碼地址:https://github.com/JoJoTec/spring-cloud-parent 為何需要封裝異步 HT...

    fxp 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<