Spring Cloud Gateway
框架的架构设计非常简单、有效,很多组件的设计都非常值得学习。本文主要对Spring Cloud Gateway
三大核心中谓词
进行剖析。
前言
在微服务架构还初兴之前,Java Web
应用的开发仍然处于单体应用
时代。此时的后端是一个完整的、紧密耦合的大系统。每次业务功能的开发,就像积木般的不断堆砌。
彼时的服务更像是一个功能齐全的大超市,所有功能都摆在这一个地方。前端就像顾客,直接和这个大超市打交道,要什么商品(数据)直接去对应的货架(接口)拿就行。

在单体应用
时代,前后端交互方式也相对简单。前端只需将将调用路径的IP
路径固定,从而进行接口间的调用即可。
但随着业务的不断发展,这个种单体应用的弊端也逐渐显露。因为一旦某个功能模块出问题,整个系统可能都得停业整顿。同时,扩展新的功能也很麻烦,需要重新规划整个系统的布局。
微服务架构的出现
为了解决单体应用的这些问题,微服务架构 理念应运而生。其实,微服务的架构思想很简单,通俗来看这就好比把大超市拆分成了一个个小便利店,每个便利店只专注于卖一类商品(实现一个特定的功能)。这样做的好处是,每个便利店可以独立管理、独立运营,出了问题也只影响这一个便利店,不会波及其他。而且,要扩展新的商品种类(功能),只需要再开一家新的便利店就行。

不过,这种微服务
模式虽然解决了后端服务压力,但却给前端带来了调用艰难的问题。因为此时面对的是一堆微服务
。由于不同的服务模块部署在不同的服务器,这导致每个服务的IP
地址和端口
存在差异,这就迫使前端在调用时,需要维护不同模块的地址信息。这显然与解耦的观念相悖。
要知道,在计算机的世界中,没有什么是加中间层解决不了的 。而此时加在前端
和后端
间的中间层就是网关
。
网关的兴起
事实上,网关就像景区的导览车,将熟练的知道景区各个景点的位置,当有游客想去对应景点时,直接将其摆渡至对应位置。 当引入网关
后 ,前端不用再记住每个后端微服务
的地址,而只需要直接与网关交互即可。
换言之,网关
就会根据请求地址将其路由至对应后端的微服务,并把响应数据拿回来交给前端。进而大大简化了前端和后端的交互过程,降低了前端开发和维护的难度。
目前主流的网关Spring Cloud Gateway
接入后端应用后整个请求轮流转大致如下:

Route
:代表网关中的一条路由,包含匹配条件和具体的转发目标。Predicate
:定义路由的匹配条件,如请求路径、HTTP
方法等。Filter
:对请求和响应进行处理,可以进行修改、增强或拦截。
本次我们主要聚焦网关
中的Predicate
的内部原理。而在介绍Predicate
内部原理之前,我们先对Java
中的Predicate
进行一个简单的介绍。
Predicate
特性
Predicate
是Java 8
在java.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=/**
在上述配置文件中定义了三个路由规则,根据不同路径转发到不同的服务或地址。例如:
-
order-rote
:将路径以/order/
开头的请求转发到server-order
服务。 -
product-rote
:将路径以/product/
开头的请求转发到server-product
服务。 -
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
还有如下断言工厂:
- After Route Predicate Factory:该断言通过接收一个日期时间参数,判断请求时间是否在指定时间之后,从而决定是否匹配路由。例如,可以配置让某个路由在特定日期之后才生效。
- Before Route Predicate Factory :与
After
相反,它判断请求时间是否在指定时间之前。比如,可用于设置某个路由在特定日期之前有效。 - Between Route Predicate Factory:用于判断请求时间是否在两个指定的日期时间之间。适用于设置路由在特定时间段内生效的场景。
- Cookie Route Predicate Factory:通过检查请求中的 Cookie 是否存在以及其值是否匹配指定模式来进行路由判断。例如,根据用户是否携带特定 Cookie 来决定是否将请求路由到特定服务。
- Header Route Predicate Factory :根据请求头中的信息进行路由决策。可以检查请求头是否存在某个字段,或者字段值是否匹配指定模式。如根据请求头中的
User-Agent
来决定路由。 - Host Route Predicate Factory:基于请求的主机名进行路由匹配。可以配置允许特定主机名的请求通过路由,常用于多租户或基于不同域名的路由场景。
- Method Route Predicate Factory:根据 HTTP 请求方法(GET、POST、PUT 等)来判断是否匹配路由。例如,只允许 POST 请求通过某个路由。
- Path Route Predicate Factory:通过匹配请求的路径来决定是否路由。可以使用 Ant 风格的路径模式进行匹配,灵活地根据不同路径将请求路由到不同服务。
- Query Route Predicate Factory:根据请求中的查询参数进行路由判断。可以检查是否存在特定参数,或者参数值是否符合指定条件。
- RemoteAddr Route Predicate Factory:根据请求的远程地址来进行路由决策。可用于限制某些 IP 地址段的访问,或者根据客户端的来源 IP 进行路由。
- Weight Route Predicate Factory:为不同的路由分配权重,根据权重来决定请求被路由到不同服务的概率。适用于负载均衡和流量分配的场景。
虽然断言类型众多,但核心原理都是类似的,掌握了一种,其他的也就触类旁通了,感兴趣的读者可私下自行对其他类型的断言工厂进行研究。
总结
本文从单体应用到微服务架构的演进历程切入,深入剖析了Spring Cloud Gateway
诞生的必然性。在阐述网关三大核心组件时,着重介绍了断言的作用,并以 PathRoutePredicateFactory
为例,详细解析Spring Cloud Gateway
中断言的实现机制。
事实上,虽然Spring Cloud gate Way
中的断言类型丰富多样,但其核心原理一脉相承。