从单体到微服务:Spring Cloud Gateway 断言的深度剖析

Spring Cloud Gateway框架的架构设计非常简单、有效,很多组件的设计都非常值得学习。本文主要对Spring Cloud Gateway三大核心中谓词进行剖析。

前言

在微服务架构还初兴之前,Java Web应用的开发仍然处于单体应用时代。此时的后端是一个完整的、紧密耦合的大系统。每次业务功能的开发,就像积木般的不断堆砌。

彼时的服务更像是一个功能齐全的大超市,所有功能都摆在这一个地方。前端就像顾客,直接和这个大超市打交道,要什么商品(数据)直接去对应的货架(接口)拿就行

单体应用时代,前后端交互方式也相对简单。前端只需将将调用路径的IP路径固定,从而进行接口间的调用即可。

但随着业务的不断发展,这个种单体应用的弊端也逐渐显露。因为一旦某个功能模块出问题,整个系统可能都得停业整顿。同时,扩展新的功能也很麻烦,需要重新规划整个系统的布局。

微服务架构的出现

为了解决单体应用的这些问题,微服务架构 理念应运而生。其实,微服务的架构思想很简单,通俗来看这就好比把大超市拆分成了一个个小便利店,每个便利店只专注于卖一类商品(实现一个特定的功能)。这样做的好处是,每个便利店可以独立管理、独立运营,出了问题也只影响这一个便利店,不会波及其他。而且,要扩展新的商品种类(功能),只需要再开一家新的便利店就行。

不过,这种微服务模式虽然解决了后端服务压力,但却给前端带来了调用艰难的问题。因为此时面对的是一堆微服务。由于不同的服务模块部署在不同的服务器,这导致每个服务的IP地址和端口存在差异,这就迫使前端在调用时,需要维护不同模块的地址信息。这显然与解耦的观念相悖。

要知道,在计算机的世界中,没有什么是加中间层解决不了的 。而此时加在前端后端间的中间层就是网关

网关的兴起

事实上,网关就像景区的导览车,将熟练的知道景区各个景点的位置,当有游客想去对应景点时,直接将其摆渡至对应位置。 当引入网关后 ,前端不用再记住每个后端微服务的地址,而只需要直接与网关交互即可。

换言之,网关就会根据请求地址将其路由至对应后端的微服务,并把响应数据拿回来交给前端。进而大大简化了前端和后端的交互过程,降低了前端开发和维护的难度。

目前主流的网关Spring Cloud Gateway接入后端应用后整个请求轮流转大致如下:

  • Route:代表网关中的一条路由,包含匹配条件和具体的转发目标。
  • Predicate:定义路由的匹配条件,如请求路径、HTTP方法等。
  • Filter:对请求和响应进行处理,可以进行修改、增强或拦截。

本次我们主要聚焦网关中的Predicate的内部原理。而在介绍Predicate内部原理之前,我们先对Java中的Predicate进行一个简单的介绍。

Predicate特性

PredicateJava 8java.util.function包下引入的一个用于处理处理条件判断函数式接口。具体来看Predicate 接口包含四个方法:

  • test(T t):该方法接受一个参数并返回一个布尔值
  • and(Predicate other):与另一个 Predicate 进行组合,实现逻辑与操作。
  • negate():与另一个 Predicate 进行组合,实现逻辑非操作。
  • or(Predicate other):与另一个 Predicate 进行组合,实现逻辑或操作。

test 方法

Predicate 接口通常用于测试一个条件是否成立。例如:

java 复制代码
// Predicate 接口,泛型参数是入参类型,返回布尔值
Predicate<String> predicate = s -> s.contains("utf-8");
boolean flag = predicate.test("utf-8能给你带来收获吗?");
// 打印:god23bin能给你带来收获吗?true
System.out.println("god23bin能给你带来收获吗?" + flag); 

and 方法

为了便于演示,这里准备两个 Predicate

java 复制代码
Predicate<String> startsWithA = (str) -> str.startsWith("A"); // 如果传入的字符串是A开头,则返回 true
Predicate<String> endsWithZ = (str) -> str.endsWith("Z"); // 如果传入的字符串是Z结尾,则返回 true

使用 and 进行组合,操作:

