1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        深入理解SpringCloud之Gateway

        共 20829字,需瀏覽 42分鐘

         ·

        2021-03-14 18:19

        點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

        優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          作者 |  簡之軒

        來源 |  urlify.cn/2u2EFn

        雖然在服務(wù)網(wǎng)關(guān)有了zuul(在這里是zuul1),其本身還是基于servlet實(shí)現(xiàn)的,換言之還是同步阻塞方式的實(shí)現(xiàn)。就其本身來講它的最根本弊端也是再此。而非阻塞帶來的好處不言而喻,高效利用線程資源進(jìn)而提高吞吐量,基于此Spring率先拿出針對于web的殺手锏,對,就是webflux。而Gateway本身就是基于webflux基礎(chǔ)之上實(shí)現(xiàn)的。畢竟spring推出的技術(shù),當(dāng)然要得以推廣嘛。不過就國內(nèi)的軟件公司而言為了穩(wěn)定而選擇保守,因此就這項(xiàng)技術(shù)的廣度來說我本身還是在觀望中。

        1. Gateway快速上手
          添加依賴:

          implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
          這里請注意,springcloud-gateway是基于netty運(yùn)行的環(huán)境,在servlet容器環(huán)境或者把它構(gòu)建為war包運(yùn)行的話是不允許的,因此在項(xiàng)目當(dāng)中沒有必要添加spring-boot-starter-web。在gateway當(dāng)中有三個(gè)重要的元素他們分別是:

        Route 是最核心的路由元素,它定義了ID,目標(biāo)URI ,predicates的集合與filter的集合,如果Predicate聚合返回真,則匹配該路由
        Predicate 基于java8的函數(shù)接口Predicate,其輸入?yún)?shù)類型ServerWebExchange,其作用就是允許開發(fā)人員根據(jù)當(dāng)前的http請求進(jìn)行規(guī)則的匹配,比如說http請求頭,請求時(shí)間等,匹配的結(jié)果將決定執(zhí)行哪種路由
        Filter為GatewayFilter,它是由特殊的工廠構(gòu)建,通過Filter可以在下層請求路由前后改變http請求與響應(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)我們訪問路徑中包含/baidu的,gateway將會幫我們轉(zhuǎn)發(fā)至百度頁面

        1. 工作流程
          在這里我貼上官網(wǎng)的一張圖:

        在這里我想結(jié)合源代碼來說明其流程,這里面有個(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,其作用是將請求映射找到對應(yīng)的handler來處理。
        在這里處理的關(guān)鍵就是先尋找合適的route,關(guān)鍵的方法為lookupRoute():
        protected Mono
         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.
        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()); }
             */
           }

        其中RouteLocator的接口作用是獲取Route定義,那么在GatewayAutoConfiguaration里有相關(guān)的配置,大家可自行查閱:
        @Bean
        public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
        List
         GatewayFilters,
        List
         predicates,
        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的聲明條件過濾出合適的Route
        最終拿到FilteringWebHandler作為它的返回值,這個(gè)類是真正意義上處理請求的類,它實(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();
            //獲取全局過濾器
            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)的過濾器鏈執(zhí)行所有的過濾操作
            return new DefaultGatewayFilterChain(combined).filter(exchange);
           }
          
            //....省略其它代碼
          }

        在這里可以看到它的實(shí)際處理方式是委派給過濾器鏈進(jìn)行處理請求操作的

        1. Predicate
          Spring Cloud Gateway包含許多內(nèi)置的Predicate Factory。所有的Predicate都匹配HTTP請求的不同屬性。如果配置類多個(gè)Predicate, 那么必須滿足所有的predicate才可以,官網(wǎng)上列舉的內(nèi)置的Predicate,我在這里不做過多的說明,請大家參考:地址,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ī)則建立對應(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í)需要注冊為spring的Bean
        在此類當(dāng)中按照規(guī)范來講,需要定義一個(gè)內(nèi)部類,該類的作用用于封裝application.yaml中的配置,Number=1這個(gè)配置會按照規(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ù)都會被封裝到該屬性當(dāng)中

        4. Filter
        Gateway中的filter可以分為(GlobalFilter)全局過濾器與普通過濾器,過濾器可以在路由到代理服務(wù)的前后改變請求與響應(yīng)。在這里我會列舉兩個(gè)常見的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í)行過濾器鏈
           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處會執(zhí)行濾器鏈,寫到此處的代碼會被統(tǒng)一加上hystrix的保護(hù)
        在代碼2處再是執(zhí)行回退的方法,根據(jù)fallbackUri構(gòu)建一個(gè)回退請求地址
        在代碼3處獲取WebFlux的總控制器DispatcherHandler進(jìn)行回退地址的處理

        5、服務(wù)發(fā)現(xiàn)
        服務(wù)發(fā)現(xiàn)對于Gateway來說也是個(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()) //過濾出不為空的服務(wù)實(shí)例
            .map(instances -> instances.get(0)).filter(includePredicate)//根據(jù)properites里的include表達(dá)式過濾實(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,不過在此之前,在自動裝配的類GatewayDiscoveryClientAutoConfiguration里已經(jīng)配置了默認(rèn)的Predicate與Filter,它會預(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;
         }

        這里面主要會根據(jù)ServiceId構(gòu)建為 Path=/serviceId/**的Predicate和路由至對應(yīng)服務(wù)前把ServiceId去掉的filter

        6、總結(jié)
        根據(jù)上述說明,我僅僅選取了兩個(gè)比較典型意義的Predicate與Filter代碼進(jìn)行說明,由于官網(wǎng)上沒有說明自定義Predicate,我在這里索性寫了個(gè)簡單的例子,那么自定義Filter的例子可以參考官網(wǎng)地址:

        這里需要吐槽一下官方 什么時(shí)候能把TODO補(bǔ)充完整的呢?

        Gateway是基于Webflux實(shí)現(xiàn)的,它通過擴(kuò)展HandlerMapping與WebHandler來處理用戶的請求,先通過Predicate定位到Router然后在經(jīng)過FilterChain的過濾處理,最后定位到下層服務(wù)。同時(shí)官方給我們提供了許多Prdicate與Filter,比如說限流的。從這點(diǎn)來說它的功能比zuul還強(qiáng)大呢,zuul里有的服務(wù)發(fā)現(xiàn),斷路保護(hù)等,Gateway分別通過GlobalFilter與Filter來實(shí)現(xiàn)。

        最后至于Gateway能普及到什么樣的程度,亦或者能不能最終成為統(tǒng)一的網(wǎng)關(guān)標(biāo)準(zhǔn),這個(gè)我也不能再這里有所保證,那么就交給時(shí)間來證明吧。





        粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

        ??????

        ??長按上方微信二維碼 2 秒


        感謝點(diǎn)贊支持下哈 

        瀏覽 50
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            久久国产剧情 | 美女扒开粉嫩尿囗桶爽免费 | 91偷偷鲁偷偷鲁综合网站 | 翔田千里熟妇息孑交尾 | 国产三级农村妇女 | 温碧霞三级全做爰电影 | 极品嫩模被强到高潮呻吟91 | 国产嫩草一区二区 | 美女靠逼免费视频 | 欧美一級黃色A片免費看野花 |