SpringCloudGateway源码解析(3)- 路由

SpringCloudGateway源码解析(3)- 路由

​ 在文章《SpringCloudGateway源码解析-揭开SpringCloudGateway神秘面纱》中,我们从宏观上了解了Spring Cloud Gateway的整体架构和思想,本篇文章就是要带着大家了解网关的一等公民”路由”的前世和今生。

前言

​ 在文章《SpringCloudGateway源码解析-揭开SpringCloudGateway神秘面纱》中,我们从宏观上了解了Spring Cloud Gateway的整体架构和思想,本篇文章就是要带着大家了解网关的一等公民”路由”的前世和今生。

路由

什么是路由?维基百科中定义。路由routing)就是通过互联的网络信息从源地址传输到目的地址的活动。路由发生在OSI网络参考模型中的第三层即网络层

通过定义我们可以很清晰的认识到,路由中最核心的两点,即”源地址”和”目的地址”,更简单的说,网关最核心的功能就是把一个网络包安全快速的从源头搬运到目的地地址上

在Spring Cloud Gateway中,路由是一个叫Route的对象定义的,作为Gateway的使用者,需要了解路由是如何创建?如何修改?如何删除?或者更高级的特性,如何在不重启网关的情况下,任意的修改路由的配置,并让它实时生效等等,而且网关左右微服务体系下的门户,”不重启”是很重要的特性。接下来我们就从源码角度,仔细的学习Spring Cloud Gateway是如何实现一个网关路由的。

##Route && RouteDefinition

了解大数据技术的同学应该听说过”元数据”这个术语。元数据是描述数据的数据,而Gateway中的RouteDefinition,就是为了描述Route的。换句话说,配置文件—>RouteDefinition—>Route。那这个时候有的同学会发问了,为何不能直接配置文件——>Route,为何还要再加一个RouteDefinition?是因为Spring Cloud Gateway不仅仅支持Route通过配置文件的方式配置,还支持FluentAPI、Endpoint、注册中心等等其他的路由来源,根据”单一职责”设计,RouteDefinition使得Route和用户的行为解耦。聪明的小伙伴的可以发现,这种方式的可扩展性是非常强的,最左侧可以增加Redis,Mysql,Apollo等等组件,想想还有点小兴奋。

路由配置转换为Route对象

Route类

1
2
3
4
5
6
7
8
9
10
11
12
public class Route implements Ordered {

private final String id;//路由id

private final URI uri;//target地址

private final int order;//路由优先级,值越小优先级越高

private final AsyncPredicate<ServerWebExchange> predicate;//路由断言

private final List<GatewayFilter> gatewayFilters;//路由过滤器栈
}

RouteDefinition类,包含了两个构造函数,还可以通过字符串的方式构建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Validated
public class RouteDefinition {

@NotEmpty
private String id = UUID.randomUUID().toString();//路由元数据

@NotEmpty
@Valid
private List<PredicateDefinition> predicates = new ArrayList<>();//路由断言集合

@Valid
private List<FilterDefinition> filters = new ArrayList<>();//路由过滤器集合

@NotNull
private URI uri;//目标地址URI

private int order = 0;//路由优先级


public RouteDefinition() {
}

public RouteDefinition(String text) {
int eqIdx = text.indexOf('=');
if (eqIdx <= 0) {
throw new ValidationException("Unable to parse RouteDefinition text '" + text
+ "'" + ", must be of the form name=value");
}
setId(text.substring(0, eqIdx));
String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");
setUri(URI.create(args[0]));
for (int i = 1; i < args.length; i++) {
this.predicates.add(new PredicateDefinition(args[i]));
}
}
}

##RouteDefinitionLocator

RouteDefinitionLocator只有一个方法来获取RouteDefinition异步序列。

RouteDefinitionLocator

​ RouteDefinitionLocator继承关系图

1
2
3
public interface RouteDefinitionLocator {
Flux<RouteDefinition> getRouteDefinitions();
}