java 复制代码
Predicate<String> startsWithAAndEndsWithZ = startsWithA.and(endsWithZ);
System.out.println(startsWithAAndEndsWithZ.test("ABCDEFZ")); // true
System.out.println(startsWithAAndEndsWithZ.test("BCDEFGH")); // false

negate 方法

使用 negate 进行组合,操作:

java 复制代码
Predicate<String> notStartsWithA = startsWithA.negate();
System.out.println(notStartsWithA.test("ABCDEF")); // false
System.out.println(notStartsWithA.test("BCDEFGH")); // true

or 方法

使用 or 进行组合,操作:

java 复制代码
Predicate<String> startsWithAOrEndsWithZ = startsWithA.or(endsWithZ);
System.out.println(startsWithAOrEndsWithZ.test("ABCDEF")); // true
System.out.println(startsWithAOrEndsWithZ.test("BCDEFGH")); // false

GateWay断言的原理

在网关处理请求的过程中,需要根据前端发来的请求信息,判断该将请求转发到哪个后端服务路径上。而这个进行判断的过程也正是Predicate的最佳应用场景。

为什么在网关中要使用断言呢?当请求进入网关时,会携带路径信息。我们可以根据配置文件中的规则,将这些路径信息作为断言判断的依据。比如使用正则表达式来定义匹配规则,当请求路径符合设定的正则表达式时,断言返回true,否则返回false网关会根据断言结果决定是否放行该请求,这也是网关中过滤器的核心判定逻辑。

GateWay断言配置

Spring Cloud gataWay网关Path的规则大致如下:

