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

1. Gateway快速上手

添加依赖:

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

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

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

我们编辑application.yaml,定义如下配置:

  1. spring:
  2. application:
  3. name: gateway
  4. cloud:
  5. gateway:
  6. routes:
  7. - id: before_route
  8. uri: http://www.baidu.com
  9. predicates:
  10. - Path=/baidu
  11. server:
  12. port: 8088

此时当我们访问路径中包含/baidu的,gateway将会帮我们转发至百度页面

2. 工作流程

在这里我贴上官网的一张图:

在这里我想结合源代码来说明其流程,这里面有个关键的类,叫RoutePredicateHandlerMapping,我们可以发现这个类有如下特点:

  1. public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
  2. // ....省略部分代码
  3. @Override
  4. protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
  5. // don't handle requests on management port if set and different than server port
  6. if (this.managementPortType == DIFFERENT && this.managementPort != null
  7. && exchange.getRequest().getURI().getPort() == this.managementPort) {
  8. return Mono.empty();
  9. }
  10. exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());
  11. return lookupRoute(exchange)
  12. // .log("route-predicate-handler-mapping", Level.FINER) //name this
  13. .flatMap((Function<Route, Mono<?>>) r -> {
  14. exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
  15. if (logger.isDebugEnabled()) {
  16. logger.debug(
  17. "Mapping [" + getExchangeDesc(exchange) + "] to " + r);
  18. }
  19. exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
  20. return Mono.just(webHandler);
  21. }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
  22. exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
  23. if (logger.isTraceEnabled()) {
  24. logger.trace("No RouteDefinition found for ["
  25. + getExchangeDesc(exchange) + "]");
  26. }
  27. })));
  28. }
  29. //...省略部分代码
  30. }
  • 此类继承了AbstractHandlerMapping,注意这里的是reactive包下的,也就是webflux提供的handlermapping,其作用等同于webmvc的handlermapping,其作用是将请求映射找到对应的handler来处理。
  • 在这里处理的关键就是先寻找合适的route,关键的方法为lookupRoute():
  1. protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
  2. return this.routeLocator.getRoutes()
  3. // individually filter routes so that filterWhen error delaying is not a
  4. // problem
  5. .concatMap(route -> Mono.just(route).filterWhen(r -> {
  6. // add the current route we are testing
  7. exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
  8. return r.getPredicate().apply(exchange);
  9. })
  10. // instead of immediately stopping main flux due to error, log and
  11. // swallow it
  12. .doOnError(e -> logger.error(
  13. "Error applying predicate for route: " + route.getId(),
  14. e))
  15. .onErrorResume(e -> Mono.empty()))
  16. // .defaultIfEmpty() put a static Route not found
  17. // or .switchIfEmpty()
  18. // .switchIfEmpty(Mono.<Route>empty().log("noroute"))
  19. .next()
  20. // TODO: error handling
  21. .map(route -> {
  22. if (logger.isDebugEnabled()) {
  23. logger.debug("Route matched: " + route.getId());
  24. }
  25. validateRoute(route, exchange);
  26. return route;
  27. });
  28. /*
  29. * TODO: trace logging if (logger.isTraceEnabled()) {
  30. * logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); }
  31. */
  32. }
  • 其中RouteLocator的接口作用是获取Route定义,那么在GatewayAutoConfiguaration里有相关的配置,大家可自行查阅:
  1. @Bean
  2. public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
  3. List<GatewayFilterFactory> GatewayFilters,
  4. List<RoutePredicateFactory> predicates,
  5. RouteDefinitionLocator routeDefinitionLocator,
  6. @Qualifier("webFluxConversionService") ConversionService conversionService) {
  7. return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,
  8. GatewayFilters, properties, conversionService);
  9. }
  • 然后在注释add the current route we are testing处可以得到一个结论,其是根据Predicate的声明条件过滤出合适的Route
  • 最终拿到FilteringWebHandler作为它的返回值,这个类是真正意义上处理请求的类,它实现了webflux提供的WebHandler接口:
  1. public class FilteringWebHandler implements WebHandler {
  2. //.....省略其它代码
  3. @Override
  4. public Mono<Void> handle(ServerWebExchange exchange) {
  5. //拿到当前的route
  6. Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
  7. //获取所有的gatewayFilter
  8. List<GatewayFilter> gatewayFilters = route.getFilters();
  9. //获取全局过滤器
  10. List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
  11. combined.addAll(gatewayFilters);
  12. // TODO: needed or cached?
  13. AnnotationAwareOrderComparator.sort(combined);
  14. if (logger.isDebugEnabled()) {
  15. logger.debug("Sorted gatewayFilterFactories: " + combined);
  16. }
  17. //交给默认的过滤器链执行所有的过滤操作
  18. return new DefaultGatewayFilterChain(combined).filter(exchange);
  19. }
  20. //....省略其它代码
  21. }

