在文章《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类
1 | public class Route implements Ordered { |
RouteDefinition类,包含了两个构造函数,还可以通过字符串的方式构建。
1 | @Validated |
##RouteDefinitionLocator
RouteDefinitionLocator只有一个方法来获取RouteDefinition异步序列。
RouteDefinitionLocator继承关系图
1 | public interface RouteDefinitionLocator { |
它的子类的作用加载情况如下:
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
13spring:
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
31public 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
14public 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,
1 | public interface RouteLocator { |
1 | @Override |
#设计模式
在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(); |