深入理解SpringCloud之Gateway
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
作者 | 簡(jiǎn)之軒
來(lái)源 | urlify.cn/2u2EFn
雖然在服務(wù)網(wǎng)關(guān)有了zuul(在這里是zuul1),其本身還是基于servlet實(shí)現(xiàn)的,換言之還是同步阻塞方式的實(shí)現(xiàn)。就其本身來(lái)講它的最根本弊端也是再此。而非阻塞帶來(lái)的好處不言而喻,高效利用線程資源進(jìn)而提高吞吐量,基于此Spring率先拿出針對(duì)于web的殺手锏,對(duì),就是webflux。而Gateway本身就是基于webflux基礎(chǔ)之上實(shí)現(xiàn)的。畢竟spring推出的技術(shù),當(dāng)然要得以推廣嘛。不過(guò)就國(guó)內(nèi)的軟件公司而言為了穩(wěn)定而選擇保守,因此就這項(xiàng)技術(shù)的廣度來(lái)說(shuō)我本身還是在觀望中。
Gateway快速上手
添加依賴:implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
這里請(qǐng)注意,springcloud-gateway是基于netty運(yùn)行的環(huán)境,在servlet容器環(huán)境或者把它構(gòu)建為war包運(yùn)行的話是不允許的,因此在項(xiàng)目當(dāng)中沒(méi)有必要添加spring-boot-starter-web。在gateway當(dāng)中有三個(gè)重要的元素他們分別是:
Route 是最核心的路由元素,它定義了ID,目標(biāo)URI ,predicates的集合與filter的集合,如果Predicate聚合返回真,則匹配該路由
Predicate 基于java8的函數(shù)接口Predicate,其輸入?yún)?shù)類型ServerWebExchange,其作用就是允許開(kāi)發(fā)人員根據(jù)當(dāng)前的http請(qǐng)求進(jìn)行規(guī)則的匹配,比如說(shuō)http請(qǐng)求頭,請(qǐng)求時(shí)間等,匹配的結(jié)果將決定執(zhí)行哪種路由
Filter為GatewayFilter,它是由特殊的工廠構(gòu)建,通過(guò)Filter可以在下層請(qǐng)求路由前后改變http請(qǐng)求與響應(yīng)
我們編輯application.yaml,定義如下配置:
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: before_route
uri: http://www.baidu.com
predicates:
- Path=/baidu
server:
port: 8088
此時(shí)當(dāng)我們?cè)L問(wèn)路徑中包含/baidu的,gateway將會(huì)幫我們轉(zhuǎn)發(fā)至百度頁(yè)面
工作流程
在這里我貼上官網(wǎng)的一張圖:

在這里我想結(jié)合源代碼來(lái)說(shuō)明其流程,這里面有個(gè)關(guān)鍵的類,叫RoutePredicateHandlerMapping,我們可以發(fā)現(xiàn)這個(gè)類有如下特點(diǎn):
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
// ....省略部分代碼
@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
// don't handle requests on management port if set and different than server port
if (this.managementPortType == DIFFERENT && this.managementPort != null
&& exchange.getRequest().getURI().getPort() == this.managementPort) {
return Mono.empty();
}
exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());
return lookupRoute(exchange)
// .log("route-predicate-handler-mapping", Level.FINER) //name this
.flatMap((Function<Route, Mono<?>>) r -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isDebugEnabled()) {
logger.debug(
"Mapping [" + getExchangeDesc(exchange) + "] to " + r);
}
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
return Mono.just(webHandler);
}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isTraceEnabled()) {
logger.trace("No RouteDefinition found for ["
+ getExchangeDesc(exchange) + "]");
}
})));
}
//...省略部分代碼
}
此類繼承了AbstractHandlerMapping,注意這里的是reactive包下的,也就是webflux提供的handlermapping,其作用等同于webmvc的handlermapping,其作用是將請(qǐng)求映射找到對(duì)應(yīng)的handler來(lái)處理。
在這里處理的關(guān)鍵就是先尋找合適的route,關(guān)鍵的方法為lookupRoute():
protected Mono
return this.routeLocator.getRoutes()
// individually filter routes so that filterWhen error delaying is not a
// problem
.concatMap(route -> Mono.just(route).filterWhen(r -> {
// add the current route we are testing
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()))
// .defaultIfEmpty() put a static Route not found
// or .switchIfEmpty()
// .switchIfEmpty(Mono.
.next()
// TODO: error handling
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("Route matched: " + route.getId());
}
validateRoute(route, exchange);
return route;
});
/*
* TODO: trace logging if (logger.isTraceEnabled()) {
* logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); }
*/
}
其中RouteLocator的接口作用是獲取Route定義,那么在GatewayAutoConfiguaration里有相關(guān)的配置,大家可自行查閱:
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
List
List
RouteDefinitionLocator routeDefinitionLocator,
@Qualifier("webFluxConversionService") ConversionService conversionService) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,
GatewayFilters, properties, conversionService);
}
然后在注釋add the current route we are testing處可以得到一個(gè)結(jié)論,其是根據(jù)Predicate的聲明條件過(guò)濾出合適的Route
最終拿到FilteringWebHandler作為它的返回值,這個(gè)類是真正意義上處理請(qǐng)求的類,它實(shí)現(xiàn)了webflux提供的WebHandler接口:
public class FilteringWebHandler implements WebHandler {
//.....省略其它代碼
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
//拿到當(dāng)前的route
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
//獲取所有的gatewayFilter
List<GatewayFilter> gatewayFilters = route.getFilters();
//獲取全局過(guò)濾器
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
combined.addAll(gatewayFilters);
// TODO: needed or cached?
AnnotationAwareOrderComparator.sort(combined);
if (logger.isDebugEnabled()) {
logger.debug("Sorted gatewayFilterFactories: " + combined);
}
//交給默認(rèn)的過(guò)濾器鏈執(zhí)行所有的過(guò)濾操作
return new DefaultGatewayFilterChain(combined).filter(exchange);
}
//....省略其它代碼
}
在這里可以看到它的實(shí)際處理方式是委派給過(guò)濾器鏈進(jìn)行處理請(qǐng)求操作的
Predicate
Spring Cloud Gateway包含許多內(nèi)置的Predicate Factory。所有的Predicate都匹配HTTP請(qǐng)求的不同屬性。如果配置類多個(gè)Predicate, 那么必須滿足所有的predicate才可以,官網(wǎng)上列舉的內(nèi)置的Predicate,我在這里不做過(guò)多的說(shuō)明,請(qǐng)大家參考:地址,predicate的實(shí)現(xiàn)可以在org.springframework.cloud.gateway.handler.predicate的包下找到。
3.1、自定義Predicate
先改一下application.yaml中的配置:
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: before_route
uri: http://www.baidu.com
predicates:
- Number=1
默認(rèn)命名規(guī)則:名稱RoutePredicateFactory,在這里我們可以看到如下代碼規(guī)則用以解析Predicate的名稱,該代碼在NameUtils當(dāng)中:
public static String normalizeRoutePredicateName(
Class<? extends RoutePredicateFactory> clazz) {
return removeGarbage(clazz.getSimpleName()
.replace(RoutePredicateFactory.class.getSimpleName(), ""));
}
那么在這里我們就按照如上規(guī)則建立對(duì)應(yīng)的NumberRoutePredicateFactory,代碼如下:
@Component
public class NumberRoutePredicateFactory extends AbstractRoutePredicateFactory<NumberRoutePredicateFactory.Config> {
public NumberRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("number");
}
@Override
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
String number = serverWebExchange.getRequest().getQueryParams().getFirst("number");
return config.number == Integer.parseInt(number);
}
};
}
public static class Config {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
}
該類可以繼承AbstractRoutePredicateFactory,同時(shí)需要注冊(cè)為spring的Bean
在此類當(dāng)中按照規(guī)范來(lái)講,需要定義一個(gè)內(nèi)部類,該類的作用用于封裝application.yaml中的配置,Number=1這個(gè)配置會(huì)按照規(guī)則進(jìn)行封裝,這個(gè)規(guī)則由以下幾項(xiàng)決定:
ShortcutType,該值是枚舉類型,分別是
DEFAULT :按照shortcutFieldOrder順序依次賦值
GATHER_LIST:shortcutFiledOrder只能有一個(gè)值,如果參數(shù)有多個(gè)拼成一個(gè)集合
GATHER_LIST_TAIL_FLAG:shortcutFiledOrder只能有兩個(gè)值,其中最后一個(gè)值為true或者false,其余的值變成一個(gè)集合付給第一個(gè)值
shortcutFieldOrder,這個(gè)值決定了Config中配置的屬性,配置的參數(shù)都會(huì)被封裝到該屬性當(dāng)中
4. Filter
Gateway中的filter可以分為(GlobalFilter)全局過(guò)濾器與普通過(guò)濾器,過(guò)濾器可以在路由到代理服務(wù)的前后改變請(qǐng)求與響應(yīng)。在這里我會(huì)列舉兩個(gè)常見(jiàn)的filter給大家用作參考:
4.1、負(fù)載均衡的實(shí)現(xiàn)
與zuul類似,Gateway也可以作為服務(wù)端的負(fù)載均衡,那么負(fù)載均衡的處理關(guān)鍵就是與Ribbon集成,那么Gateway是利用GlobalFilter進(jìn)行實(shí)現(xiàn)的,它的實(shí)現(xiàn)類是LoadBalancerClientFilter:
public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
protected final LoadBalancerClient loadBalancer;
private LoadBalancerProperties properties;
//....
@Override
@SuppressWarnings("Duplicates")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// preserve the original url
addOriginalRequestUrl(exchange, url);
log.trace("LoadBalancerClientFilter url before: " + url);
//選擇一個(gè)服務(wù)實(shí)例
final ServiceInstance instance = choose(exchange);
if (instance == null) {
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
//判斷協(xié)議類型
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
//重構(gòu)uri地址
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
//...
return chain.filter(exchange);
}
}
在這里我們可以看到這里它是基于Spring-Cloud-Commons規(guī)范里的LoadBalanceClient包裝實(shí)現(xiàn)的。
4.2、集成Hystrix
Gateway同樣也可以和Hystrix進(jìn)行集成,這里面的關(guān)鍵類是HystrixGatewayFilterFactory,這里面的關(guān)鍵是RouteHystrixCommand該類繼承了HystrixObservableCommand:
@Override
protected Observable<Void> construct() {
// 執(zhí)行過(guò)濾器鏈
return RxReactiveStreams.toObservable(this.chain.filter(exchange));//1
}
@Override
protected Observable<Void> resumeWithFallback() {
if (this.fallbackUri == null) {
return super.resumeWithFallback();
}
// TODO: copied from RouteToRequestUrlFilter
URI uri = exchange.getRequest().getURI();
// TODO: assume always?
boolean encoded = containsEncodedParts(uri);
URI requestUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null)
.uri(this.fallbackUri).scheme(null).build(encoded).toUri();//2
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
addExceptionDetails();
ServerHttpRequest request = this.exchange.getRequest().mutate()
.uri(requestUrl).build();
ServerWebExchange mutated = exchange.mutate().request(request).build();
return RxReactiveStreams.toObservable(getDispatcherHandler().handle(mutated));//3
}
在代碼1處會(huì)執(zhí)行濾器鏈,寫到此處的代碼會(huì)被統(tǒng)一加上hystrix的保護(hù)
在代碼2處再是執(zhí)行回退的方法,根據(jù)fallbackUri構(gòu)建一個(gè)回退請(qǐng)求地址
在代碼3處獲取WebFlux的總控制器DispatcherHandler進(jìn)行回退地址的處理
5、服務(wù)發(fā)現(xiàn)
服務(wù)發(fā)現(xiàn)對(duì)于Gateway來(lái)說(shuō)也是個(gè)非常重要的內(nèi)容,Gateway在這里定義了一個(gè)核心接口叫做:RouteDefinitionLocator,這個(gè)接口用于獲取Route的定義,服務(wù)發(fā)現(xiàn)的機(jī)制實(shí)現(xiàn)了該接口:
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
//....省略部分代碼
return Flux.fromIterable(discoveryClient.getServices())//獲取所有服務(wù)
.map(discoveryClient::getInstances) //映射轉(zhuǎn)換所有服務(wù)實(shí)例
.filter(instances -> !instances.isEmpty()) //過(guò)濾出不為空的服務(wù)實(shí)例
.map(instances -> instances.get(0)).filter(includePredicate)//根據(jù)properites里的include表達(dá)式過(guò)濾實(shí)例
.map(instance -> {
/*
構(gòu)建Route的定義
*/
String serviceId = instance.getServiceId();
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
String uri = urlExpr.getValue(evalCtxt, instance, String.class);
routeDefinition.setUri(URI.create(uri));
final ServiceInstance instanceForEval = new DelegatingServiceInstance(
instance, properties);
//添加Predicate
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);
}
//添加filter
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;
});
}
}
由此我們可以知道,這里面利用DiscoveryClient獲取所有的服務(wù)實(shí)例并將每個(gè)實(shí)例構(gòu)建為一個(gè)Route,不過(guò)在此之前,在自動(dòng)裝配的類GatewayDiscoveryClientAutoConfiguration里已經(jīng)配置了默認(rèn)的Predicate與Filter,它會(huì)預(yù)先幫我們配置默認(rèn)的Predicate與Filter:
public static List<PredicateDefinition> initPredicates() {
ArrayList<PredicateDefinition> definitions = new ArrayList<>();
// TODO: add a predicate that matches the url at /serviceId?
// add a predicate that matches the url at /serviceId/**
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class));
predicate.addArg(PATTERN_KEY, "'/'+serviceId+'/**'");
definitions.add(predicate);
return definitions;
}
public static List<FilterDefinition> initFilters() {
ArrayList<FilterDefinition> definitions = new ArrayList<>();
// add a filter that removes /serviceId by default
FilterDefinition filter = new FilterDefinition();
filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "'/' + serviceId + '/(?<remaining>.*)'";
String replacement = "'/${remaining}'";
filter.addArg(REGEXP_KEY, regex);
filter.addArg(REPLACEMENT_KEY, replacement);
definitions.add(filter);
return definitions;
}
這里面主要會(huì)根據(jù)ServiceId構(gòu)建為 Path=/serviceId/**的Predicate和路由至對(duì)應(yīng)服務(wù)前把ServiceId去掉的filter
6、總結(jié)
根據(jù)上述說(shuō)明,我僅僅選取了兩個(gè)比較典型意義的Predicate與Filter代碼進(jìn)行說(shuō)明,由于官網(wǎng)上沒(méi)有說(shuō)明自定義Predicate,我在這里索性寫了個(gè)簡(jiǎn)單的例子,那么自定義Filter的例子可以參考官網(wǎng)地址:
這里需要吐槽一下官方 什么時(shí)候能把TODO補(bǔ)充完整的呢?
Gateway是基于Webflux實(shí)現(xiàn)的,它通過(guò)擴(kuò)展HandlerMapping與WebHandler來(lái)處理用戶的請(qǐng)求,先通過(guò)Predicate定位到Router然后在經(jīng)過(guò)FilterChain的過(guò)濾處理,最后定位到下層服務(wù)。同時(shí)官方給我們提供了許多Prdicate與Filter,比如說(shuō)限流的。從這點(diǎn)來(lái)說(shuō)它的功能比zuul還強(qiáng)大呢,zuul里有的服務(wù)發(fā)現(xiàn),斷路保護(hù)等,Gateway分別通過(guò)GlobalFilter與Filter來(lái)實(shí)現(xiàn)。
最后至于Gateway能普及到什么樣的程度,亦或者能不能最終成為統(tǒng)一的網(wǎng)關(guān)標(biāo)準(zhǔn),這個(gè)我也不能再這里有所保證,那么就交給時(shí)間來(lái)證明吧。
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長(zhǎng)按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