在这里可以看到它的实际处理方式是委派给过滤器链进行处理请求操作的

3. Predicate

Spring Cloud Gateway包含许多内置的Predicate Factory。所有的Predicate都匹配HTTP请求的不同属性。如果配置类多个Predicate, 那么必须满足所有的predicate才可以,官网上列举的内置的Predicate,我在这里不做过多的说明,请大家参考:地址,predicate的实现可以在org.springframework.cloud.gateway.handler.predicate的包下找到。

3.1、自定义Predicate

先改一下application.yaml中的配置:

  1. spring:
  2. application:
  3. name: gateway
  4. cloud:
  5. gateway:
  6. routes:
  7. - id: before_route
  8. uri: http://www.baidu.com
  9. predicates:
  10. - Number=1

默认命名规则:名称RoutePredicateFactory,在这里我们可以看到如下代码规则用以解析Predicate的名称,该代码在NameUtils当中:

  1. public static String normalizeRoutePredicateName(
  2. Class<? extends RoutePredicateFactory> clazz) {
  3. return removeGarbage(clazz.getSimpleName()
  4. .replace(RoutePredicateFactory.class.getSimpleName(), ""));
  5. }

那么在这里我们就按照如上规则建立对应的NumberRoutePredicateFactory,代码如下:

  1. @Component
  2. public class NumberRoutePredicateFactory extends AbstractRoutePredicateFactory<NumberRoutePredicateFactory.Config> {
  3. public NumberRoutePredicateFactory() {
  4. super(Config.class);
  5. }
  6. @Override
  7. public List<String> shortcutFieldOrder() {
  8. return Arrays.asList("number");
  9. }
  10. @Override
  11. public ShortcutType shortcutType() {
  12. return ShortcutType.GATHER_LIST;
  13. }
  14. @Override
  15. public Predicate<ServerWebExchange> apply(Config config) {
  16. return new GatewayPredicate() {
  17. @Override
  18. public boolean test(ServerWebExchange serverWebExchange) {
  19. String number = serverWebExchange.getRequest().getQueryParams().getFirst("number");
  20. return config.number == Integer.parseInt(number);
  21. }
  22. };
  23. }
  24. public static class Config {
  25. private int number;
  26. public int getNumber() {
  27. return number;
  28. }
  29. public void setNumber(int number) {
  30. this.number = number;
  31. }
  32. }
  33. }
  • 该类可以继承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.1、负载均衡的实现

与zuul类似,Gateway也可以作为服务端的负载均衡,那么负载均衡的处理关键就是与Ribbon集成,那么Gateway是利用GlobalFilter进行实现的,它的实现类是LoadBalancerClientFilter:

  1. public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
  2. protected final LoadBalancerClient loadBalancer;
  3. private LoadBalancerProperties properties;
  4. //....
  5. @Override
  6. @SuppressWarnings("Duplicates")
  7. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  8. // preserve the original url
  9. addOriginalRequestUrl(exchange, url);
  10. log.trace("LoadBalancerClientFilter url before: " + url);
  11. //选择一个服务实例
  12. final ServiceInstance instance = choose(exchange);
  13. if (instance == null) {
  14. throw NotFoundException.create(properties.isUse404(),
  15. "Unable to find instance for " + url.getHost());
  16. }
  17. URI uri = exchange.getRequest().getURI();
  18. // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
  19. // if the loadbalancer doesn't provide one.
  20. //判断协议类型
  21. String overrideScheme = instance.isSecure() ? "https" : "http";
  22. if (schemePrefix != null) {
  23. overrideScheme = url.getScheme();
  24. }
  25. //重构uri地址
  26. URI requestUrl = loadBalancer.reconstructURI(
  27. new DelegatingServiceInstance(instance, overrideScheme), uri);
  28. //...
  29. return chain.filter(exchange);
  30. }
  31. }

