写点什么

SpringCloud Gateway 路由断言

用户头像
中原银行
关注
发布于: 2021 年 06 月 26 日
SpringCloud Gateway 路由断言

SpringCloud Gateway 系列文章共五篇,由我行开发工程师 @Aaron 提供,带大家深入剖析 Gateway 工作原理,及如何基于 Gateway 进行定制化开发以适应企业特定环境需求。


第一篇:SpringCloud Gateway 动态路由

第二篇:SpringCloud Gateway 路由数量对性能的影响研究

第三篇:SpringCloud Gateway 路由转发性能优化

第四篇:SpringCloud Gateway 断言

第五篇:SpringCloud Gateway 过滤器。


断言即判断一个命题的真伪,路由断言则用于判断该路由是否可应用于当前请求。

路由定义

见上图,Route 各属性分别为:

  • id,路由的唯一编号可用于定义一个路由,gateway 中用,一些的过滤器会根据 id 是否属于某个集合做特定的操作;

  • order,路由的优先级,值越小,优先级越高(路由表按此排序);

  • metadata,可以定义连接超时、响应超时等参数,将来可能会拓展用途;

  • uri,上游地址,即 upstream,可以是一个确定的服务地址,也可以是一个 lb 开头的地址,表示可通过服务发现,发现上游节点;

  • filters,过滤器列表,请求根据断言命中一个路由后,会依次执行过滤列表的过滤器(《SpringCloud Gateway 过滤器》将详细介绍);

  • predicate,路由断言,本文重点,用于判断路由是否匹配当前路由。


接下来我们看 AsyncPredicate 是如何定义的:

public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {//...}
复制代码

我们对比下 jdk 中的内置 FunctionalInterface:

public interface Predicate<T> {//...}
public interface Function<T, R> {//...}
复制代码

如果你能认识到Predicate<T>其实是一个特殊的Function<T, R>,它的返回类型R规定为Boolean,你就能轻松的理解 AsyncPredicate<T>:它是一个响应式编程模型下的Predicate<T>,返回值为Mono<Boolean>

工作原理

在前文动态路由章节,我们已经在路由查找过程中,看到过断言时如何工作的,见RoutePredicateHandlerMapping 源码 126-134 行

protected Mono<Route> lookupRoute(ServerWebExchange exchange) {  // routeLocator.getRoutes() 获取的路由已经排序  return this.routeLocator.getRoutes()      // concatMap 与 flatmap的区别就是能保持执行顺序不变      .concatMap(route ->              // filterWhen 发生订阅时执行,就相当于按序遍历符合条件的记录              Mono.just(route).filterWhen(r -> {                exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());                // 从route中获取断言,并判断是否为真,并返回                return r.getPredicate().apply(exchange);              })  //...}
复制代码

filterWhen 接收的是一个Function<T, Publisher<Boolean>> 的函数式接口,也就是上面我们介绍的 AsyncPredicate<T>

前文动态路由章节,我们介绍过Route是有RouteDefinition转换而来,执行转换的类为

  • RouteDefinitionRouteLocator,源码 168-174 行


  • private Route convertToRoute(RouteDefinition routeDefinition) {  // combinePredicates 根据路由定义中的断言定义组合为路由断言  AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);  List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);
    return Route.async(routeDefinition).asyncPredicate(predicate) .replaceFilters(gatewayFilters).build();}
    复制代码

    源码 241-286 行

    // 该方法使用 and 关系 组合了所有的断言定义private AsyncPredicate<ServerWebExchange> combinePredicates(     RouteDefinition routeDefinition) {   List<PredicateDefinition> predicates = routeDefinition.getPredicates();   if (predicates == null || predicates.isEmpty()) {     // this is a very rare case, but possible, just match all     return AsyncPredicate.from(exchange -> true);   }   AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition,       predicates.get(0));
    for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) { AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition, andPredicate); // and 表明多个断言的关系,也就是必须所有条件都符合才能命中一个路由 predicate = predicate.and(found); }
    return predicate; }
    // 根据路由定义中的断言定义,从指定 断言工厂 获取一个实例化 断言 private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) { RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName()); // 省略非关键代码 return factory.applyAsync(config); }
    复制代码

    断言工厂

    上文我们已经解析清楚,一个请求是否命中某个路由,最终是由路由定义中的断言定义对应的断言工厂的applyAsync 方法决定。

    见上图,RoutePredicateFactory 是一个FunctionalInterface 其唯一抽象方法为:

    Predicate<ServerWebExchange> apply(C config);
    复制代码

    具体的RoutePredicateFactory的实现工厂类,需实现上述方法。

    Gateway 内置的路由工厂有:

    以上每个工厂的apply都实现了如何根据配置及ServerWebExchange来判断当前请求是否匹配断言(如果需要自定义断言工厂,也需要实现此逻辑)。

    每个路由工厂如何配置,官方文档均有示例。

    我们看如下示例:

    spring:  cloud:    gateway:      routes:      - id: path_route        uri: https://example.org        predicates:        - Path=/red/**,/blue/**
    复制代码

    关键字 Path 表示采用 PathRoutePredicateFactory 工厂创建该断言;

    Path 后是逗号分隔的有序字符串数据,表示只要匹配任意一个 path,该断言即为真(见apply方法),路由命中。

    下面方法可用于创建动态路由时,生成断言定义:

      /**   * 生成断言定义   *   * @param type 断言名称   * @param orderedArgs 有序的参数数组   * @return 路由断言定义   */  public static PredicateDefinition createPredicate(@NotNull PredicateType type,      @NotNull String... orderedArgs) {    PredicateDefinition definition = new PredicateDefinition();    definition.setName(type.name());    int order = 0;    for (String arg : orderedArgs) {      if (StringUtils.isNotBlank(arg)) {        definition.addArg(NameUtils.generateName(order++), arg);      }    }    return definition;  }
    复制代码

    断言定义的 Name 即为断言工厂的前缀字符串。

    总结

    本文较为详细的分析了 Gateway 是如何判断一个请求应采用哪个路由进行处理的原理,并进一步分析了断言工厂的实现,以及配置文件及断言定义如何与断言工厂相对应。

    希望通过本文,你能帮助你加深对 Gateway 的理解。

    发布于: 2021 年 06 月 26 日阅读数: 14
    用户头像

    中原银行

    关注

    打造科技驱动、创新引领的数字化未来银行。 2020.02.06 加入

    中原银行是河南省属法人银行,总部位于河南省郑州市。我行坚持“科技立行、科技兴行”,秉承“稳健 创新 进取 高效”理念,发展移动金融、线上金融,提升综合金融服务能力,金融科技应用水平居国内城商行领先地位。

    评论

    发布
    暂无评论
    SpringCloud Gateway 路由断言