开始前
接下来分析的spring cloud gateway
功能,是接入了服务发现。Spring Cloud Gateway
在接入服务发现后,核心路由逻辑和通过property
是有所不同的,核心实现类是不同的。
入口
Spring Cloud Gateway
基于WebFlux
实现,在webflux
中mapping
定义和org.springframework.web.reactive.HandlerMapping
有关。
Spring Cloud Gateway
可以根据请求前缀转发请求到同名的微服务,既然是gateway
是基于webflux
实现,又能接收接入spring cloud
平台的所有微服务名为前缀的请求,那么spring cloud gateway
一定实现了特殊的HandlerMapping
,接收不同前缀的请求。所以接下来从HandlerMapping
入手,分析spring cloud gateway
核心路由功能。
核心流程
根据请求找到对应的 Route
在spring cloud gateway
项目内,org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping
实现了HandlerMapping
接口,可以确定核心路由都是围绕此类展开。
以下代码是RoutePredicateHandlerMapping
的getHandlerInternal
方法,都是基于webflux
的实现,代码逻辑很简单:
- 调用
lookupRoute
,根据方法名是在寻找路由。 - 如果找到了路由,返回
webHandler
对象,该对象为FilteringWebHandler
实例,由构造器传入。 - 如果找不到返回
Mono.empty()
@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
...
return lookupRoute(exchange)
.flatMap((Function<Route, Mono<?>>) r -> {
...
// 直接返回了 webHandler 对象,该对象在 spring cloud gateway 项目内为
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().put(GATEWAY_ROUTE_ATTR, r);
这段代码非常重要,首先lookupRoute
看方法名意思就是根据请求,找到对应的Route
,用来路由请求。如果lookupRoute
找到了Route
,Route
对象会被放入exchange
对象内,这一步非常重要!!
除了lookupRoute
外,可以看到getHandlerInternal
方法返回了一个webHandler
对象,在HandlerMapping
接口里,getHandler
方法需要返回一个handler
,用来处理请求,在spring cloud gateway
内,所有的请求都最后由webHandler
处理,这里也非常的重要,之后会分析webHandler
逻辑。
===========================================================================
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
// 通过 routeLocator 获取所有的 routes
return this.routeLocator.getRoutes()
.concatMap(route -> Mono.just(route).filterWhen(r -> {
exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
// 当前 route 是否匹配,不匹配过滤掉
return r.getPredicate().apply(exchange);
})
.doOnError(e -> logger.error(
"Error applying predicate for route: " + route.getId(), e))
.onErrorResume(e -> Mono.empty()))
// 得到第一个匹配的结果
.next()
.map(route -> {
...
validateRoute(route, exchange);
return route;
});
}
在lookupRoute
方法内,通过调用routeLocator.getRoutes
获取所有的路由规则,再根据请求判断是否和route
匹配,如果匹配返回,如果都没有匹配的route
,返回Mono.empty()
。
所以关键又来到了routeLocator
对象,该对象由构造器传入,在GatewayAutoConfiguration
可以找到,RoutePredicateHandlerMapping bean
创建时,routeLocator
由外部注入,在同一个配置类内,可以找到RouteLocator bean
的定义方法。
@Bean
public RoutePredicateHandlerMapping routePredicateHandlerMapping(
FilteringWebHandler webHandler, RouteLocator routeLocator,
GlobalCorsProperties globalCorsProperties, Environment environment) {
return new RoutePredicateHandlerMapping(webHandler, routeLocator,
globalCorsProperties, environment);
}
@Bean
@Primary
@ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")
// 这里通过 CompositeRouteLocator 串联了多个`RouteLocator`
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
return new CachingRouteLocator(
new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}
在cachedCompositeRouteLocator bean
定义方法内,也需要RouteLocator bean
。可以在spring cloud gateway
项目中找对应的RouteLocator
实现,除去包装串联实现类外,只有一个org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator
实现,很明显它就是获取所有route
的核心类。
RouteDefinitionRouteLocator
实现了RouteLocator
的getRoutes
方法,如下
@Override
public Flux<Route> getRoutes() {
Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions()
.map(this::convertToRoute);
...
return routes.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition matched: " + route.getId());
}
return route;
});
}
通过routeDefinitionLocator.getRouteDefinitions
获取所有的route
定义,再转化为Route
对象。
RouteDefinitionLocator
有多个实现类,除去包装缓存实现类和串联实现类外,还有基于property
和基于服务发现的实现。由于在文章开头已经描述,是在接入服务发现的情况下,所以接下里只需要关注服务发现的实现类DiscoveryClientRouteDefinitionLocator
。
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
...
// serviceInstances 为多个微服务实例
return serviceInstances
...
.map(instance -> {
// 根据 instance 创建 RouteDefinition
RouteDefinition routeDefinition = buildRouteDefinition(urlExpr,
instance);
final ServiceInstance instanceForEval = new DelegatingServiceInstance(
instance, properties);
... // 从 application.yml 获取 predicate 和 filter 的逻辑在这里不展开,注释了
return routeDefinition;
});
}
DiscoveryClientRouteDefinitionLocator.getRouteDefinitions()
方法内,buildRouteDefinition
方法根据微服务实例信息,构建RouteDefinition
,那么微服务实例哪里来?答案如下
public DiscoveryClientRouteDefinitionLocator(ReactiveDiscoveryClient discoveryClient,
DiscoveryLocatorProperties properties) {
this(discoveryClient.getClass().getSimpleName(), properties);
// 从注册中心获取所有的微服务信息
serviceInstances = discoveryClient.getServices()
.flatMap(service -> discoveryClient.getInstances(service).collectList());
}
在DiscoveryClientRouteDefinitionLocator
构造器内,从注册中心获取了所有的注册的微服务实例。
根据以上的分析的内容可以总结以下几点:
RouteDefinitionLocator
内会从注册中心获取所有的微服务实例,并转化为RouteDefinition
。RouteLocator
内会将RouteDefinition
转化为Route
。RoutePredicateHandlerMapping
内根据接收到的请求匹配,找到对应的Route
。- 将
Route
对象放入exchange
请求对象内。 - 返回
webHandler
。
根据 Route 处理请求
在上一个小节里讲到了webHandler
,由于RoutePredicateHandlerMapping
的getHandlerInternal
方法内,不管找到的Route
是什么,最后都会返回webHandler
对象,所以所有的请求都在webHandler
内处理。
在Spring Cloud Gateway
内,webHandler
接口的实现类为FilteringWebHandler
。
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
// 在 RoutePredicateHandlerMapping 被放入 exchange 对象内的 Route 在 webHandler 内被取出
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
List<GatewayFilter> gatewayFilters = route.getFilters();
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
combined.addAll(gatewayFilters);
AnnotationAwareOrderComparator.sort(combined);
// 执行 filter
return new DefaultGatewayFilterChain(combined).filter(exchange);
}
handle
方法包含了对请求处理的所有逻辑:
- 从
exchange
对象内取出Route
对象。 - 从
Route
获取所有filter
,合并全局filter
。 - 链式处理
exchange
,就是处理请求的过程。
在spring cloud gateway
的设计中,请求处理被设计为链式处理流程,可以通过实现GatewayFilter
接口,实现对请求的处理。
在spring cloud gateway
项目内,有非常非常多的GatewayFilter
实现,包括熔断,限速,重试,header
处理等等,不在详细分析。
filter
最重要的逻辑处理为:
- 从请求里获取目标调用服务名。
- 通过
LoadBalanced
根据服务名获取请求IP
。 - 默认通过
netty
路由请求。 - 返回结果写入
exchange response
。 - 路由完成。
总结
通过上面的分析,spring cloud gateway
的实现逻辑很简单,代码逻辑非常清晰,非常优雅。
核心功能上就分为2
点:
- 根据请求和注册中心获取的微服务实例获取路由信息。
- 处理转发请求。
请求处理逻辑通过filter
方式,链式处理请求,又清晰又好理解。总结一个字就是"巧"!