<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深入理解SpringCloud之Gateway

          共 20829字,需瀏覽 42分鐘

           ·

          2021-03-14 18:19

          點(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ō)我本身還是在觀望中。

          1. 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è)面

          1. 工作流程
            在這里我貼上官網(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
           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的聲明條件過(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)求操作的

          1. 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)贊支持下哈 

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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99爱视频在线 | 天天操天天摸天天日天天爱 | 欧美精产国品一区二区区别 | 18禁全裸美女麻豆网站 | 99热在线观看6 |