在这里我们可以看到这里它是基于Spring-Cloud-Commons规范里的LoadBalanceClient包装实现的。

4.2、集成Hystrix

Gateway同样也可以和Hystrix进行集成,这里面的关键类是HystrixGatewayFilterFactory,这里面的关键是RouteHystrixCommand该类继承了HystrixObservableCommand:

  1. @Override
  2. protected Observable<Void> construct() {
  3. // 执行过滤器链
  4. return RxReactiveStreams.toObservable(this.chain.filter(exchange));//1
  5. }
  6. @Override
  7. protected Observable<Void> resumeWithFallback() {
  8. if (this.fallbackUri == null) {
  9. return super.resumeWithFallback();
  10. }
  11. // TODO: copied from RouteToRequestUrlFilter
  12. URI uri = exchange.getRequest().getURI();
  13. // TODO: assume always?
  14. boolean encoded = containsEncodedParts(uri);
  15. URI requestUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null)
  16. .uri(this.fallbackUri).scheme(null).build(encoded).toUri();//2
  17. exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
  18. addExceptionDetails();
  19. ServerHttpRequest request = this.exchange.getRequest().mutate()
  20. .uri(requestUrl).build();
  21. ServerWebExchange mutated = exchange.mutate().request(request).build();
  22. return RxReactiveStreams.toObservable(getDispatcherHandler().handle(mutated));//3
  23. }
  • 在代码1处会执行滤器链,写到此处的代码会被统一加上hystrix的保护
  • 在代码2处再是执行回退的方法,根据fallbackUri构建一个回退请求地址
  • 在代码3处获取WebFlux的总控制器DispatcherHandler进行回退地址的处理

5、服务发现

服务发现对于Gateway来说也是个非常重要的内容,Gateway在这里定义了一个核心接口叫做:RouteDefinitionLocator,这个接口用于获取Route的定义,服务发现的机制实现了该接口:

  1. public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
  2. @Override
  3. public Flux<RouteDefinition> getRouteDefinitions() {
  4. //....省略部分代码
  5. return Flux.fromIterable(discoveryClient.getServices())//获取所有服务
  6. .map(discoveryClient::getInstances) //映射转换所有服务实例
  7. .filter(instances -> !instances.isEmpty()) //过滤出不为空的服务实例
  8. .map(instances -> instances.get(0)).filter(includePredicate)//根据properites里的include表达式过滤实例
  9. .map(instance -> {
  10. /*
  11. 构建Route的定义
  12. */
  13. String serviceId = instance.getServiceId();
  14. RouteDefinition routeDefinition = new RouteDefinition();
  15. routeDefinition.setId(this.routeIdPrefix + serviceId);
  16. String uri = urlExpr.getValue(evalCtxt, instance, String.class);
  17. routeDefinition.setUri(URI.create(uri));
  18. final ServiceInstance instanceForEval = new DelegatingServiceInstance(
  19. instance, properties);
  20. //添加Predicate
  21. for (PredicateDefinition original : this.properties.getPredicates()) {
  22. PredicateDefinition predicate = new PredicateDefinition();
  23. predicate.setName(original.getName());
  24. for (Map.Entry<String, String> entry : original.getArgs()
  25. .entrySet()) {
  26. String value = getValueFromExpr(evalCtxt, parser,
  27. instanceForEval, entry);
  28. predicate.addArg(entry.getKey(), value);
  29. }
  30. routeDefinition.getPredicates().add(predicate);
  31. }
  32. //添加filter
  33. for (FilterDefinition original : this.properties.getFilters()) {
  34. FilterDefinition filter = new FilterDefinition();
  35. filter.setName(original.getName());
  36. for (Map.Entry<String, String> entry : original.getArgs()
  37. .entrySet()) {
  38. String value = getValueFromExpr(evalCtxt, parser,
  39. instanceForEval, entry);
  40. filter.addArg(entry.getKey(), value);
  41. }
  42. routeDefinition.getFilters().add(filter);
  43. }
  44. return routeDefinition;
  45. });
  46. }
  47. }

