雖然在服務網關有了zuul(在這裏是zuul1),其自己仍是基於servlet實現的,換言之仍是同步阻塞方式的實現。就其自己來說它的最根本弊端也是再此。而非阻塞帶來的好處不言而喻,高效利用線程資源進而提升吞吐量,基於此Spring率先拿出針對於web的殺手鐗,對,就是webflux。而Gateway自己就是基於webflux基礎之上實現的。畢竟spring推出的技術,固然要得以推廣嘛。不過就國內的軟件公司而言爲了穩定而選擇保守,所以就這項技術的廣度來講我自己仍是在觀望中。html
添加依賴:java
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
這裏請注意,springcloud-gateway是基於netty運行的環境,在servlet容器環境或者把它構建爲war包運行的話是不容許的,所以在項目當中沒有必要添加spring-boot-starter-web。在gateway當中有三個重要的元素他們分別是:react
咱們編輯application.yaml,定義以下配置:web
spring: application: name: gateway cloud: gateway: routes: - id: before_route uri: http://www.baidu.com predicates: - Path=/baidu server: port: 8088
此時當咱們訪問路徑中包含/baidu的,gateway將會幫咱們轉發至百度頁面spring
在這裏我貼上官網的一張圖:mvc
在這裏我想結合源代碼來講明其流程,這裏面有個關鍵的類,叫RoutePredicateHandlerMapping,咱們能夠發現這個類有以下特色:app
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) + "]"); } }))); } //...省略部分代碼 }
protected Mono<Route> lookupRoute(ServerWebExchange exchange) { 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.<Route>empty().log("noroute")) .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()); } */ }
@Bean public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties, List<GatewayFilterFactory> GatewayFilters, List<RoutePredicateFactory> predicates, RouteDefinitionLocator routeDefinitionLocator, @Qualifier("webFluxConversionService") ConversionService conversionService) { return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters, properties, conversionService); }
public class FilteringWebHandler implements WebHandler { //.....省略其它代碼 @Override public Mono<Void> handle(ServerWebExchange exchange) { //拿到當前的route Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR); //獲取全部的gatewayFilter List<GatewayFilter> gatewayFilters = route.getFilters(); //獲取全局過濾器 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); } //交給默認的過濾器鏈執行全部的過濾操做 return new DefaultGatewayFilterChain(combined).filter(exchange); } //....省略其它代碼 }
在這裏能夠看到它的實際處理方式是委派給過濾器鏈進行處理請求操做的負載均衡
Spring Cloud Gateway包含許多內置的Predicate Factory。全部的Predicate都匹配HTTP請求的不一樣屬性。若是配置類多個Predicate, 那麼必須知足全部的predicate才能夠,官網上列舉的內置的Predicate,我在這裏不作過多的說明,請你們參考:地址,predicate的實現能夠在org.springframework.cloud.gateway.handler.predicate
的包下找到。ide
先改一下application.yaml中的配置:函數
spring: application: name: gateway cloud: gateway: routes: - id: before_route uri: http://www.baidu.com predicates: - Number=1
默認命名規則:名稱RoutePredicateFactory,在這裏咱們能夠看到以下代碼規則用以解析Predicate的名稱,該代碼在NameUtils當中:
public static String normalizeRoutePredicateName( Class<? extends RoutePredicateFactory> clazz) { return removeGarbage(clazz.getSimpleName() .replace(RoutePredicateFactory.class.getSimpleName(), "")); }
那麼在這裏咱們就按照如上規則創建對應的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; } } }
Gateway中的filter能夠分爲(GlobalFilter)全局過濾器與普經過濾器,過濾器能夠在路由到代理服務的先後改變請求與響應。在這裏我會列舉兩個常見的filter給你們用做參考:
與zuul相似,Gateway也能夠做爲服務端的負載均衡,那麼負載均衡的處理關鍵就是與Ribbon集成,那麼Gateway是利用GlobalFilter進行實現的,它的實現類是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); //選擇一個服務實例 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. //判斷協議類型 String overrideScheme = instance.isSecure() ? "https" : "http"; if (schemePrefix != null) { overrideScheme = url.getScheme(); } //重構uri地址 URI requestUrl = loadBalancer.reconstructURI( new DelegatingServiceInstance(instance, overrideScheme), uri); //... return chain.filter(exchange); } }
在這裏咱們能夠看到這裏它是基於Spring-Cloud-Commons規範裏的LoadBalanceClient包裝實現的。
Gateway一樣也能夠和Hystrix進行集成,這裏面的關鍵類是HystrixGatewayFilterFactory,這裏面的關鍵是RouteHystrixCommand該類繼承了HystrixObservableCommand:
@Override protected Observable<Void> construct() { // 執行過濾器鏈 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 }
服務發現對於Gateway來講也是個很是重要的內容,Gateway在這裏定義了一個核心接口叫作:RouteDefinitionLocator,這個接口用於獲取Route的定義,服務發現的機制實現了該接口:
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator { @Override public Flux<RouteDefinition> getRouteDefinitions() { //....省略部分代碼 return Flux.fromIterable(discoveryClient.getServices())//獲取全部服務 .map(discoveryClient::getInstances) //映射轉換全部服務實例 .filter(instances -> !instances.isEmpty()) //過濾出不爲空的服務實例 .map(instances -> instances.get(0)).filter(includePredicate)//根據properites裏的include表達式過濾實例 .map(instance -> { /* 構建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獲取全部的服務實例並將每一個實例構建爲一個Route,不過在此以前,在自動裝配的類GatewayDiscoveryClientAutoConfiguration裏已經配置了默認的Predicate與Filter,它會預先幫咱們配置默認的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; }
這裏面主要會根據ServiceId構建爲 Path=/serviceId/**的Predicate和路由至對應服務前把ServiceId去掉的filter
根據上述說明,我僅僅選取了兩個比較典型意義的Predicate與Filter代碼進行說明,因爲官網上沒有說明自定義Predicate,我在這裏索性寫了個簡單的例子,那麼自定義Filter的例子能夠參考官網地址:
這裏須要吐槽一下官方 何時能把TODO補充完整的呢?
Gateway是基於Webflux實現的,它經過擴展HandlerMapping與WebHandler來處理用戶的請求,先經過Predicate定位到Router而後在通過FilterChain的過濾處理,最後定位到下層服務。同時官方給咱們提供了許多Prdicate與Filter,好比說限流的。從這點來講它的功能比zuul還強大呢,zuul裏有的服務發現,斷路保護等,Gateway分別經過GlobalFilter與Filter來實現。
最後至於Gateway能普及到什麼樣的程度,亦或者能不能最終成爲統一的網關標準,這個我也不能再這裏有所保證,那麼就交給時間來證實吧。