它的子类的作用加载情况如下:

  • CachingRouteDefinitionLocator不再使用。

  • PropertiesRouteDefinitionLocator在GatewayAutoConfiguration中加载。它持有GatewayProperties对象,GatewayProperties对象通过@ConfigurationProperties注解自动装配RouteDefinition,所以通过PropertiesRouteDefinitionLocator类可以直接获取application.yml中所有用户配置的路由信息

    1
    2
    3
    4
    5
    6
    7
    @ConfigurationProperties("spring.cloud.gateway")
    @Validated
    public class GatewayProperties {
    @NotNull
    @Valid
    private List<RouteDefinition> routes = new ArrayList<>();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    spring:
    cloud:
    gateway:
    routes:
    - id: yml-id
    uri: www.baidu.com
    order: -1
    predicates:
    - name: predicatesName
    args:
    arg0: 1
    arg1: 2
    - Path=/echo
  • InMemoryRouteDefinitionRepository在GatewayAutoConfiguration中加载。InMemoryRouteDefinitionRepository提供了save()和delete()两个函数,默认通过gateway的AbstractGatewayControllerEndpoint调用,换句话说,Gateway提供了RestAPI来新增和删除网关的功能,但是这种方式比较鸡肋(JVM级的更新,没有提供持久化设定,如果Gateway集群有十台服务器,你需要操作十遍),生产环境下动态路由,只能借助其他的持久化中间件来实现动态路由,后续的章节,会专门讲如何使用Apollo(携程开源的高可用注册中心)来实现Gateway的动态路由。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {

    private final Map<String, RouteDefinition> routes = synchronizedMap(
    new LinkedHashMap<String, RouteDefinition>());

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
    return route.flatMap(r -> {
    routes.put(r.getId(), r);
    return Mono.empty();
    });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
    return routeId.flatMap(id -> {
    if (routes.containsKey(id)) {
    routes.remove(id);
    return Mono.empty();
    }
    return Mono.defer(() -> Mono.error(
    new NotFoundException("RouteDefinition not found: " + routeId)));
    });
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
    return Flux.fromIterable(routes.values());
    }

    }
  • CompositeRouteDefinitionLocator在GatewayAutoConfiguration中加载。CompositeRouteDefinitionLocator持有了所有的RouteDefinitionLocator。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator {

    private final Flux<RouteDefinitionLocator> delegates;

    public CompositeRouteDefinitionLocator(Flux<RouteDefinitionLocator> delegates) {
    this.delegates = delegates;
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
    return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);
    }

    }
  • DiscoveryClientRouteDefinitionLocator在GatewayDiscoveryClientAutoConfiguration中加载,目的是集成SpringCloud的注册中心(例如Eureka,Zookeeper,Nacos等),DiscoveryClientRouteDefinitionLocator依赖DiscoveryClient对象,所有的路由信息均是通过DiscoveryClient获得,然后转换成RouteDefinition对象。

##RouteLocator

RouteLocator只包含一个函数getRoutes()获取所有的Route对象。它有三个实现类,但是真正干活的实现类是RouteDefinitionRouteLocator,

RouteLocator依赖关系图

1
2
3
public interface RouteLocator {
Flux<Route> getRoutes();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 @Override
public Flux<Route> getRoutes() {
//获取RouteDefinition集合,调用convertToRoute转换成Route对象
return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute)
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition matched: " + route.getId());
}
return route;
});
}

private Route convertToRoute(RouteDefinition routeDefinition) {
//断言
AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
//过滤器
List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);
//构建Route
return Route.async(routeDefinition).asyncPredicate(predicate)
.replaceFilters(gatewayFilters).build();
}

#设计模式

在Route包下,Spencer Gibb采用了模板模式和建造者模式。

模板模式

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。其实我个人理解模板模式就是面向接口编程。RouteLocator和RouteDefinitionLocator都采用了模板模式

建造者模式

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。

FluentAPI就是一个很经典的建造者模式的应用。

1
builder.routes().route(r -> r.path("/anything/png").uri("https://www.jd.com")).build();

评论