由此我们可以知道,这里面利用DiscoveryClient获取所有的服务实例并将每个实例构建为一个Route,不过在此之前,在自动装配的类GatewayDiscoveryClientAutoConfiguration里已经配置了默认的Predicate与Filter,它会预先帮我们配置默认的Predicate与Filter:

  1. public static List<PredicateDefinition> initPredicates() {
  2. ArrayList<PredicateDefinition> definitions = new ArrayList<>();
  3. // TODO: add a predicate that matches the url at /serviceId?
  4. // add a predicate that matches the url at /serviceId/**
  5. PredicateDefinition predicate = new PredicateDefinition();
  6. predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class));
  7. predicate.addArg(PATTERN_KEY, "'/'+serviceId+'/**'");
  8. definitions.add(predicate);
  9. return definitions;
  10. }
  11. public static List<FilterDefinition> initFilters() {
  12. ArrayList<FilterDefinition> definitions = new ArrayList<>();
  13. // add a filter that removes /serviceId by default
  14. FilterDefinition filter = new FilterDefinition();
  15. filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
  16. String regex = "'/' + serviceId + '/(?<remaining>.*)'";
  17. String replacement = "'/${remaining}'";
  18. filter.addArg(REGEXP_KEY, regex);
  19. filter.addArg(REPLACEMENT_KEY, replacement);
  20. definitions.add(filter);
  21. return definitions;
  22. }

