前面花了挺多时间,陆陆续续研读了Dubbo 消费端和服务端的启动流程,配置读取等,后面又研究了 Dubbo 代理对象生成,以及调用过程。
这篇文章开始将开始将研究的Dubbo 集群 路由功能,主要包括以下几个节点探究:
- 初始化
- 调用时间
- 种类及源码
- 更新操作
本篇例子是以单消费者,多服务提供者来进行,源码位于:
router
启动provider时,通过指定不同端口,从而产生多个实例。
而通过在 dubbo admin上配置 路由协议如下:
enabled: true
force: true
runtime: true
conditions:
- 'interface=HelloService&method=hello=>address=*:20880'
- 'interface=HelloService&method=hello2=>address=*:20882'
最终结果 hello 方法则只会在 20880 端口对应服务调用,而 hello2 则只会在20882 服务端口调用。
简单的例子程序之后,开启Router之旅!!
初始化
在 Dubbo 消费者中 代理对象 初始化详解 时候,提到了 RegistryProtocol
注册中心和集群的初始化,其中在 它的 doRefer
就是进行了 Router 的初始化:directory.buildRouterChain(subscribeUrl);
RegistryDirectory:存储了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息,比如 ip、端口、服务协议等。通过这些信息,服务消费者就可通过 Netty 等客户端进行远程调用
buildRouterChain
先看涉及到的代码:
public void buildRouterChain(URL url) {
// 调用 setRouterChain
this.setRouterChain(RouterChain.buildChain(url));
}
RouterChain.buildChain(url):
public static <T> RouterChain<T> buildChain(URL url) {
// 新建一个 RouterChain
return new RouterChain<>(url);
}
RouterChain 主要 维护invokers 集合,以及 routers集合:
// 维护从 注册中心获取的
private List<Invoker<T>> invokers = Collections.emptyList();
// 维护所有的路由规则,当有 改变时,由 ‘route://’ 改变.
private volatile List<Router> routers = Collections.emptyList();
// 内置 的 路由实例,即 各种路由实例集合,包括ConfigConditionRouter、TagRouter等
// instance will never delete or recreate.
private List<Router> builtinRouters = Collections.emptyList();
RouterChain 构造方法:
private RouterChain(URL url) {
// 加载 spi 类
List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
.getActivateExtension(url, (String[]) null);
// 根据 RouterFactory 获取具体的Router
List<Router> routers = extensionFactories.stream()
.map(factory -> factory.getRouter(url))
.collect(Collectors.toList());
// 初始化Router
initWithRouters(routers);
}
RouterFactory 的 SPI 文件:
file=org.apache.dubbo.rpc.cluster.router.file.FileRouterFactory
script=org.apache.dubbo.rpc.cluster.router.script.ScriptRouterFactory
condition=org.apache.dubbo.rpc.cluster.router.condition.ConditionRouterFactory
service=org.apache.dubbo.rpc.cluster.router.condition.config.ServiceRouterFactory # order=300
app=org.apache.dubbo.rpc.cluster.router.condition.config.AppRouterFactory # order = 200
tag=org.apache.dubbo.rpc.cluster.router.tag.TagRouterFactory # order = 100
mock=org.apache.dubbo.rpc.cluster.router.mock.MockRouterFactory # order = 0
上面首先通过加载SPI 文件,并且将@Adaptive
类都加载出来,所以最终加载出来为以下四个:
ServiceRouterFactory
AppRouterFactory
TagRouterFactory
MockRouterFactory
而后则会调用 这些工厂方法的 getRouter
方法。
Router 种类
这一小节有必要将Router
类结构贴出来,在Dubbo
中,基本都是使用工厂模式,使用 RouterFactory
产生 Router
。
所以Router 分为两类: ListenableRouter
和 非 ListenableRouter
类型。 ListenableRouter
即 可以 动态监听 注册中心变化,从而 是可以动态变化的。
RouterFacotry 类图
以下图展示了 RouterFacotry 中一些信息,所以只有 AppRouterFactory
、MockRouterFacotry
、TagRouterFactory
、ServiceRouterFactory
。
那么以 AppRouter 为例看下实现:
AppRouter
AppRouter 由 AppRouterFactory 的 getRouter 方法调用,而从 RouterChain 的私有构造方法中如下调用,
List<Router> routers = extensionFactories.stream()
.map(factory -> factory.getRouter(url))
.collect(Collectors.toList());
AppRouterFactory 中 getRouter
:
@Override
public Router getRouter(URL url) {
// 判断router
if (router != null) {
return router;
}
// 加锁 创建
synchronized (this) {
if (router == null) {
// 具体执行createRouter
router = createRouter(url);
}
}
return router;
}
createRouter方法:
private Router createRouter(URL url) {
return new AppRouter(DynamicConfiguration.getDynamicConfiguration(), url);
}
接下来在 AppRouter 中构造方法:
public AppRouter(DynamicConfiguration configuration, URL url) {
super(configuration, url, url.getParameter(CommonConstants.APPLICATION_KEY));
this.priority = APP_ROUTER_DEFAULT_PRIORITY;
}
在ListenableRouter 中构造方法:
public ListenableRouter(DynamicConfiguration configuration, URL url, String ruleKey) {
// 填充configuration 和 url
super(configuration, url);
// 默认force 为 false
this.force = false;
//
this.init(ruleKey);
}
上面代码主要是初始化相关工作,根据不同的RouterFactory
,执行不同的getRouter
。最终在init
中执行具体初始化逻辑:
private synchronized void init(String ruleKey) {
if (StringUtils.isEmpty(ruleKey)) {
return;
}
String routerKey = ruleKey + RULE_SUFFIX;
// 将该Router 注册到configuration中
configuration.addListener(routerKey, this);
// 尝试第一次取,看有没有 已配置的 router
String rule = configuration.getConfig(routerKey);
if (StringUtils.isNotEmpty(rule)) {
// 有就解析
this.process(new ConfigChangeEvent(routerKey, rule));
}
}
上面init 过程有以下几步:
- 将自己注册到configuration,当注册中心变更时得到通知
- 第一次尝试读取routerKey,如果有数据尝试解析
监听器接口为 ConfigurationListener
,当有变化则会执行 子类实现的 process
。
调用时间
由于上文中,配置的是 app 维度的 ,名字为 dubbo-provider
的 路由协议,但是 这个协议同样属于 com.anla.rpc.service.HelloService
。所以当初始化ServiceRouter
时候,仍然会读取到这两条condition
。
这样一来,我们就在com.anla.rpc.service.HelloService
的 ServiceRouter
中会有两条路由协议。
在 RegistryDirectory
调用 list
时候,会使用到 路由。
RegistryDirectory
中维护了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息,当然也能从注册中心获取可用的 invoker
,而后在调用 list
方法时,会使用 Router
进行进一步过滤 。
而后通过 RegistryDirectory
中doList
-> RouterChain
的 router
:
RouterChain 的router:
public List<Invoker<T>> route(URL url, Invocation invocation) {
List<Invoker<T>> finalInvokers = invokers;
// 对每一个 routers 进行遍历,执行其route方法进行过滤
for (Router router : routers) {
finalInvokers = router.route(finalInvokers, url, invocation);
}
return finalInvokers;
}
而后会调用每一个Router
的route
方法进行invokers
的过滤。
更新流程
当从注册中心更新时,会以怎样一种方式更新Router呢?
由于往注册中心注册了 节点更新事件,本文以ZooKeeper
为例,所以当在 DubboAdmin
更新了 Router 信息后,会调用 ConfigurationListener
的 process
。而对于 Router
来说, 只有 ListenableRouter
和 TagRouter
有实现 ConfigurationListener
,所以进一步筛选后,则会调用到他们的 process
方法。
而前面分析可知,Router
中process
方法就是对 Router
配置信息进行解析,而后存入其中的conditions
中,而后当 有 RegistryDirectory
调用 list ,而后会调用 Router
的 route
方法,对已有的Invokers
进行进一步 筛选
权重
以下介绍了 几个Router 的 权重以及介绍,侧面了反映了Router 优先级问题。
类 | 权重 | 注册中心解析的key 或解析 |
---|---|---|
AppRouter | 150 | dubbo-consumer |
ServiceRouter | 140 | com.anla.rpc.service.HelloService |
TagRouter | 100 | 目前是从系统当前读取,即application.tag-router,而不具有直接监听在线配置变更 |
MockInvokersSelector | Integer.MIN_VALUE | 主要用来实现mock,当接口是mock特性,route方法将会排除所有非mock的invoker,只返回mock的 |
ConditionRouter | 默认为0,从url中读取 | 主要用于存储 ConditionRunleParse 解析后的规则 |
ScriptRouter | 从url中读,默认为0 |
默认从小到大排序
后语
本文以一个例子开头,深入源码挖掘了 Dubbo 中 Router 实现,从 初始化,调用,更新三个方面介绍了Router 原理,但是本文没有介绍具体配置 Router 的规则以及 Router 规则解析的细节,
当然这些可以不妨碍从宏观去了解Router,有兴趣可以去Dubbo 官方文档中获取相关知识。
相关 配置规则以及解析源码可以看 https://dubbo.apache.org/zh-cn/docs/user/demos/routing-rule.html
和 https://dubbo.apache.org/zh-cn/docs/source_code_guide/router.html