背景
SpringCloud Gateway 【请求重试】
在实现 请求重试 的过程中,发现开启 SpringCloud Gateway 的服务自动路由之后,请求重试的功能就失效了,网络上大部分文章都是描述 请求重试 是如何实现的,一两篇说注释掉配置后就可以生效了,但是具体为什么则没有描述。
这里主要描述一下SpringCloud Gateway 定位路由的过程,以及为什么配置 locator.enable=true 之后配置失效的原因。
路由
局限于 Spring Cloud Gateway Server 3.1.0-SNAPSHOT
众所周知,SpringCloud Gateway是基于SpringBoot的项目,自然配置也是基于SpringBoot的自动装配,从jar包的 spring.factories,我们可以发现如下信息
# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\org.springframework.cloud.gateway.config.GatewayResilience4JCircuitBreakerAutoConfiguration,\org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration,\org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration,\org.springframework.cloud.gateway.config.SimpleUrlHandlerMappingGlobalCorsAutoConfiguration,\org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration,\org.springframework.cloud.gateway.config.GatewayReactiveOAuth2AutoConfigurationorg.springframework.boot.env.EnvironmentPostProcessor=\org.springframework.cloud.gateway.config.GatewayEnvironmentPostProcessor# Failure Analyzersorg.springframework.boot.diagnostics.FailureAnalyzer=\org.springframework.cloud.gateway.support.MvcFoundOnClasspathFailureAnalyzer
主要关注的是
- GatewayAutoConfiguration: 加载基本的 
GlobalFilter和GatewayFilter,以及locator等等。 - GatewayDiscoveryClientAutoConfiguration:  如果 
locator.enable=true,注入一个DiscoveryClientRouteDefinitionLocator用于从注册中心生成路由 
经过配置的总结之后,会生成如下路由加载关系,他们的UML图如下所示
- CompositeRouteDefinitionLocator: 基本的组合模式,开源项目常用名词
 - PropertiesRouteDefinitionLocator: 基于properties的路由,默认开启
 - InMemoryRouteDefinitionRepository: 基于内存的Route Definition,默认开启
 - DiscoveryClientRouteDefinitionLocator: 基于服务注册的路由(默认不开启,设置
locator.enable=true之后开启), 开启之后优先级最高- 比如注册中心存在一个 
nacos-provider的实例,那么就会生成一个nacos-provider的route,path = /nacos-provider/** 
 - 比如注册中心存在一个 
 - 基于redis,这里没有贴出来
刷新明细
由于老外的注册中心和配置中心不是放一起的。
 
从上述的类图基本上就可以明确调用方向了,RouteLocator 对外暴露获取 Route 的方法,其内部是委托给 RouteDefinitionLocator。  涉及到路由变更,那么肯定是涉及到三个 RouteDefinitionRoute 实现类的具体实现。
PropertiesRouteDefinitionLocator: route可以配置在Bootstrap中,也可以配置在nacos配置中心中,动态更新路由后,Gateway的路由配置便会生效。
public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {private final GatewayProperties properties;public PropertiesRouteDefinitionLocator(GatewayProperties properties) {this.properties = properties;}@Overridepublic Flux<RouteDefinition> getRouteDefinitions() {return Flux.fromIterable(this.properties.getRoutes());}}
InMemoryRouteDefinitionRepository: 一般不用,不用关心 ```java public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
private final Map
routes = synchronizedMap(new LinkedHashMap ()); @Override public Flux
getRouteDefinitions() { Map<String, RouteDefinition> routesSafeCopy = new LinkedHashMap<>(routes);return Flux.fromIterable(routesSafeCopy.values());
}
}
- DiscoveryClientRouteDefinitionLocator: 基于注册中心生成路由```javapublic Flux<RouteDefinition> getRouteDefinitions() {SpelExpressionParser parser = new SpelExpressionParser();Expression includeExpr = parser.parseExpression(properties.getIncludeExpression());Expression urlExpr = parser.parseExpression(properties.getUrlExpression());Predicate<ServiceInstance> includePredicate;if (properties.getIncludeExpression() == null || "true".equalsIgnoreCase(properties.getIncludeExpression())) {includePredicate = instance -> true;}else {includePredicate = instance -> {Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);if (include == null) {return false;}return include;};}//调用nacos获取的实例信息return serviceInstances.filter(instances -> !instances.isEmpty()).flatMap(Flux::fromIterable).filter(includePredicate).collectMap(ServiceInstance::getServiceId)// remove duplicates.flatMapMany(map -> Flux.fromIterable(map.values())).map(instance -> {RouteDefinition routeDefinition = buildRouteDefinition(urlExpr, instance);final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties);//从这里就可以知道生成的RouteDefinition,只有2个简简单单的Filter//retry是没有的,所以开启了locator.enable=true之后,使用的是这里生成的route//所以,retry不生效for (PredicateDefinition original : this.properties.getPredicates()) {PredicateDefinition predicate = new PredicateDefinition();predicate.setName(original.getName());for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);predicate.addArg(entry.getKey(), value);}routeDefinition.getPredicates().add(predicate);}for (FilterDefinition original : this.properties.getFilters()) {FilterDefinition filter = new FilterDefinition();filter.setName(original.getName());for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);filter.addArg(entry.getKey(), value);}routeDefinition.getFilters().add(filter);}return routeDefinition;});}
使用路由
无法使用的配置一无所用,路由的使用则是在 RoutePredicateHandlerMapping中进行路由查找和定位.  这部分去掉 Flux 和 Mono 的异步操作后就是一个简单的 List 轮询遍历操作。
如果对于 Flux 和 Mono 不熟悉,可以暂时类比Flux: List, Mono: 具体的实例对象
@Overrideprotected Mono<?> getHandlerInternal(ServerWebExchange exchange) {return lookupRoute(exchange).flatMap((Function<Route, Mono<?>>) r -> {exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);return Mono.just(webHandler);}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);})));}protected Mono<Route> lookupRoute(ServerWebExchange exchange) {//委托给routeLocator, 其又委托给RouteDefinitionRoute,return this.routeLocator.getRoutes()// individually filter routes so that filterWhen error delaying is not a// problem.concatMap(route -> Mono.just(route).filterWhen(r -> {//进行predict.test判定,具体则是配置文件所描述的路由定义exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());return r.getPredicate().apply(exchange);})// instead of immediately stopping main flux due to error, log and// swallow it.doOnError(e -> logger.error("Error applying predicate for route: " + route.getId(), e)).onErrorResume(e -> Mono.empty())).next().map(route -> {return route;});}
具体的业务逻辑则是进入
- HttpWebHandlerAdapter#handle: 委托给FilteringWebHandler执行
 FilteringWebHandler#handle ```java @Override public Mono
handle(ServerWebExchange exchange) { Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR); List gatewayFilters = route.getFilters(); List
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); }
private static class DefaultGatewayFilterChain implements GatewayFilterChain {
private final int index;private final List<GatewayFilter> filters;DefaultGatewayFilterChain(List<GatewayFilter> filters) {this.filters = filters;this.index = 0;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange) {return Mono.defer(() -> {if (this.index < filters.size()) {GatewayFilter filter = filters.get(this.index);DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this, this.index + 1);return filter.filter(exchange, chain);}else {return Mono.empty(); // complete}});}}
总结
从 请求重试 出发,经历了 看日志、Debug、拉源码 对路由刷新和使用进行了一个整体流程上的梳理。 
同时加强了对于SpringBoot自动装配的理解,不论是开源项目,还是公司封装的二方包,只要抓住装配的细节,同时辅以UML图进行了解,基本上可以将项目的设计和使用尽收眼底。