这里面主要会根据ServiceId构建为 Path=/serviceId/**的Predicate和路由至对应服务前把ServiceId去掉的filter

6、总结

根据上述说明,我仅仅选取了两个比较典型意义的Predicate与Filter代码进行说明,由于官网上没有说明自定义Predicate,我在这里索性写了个简单的例子,那么自定义Filter的例子可以参考官网地址:

这里需要吐槽一下官方 什么时候能把TODO补充完整的呢?

Gateway是基于Webflux实现的,它通过扩展HandlerMapping与WebHandler来处理用户的请求,先通过Predicate定位到Router然后在经过FilterChain的过滤处理,最后定位到下层服务。同时官方给我们提供了许多Prdicate与Filter,比如说限流的。从这点来说它的功能比zuul还强大呢,zuul里有的服务发现,断路保护等,Gateway分别通过GlobalFilter与Filter来实现。

最后至于Gateway能普及到什么样的程度,亦或者能不能最终成为统一的网关标准,这个我也不能再这里有所保证,那么就交给时间来证明吧。

深入理解SpringCloud之Gateway的更多相关文章

  1. 【SpringCloud】Gateway 配置全局过滤器获取请求参数和响应值

    [SpringCloud]Gateway 配置全局过滤器获取请求参数和响应值 实现Ordered接口getOrder()方法,数值越小越靠前执行,记得这一点就OK了. 获取请求参数RequestBod ...

  2. SpringCloud之Gateway

    一.为什么选择SpringCloud Gateway而不是Zuul? Gateway和Zuul的职责一样,都承担着请求分发,类似Nginx分发到后端服务器. 1.SpingCloud Gateway ...

  3. springcloud组件gateway断言(Predicate)

    Spring Cloud Gateway是SpringCloud的全新子项目,该项目基于Spring5.x.SpringBoot2.x技术版本进行编写,意在提供简单方便.可扩展的统一API路由管理方式 ...

  4. 深入理解SpringCloud之引导程序应用上下文

    tips:我希望通过这篇文章来给对于bootstrap还不理解的朋友带来帮助.当然这篇文章不仅仅是讲解知识,我更希望给广大朋友带来学习与理解官方文档的一种思路.阅读本文前,建议大家对SpringBoo ...

  5. 微服务SpringCloud之GateWay路由

    在前面博客学习了网关zuul,今天学下spring官方自带的网关spring cloud gateway.Zuul(1.x) 基于 Servlet,使用阻塞 API,它不支持任何长连接,如 WebSo ...

  6. springcloud中gateway的实际应用

    之前我一直用的是Zuul网关,用过gateway以后感觉比Zuul功能还是强大很多. Spring Cloud Gateway是基于Spring5.0,Spring Boot2.0和Project R ...

  7. springcloud zookeeper+gateway

    搭建springcloud项目的时候以 zookeeper为注册中心  gateway为路由 启动时出现以下报错: ****************************************** ...

  8. SpringCloud实战 | 第四篇:SpringCloud整合Gateway实现API网关

    一. 前言 微服务实战系列是基于开源微服务项目 有来商城youlai-mall 版本升级为背景来开展的,本篇则是讲述API网关使用Gateway替代Zuul,有兴趣的朋友可以进去给个star,非常感谢 ...

  9. SpringCloud创建Gateway模块

    1.说明 本文详细介绍Spring Cloud创建Gateway模块的方法, 基于已经创建好的Spring Cloud父工程, 请参考SpringCloud创建项目父工程, 和已经创建好的Eureka ...

随机推荐

  1. Python---列表的学习(一)

    本文将介绍列表和列表的使用: 我对列表的理解是和c,c++中的数组,vector,数据结构-链表-栈-队列,都很相似,因此列表很强大(相对于c,c++来说),所以在python的学习中列表是个重点. ...

  2. 2019icpc南昌网络赛_I_Yukino With Subinterval

    题意 给定一个序列,两种操作,单点修改,询问区间\([l,r]\)值域在\([x,y]\)范围内的连续段个数. 分析 原数组为\(a\),构造一个新的数组\(b\),\(b[i]=(a[i]==a[i ...

  3. 【Offer】[43] 【1~n整数中1出现的次数】

    题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 输入一个整数n,求1~n这n个整数的十进制表示中1出现的次数.例如,输入12, 1~12这些整数中包含1的数字有1.10.11和12,1 ...

  4. odoo12从零开始:三、2)odoo模型层

    前言 上一篇文章(创建你的第一个应用模块(module))已经大致描述了odoo的模型层(model)和视图层(view),这一篇文章,我们将系统地介绍有关于model的知识,其中包括: 1.模型的类 ...

  5. SpringBoot使用注解的方式构建Elasticsearch查询语句,实现多条件的复杂查询

    背景&痛点 通过ES进行查询,如果需要新增查询条件,则每次都需要进行硬编码,然后实现对应的查询功能.这样不仅开发工作量大,而且如果有多个不同的索引对象需要进行同样的查询,则需要开发多次,代码复 ...

  6. Winform中自定义xml配置文件,并配置获取文件路径

    场景 在Winform程序中,需要将一些配置项存到配置文件中,这时就需要自定义xml的配置文件格式.并在一些工具类中去获取配置文件的路径并加载其内容. 关注公众号霸道的程序猿获取编程相关电子书.教程推 ...

  7. 中文保存在properties乱码的解决

    方法:将中文转换为Native/ASCII编码:(比较好的一种解决方法,也必须设置好properties的字符编码(utf-8):已经试验成功) 网站:http://tool.oschina.net/ ...

  8. [Spark] 06 - What is Spark Streaming

    前言 Ref: 一文读懂 Spark 和 Spark Streaming[简明扼要的概览] 在讲解 "流计算" 之前,先做一个简单的回顾,亲! 一.MapReduce 的问题所在 ...

  9. css/js禁止点击元素

    css禁止点击页面元素,只需一句代码即可解决: pointer-events: none; 如果用js来控制的话那就是: $('#test').click(function(){ return fal ...

  10. 阿里巴巴 Sentinel + InfluxDB + Chronograf 实现监控大屏

    前言 在上一篇推文中,我们使用时序数据库 InfluxDb 做了流控数据存储,但是数据存储不是目的,分析监控预警才是最终目标,那么问题来了,如何更好的实现呢?用过阿里巴巴 Sentinel 控制台的小 ...