yml 复制代码
spring:
  cloud:
    gateway:
      # 配置tote转发规则
      routes:
        - id: order-rote
          uri: lb://server-order
          predicates:
            - Path=/order/**
        - id: product-rote
          uri: lb://server-product
          predicates:
            - Path=/product/**
        - id: bing-rote
          uri: https://cn.bing.com
          predicates:
            - Path=/**

在上述配置文件中定义了三个路由规则,根据不同路径转发到不同的服务或地址。例如:

  1. order-rote:将路径以/order/开头的请求转发到server-order服务。

  2. product-rote:将路径以/product/开头的请求转发到server-product服务。

  3. bing-rote:将所有其他请求转发到https://cn.bing.com

Path断言原理

见识了Spring Cloud中路由断言规则配置后,我们接下来以PathRoutePredicateFactory为例,看看断言工厂类的内部具体实现。

PathRoutePredicateFactory

java 复制代码
public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {

// .....省略其他无关代码

@Override
public Predicate<ServerWebExchange> apply(Config config) {
   final ArrayList<PathPattern> pathPatterns = new ArrayList<>();
   synchronized (this.pathPatternParser) {
      pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchTrailingSlash());
      config.getPatterns().forEach(pattern -> {
         PathPattern pathPattern = this.pathPatternParser.parse(pattern);
         pathPatterns.add(pathPattern);
      });
   }
   return new GatewayPredicate() {
      @Override
      public boolean test(ServerWebExchange exchange) {
         PathContainer path = parsePath(exchange.getRequest().getURI().getRawPath());

         PathPattern match = null;
         for (int i = 0; i < pathPatterns.size(); i++) {
            PathPattern pathPattern = pathPatterns.get(i);
            if (pathPattern.matches(path)) {
               match = pathPattern;
               break;
            }
         }

         if (match != null) {
            traceMatch("Pattern", match.getPatternString(), path, true);
            PathMatchInfo pathMatchInfo = match.matchAndExtract(path);
            putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());
            exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ATTR, match.getPatternString());
            String routeId = (String) exchange.getAttributes().get(GATEWAY_PREDICATE_ROUTE_ATTR);
            if (routeId != null) {
               // populated in RoutePredicateHandlerMapping
               exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR, routeId);
            }
            return true;
         }
         else {
            traceMatch("Pattern", config.getPatterns(), path, false);
            return false;
         }
      }

     
   };
}
}

PathRoutePredicateFactory这个工厂类中,apply方法是核心,它接收配置信息,返回一个断言对象。具体来看,apply方法会接收配置对象 Config对象,它通过 PathPatternParser 解析路径模式,并将匹配逻辑封装到返回的 GatewayPredicater

而在 GatewayPredicater在断言对象的test方法中,会从服务器端的exchange对象中取出请求(request),进而获取请求的路径(uri)。然后,通过循环迭代的方式,将获取到的路径与配置中的路径模式进行正则表达式匹配(matches方法)。一旦匹配成功,就会记录相关信息,提取匹配到的路径信息、路径 ID 等,并将路径 ID 存储到exchange中,最后返回true,表示该请求符合断言规则,可以放行。

除了路径断言,还有其他类型的断言,比如AfterRoutePredicateFactory。它用于判断请求发生的时间是否在给定时间之后,配置时通常会指定一个时间参数,只有在该时间之后发出的请求才会被放行。而处理我们介绍的,Spring Cloud gateWay还有如下断言工厂:

  1. After Route Predicate Factory:该断言通过接收一个日期时间参数,判断请求时间是否在指定时间之后,从而决定是否匹配路由。例如,可以配置让某个路由在特定日期之后才生效。
  2. Before Route Predicate Factory :与After相反,它判断请求时间是否在指定时间之前。比如,可用于设置某个路由在特定日期之前有效。
  3. Between Route Predicate Factory:用于判断请求时间是否在两个指定的日期时间之间。适用于设置路由在特定时间段内生效的场景。
  4. Cookie Route Predicate Factory:通过检查请求中的 Cookie 是否存在以及其值是否匹配指定模式来进行路由判断。例如,根据用户是否携带特定 Cookie 来决定是否将请求路由到特定服务。
  5. Header Route Predicate Factory :根据请求头中的信息进行路由决策。可以检查请求头是否存在某个字段,或者字段值是否匹配指定模式。如根据请求头中的User-Agent来决定路由。
  6. Host Route Predicate Factory:基于请求的主机名进行路由匹配。可以配置允许特定主机名的请求通过路由,常用于多租户或基于不同域名的路由场景。
  7. Method Route Predicate Factory:根据 HTTP 请求方法(GET、POST、PUT 等)来判断是否匹配路由。例如,只允许 POST 请求通过某个路由。
  8. Path Route Predicate Factory:通过匹配请求的路径来决定是否路由。可以使用 Ant 风格的路径模式进行匹配,灵活地根据不同路径将请求路由到不同服务。
  9. Query Route Predicate Factory:根据请求中的查询参数进行路由判断。可以检查是否存在特定参数,或者参数值是否符合指定条件。
  10. RemoteAddr Route Predicate Factory:根据请求的远程地址来进行路由决策。可用于限制某些 IP 地址段的访问,或者根据客户端的来源 IP 进行路由。
  11. Weight Route Predicate Factory:为不同的路由分配权重,根据权重来决定请求被路由到不同服务的概率。适用于负载均衡和流量分配的场景。

虽然断言类型众多,但核心原理都是类似的,掌握了一种,其他的也就触类旁通了,感兴趣的读者可私下自行对其他类型的断言工厂进行研究。

总结

本文从单体应用到微服务架构的演进历程切入,深入剖析了Spring Cloud Gateway诞生的必然性。在阐述网关三大核心组件时,着重介绍了断言的作用,并以 PathRoutePredicateFactory 为例,详细解析Spring Cloud Gateway中断言的实现机制。

事实上,虽然Spring Cloud gate Way中的断言类型丰富多样,但其核心原理一脉相承。

相关推荐
未完结小说22 分钟前
服务注册与发现(nacos)
后端
寒也26 分钟前
识别法院PDF文件特定字段并插入数据库【正则表达式+本地化部署】
java·数据库·正则表达式·eclipse·pdf·达梦·ruoyi
AI智能科技用户79463297828 分钟前
okcc呼叫中心两个sip对接线路外呼任务怎么设置才能一个任务对应yigesip中继?
人工智能·后端
中国lanwp37 分钟前
Spring Boot 版本与对应 JDK 版本兼容性
java·开发语言·spring boot
懒虫虫~40 分钟前
Spring源码中关于抽象方法且是个空实现这样设计的思考
java·后端·spring
码银42 分钟前
【Java】接口interface学习
java·开发语言·学习
雷渊1 小时前
DDD的分层架构是怎么样的?
后端
DKPT1 小时前
重构之去除多余的if-else
java·开发语言·笔记·学习·面试
蓝黑20201 小时前
Java如何在遍历集合时删除特定元素
java
会有猫1 小时前
阿里云OSS挂载到Linux
后端