深刻理解SpringCloud之Gateway

虽然在服务网关有了zuul(在这里是zuul1),其自己仍是基于servlet实现的,换言之仍是同步阻塞方式的实现。就其自己来说它的最根本弊端也是再此。而非阻塞带来的好处不言而喻,高效利用线程资源进而提升吞吐量,基于此Spring率先拿出针对于web的杀手锏,对,就是webflux。而Gateway自己就是基于webflux基础之上实现的。毕竟spring推出的技术,固然要得以推广嘛。不过就国内的软件公司而言为了稳定而选择保守,所以就这项技术的广度来讲我自己仍是在观望中。html

1. Gateway快速上手

添加依赖:java

implementation 'org.springframework.cloud:spring-cloud-starter-gateway'

这里请注意,springcloud-gateway是基于netty运行的环境,在servlet容器环境或者把它构建为war包运行的话是不容许的,所以在项目当中没有必要添加spring-boot-starter-web。在gateway当中有三个重要的元素他们分别是:react

  • Route 是最核心的路由元素,它定义了ID,目标URI ,predicates的集合与filter的集合,若是Predicate聚合返回真,则匹配该路由
  • Predicate 基于java8的函数接口Predicate,其输入参数类型ServerWebExchange,其做用就是容许开发人员根据当前的http请求进行规则的匹配,好比说http请求头,请求时间等,匹配的结果将决定执行哪一种路由
  • Filter为GatewayFilter,它是由特殊的工厂构建,经过Filter能够在下层请求路由先后改变http请求与响应

咱们编辑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

2. 工做流程

在这里我贴上官网的一张图: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) + "]");
                        }
                    })));
        }
      
      //...省略部分代码
    
    }
  • 此类继承了AbstractHandlerMapping,注意这里的是reactive包下的,也就是webflux提供的handlermapping,其做用等同于webmvc的handlermapping,其做用是将请求映射找到对应的handler来处理。
  • 在这里处理的关键就是先寻找合适的route,关键的方法为lookupRoute():
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()); }
             */
        }
  • 其中RouteLocator的接口做用是获取Route定义,那么在GatewayAutoConfiguaration里有相关的配置,你们可自行查阅:
@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);
            }
  • 而后在注释add the current route we are testing处能够获得一个结论,其是根据Predicate的声明条件过滤出合适的Route
  • 最终拿到FilteringWebHandler做为它的返回值,这个类是真正意义上处理请求的类,它实现了webflux提供的WebHandler接口:
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);
        }
      
        //....省略其它代码
      }

在这里能够看到它的实际处理方式是委派给过滤器链进行处理请求操做的负载均衡

3. Predicate

Spring Cloud Gateway包含许多内置的Predicate Factory。全部的Predicate都匹配HTTP请求的不一样属性。若是配置类多个Predicate, 那么必须知足全部的predicate才能够,官网上列举的内置的Predicate,我在这里不作过多的说明,请你们参考:地址,predicate的实现能够在org.springframework.cloud.gateway.handler.predicate的包下找到。ide

3.一、自定义Predicate

先改一下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;
            }
        }
    }
  • 该类能够继承AbstractRoutePredicateFactory,同时须要注册为spring的Bean
  • 在此类当中按照规范来说,须要定义一个内部类,该类的做用用于封装application.yaml中的配置,Number=1这个配置会按照规则进行封装,这个规则由如下几项决定:
    • ShortcutType,该值是枚举类型,分别是
      • DEFAULT :按照shortcutFieldOrder顺序依次赋值
      • GATHER_LIST:shortcutFiledOrder只能有一个值,若是参数有多个拼成一个集合
      • GATHER_LIST_TAIL_FLAG:shortcutFiledOrder只能有两个值,其中最后一个值为true或者false,其他的值变成一个集合付给第一个值
    • shortcutFieldOrder,这个值决定了Config中配置的属性,配置的参数都会被封装到该属性当中

4. Filter

Gateway中的filter能够分为(GlobalFilter)全局过滤器与普经过滤器,过滤器能够在路由到代理服务的先后改变请求与响应。在这里我会列举两个常见的filter给你们用做参考:

4.一、负载均衡的实现

与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包装实现的。

4.二、集成Hystrix

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
            }
  • 在代码1处会执行滤器链,写到此处的代码会被统一加上hystrix的保护
  • 在代码2处再是执行回退的方法,根据fallbackUri构建一个回退请求地址
  • 在代码3处获取WebFlux的总控制器DispatcherHandler进行回退地址的处理

五、服务发现

服务发现对于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能普及到什么样的程度,亦或者能不能最终成为统一的网关标准,这个我也不能再这里有所保证,那么就交给时间来证实吧。

相关文章
相关标签/搜索