还是先从整体架构入手,先看下Dubbo官方的架构设计图,调用方从集群中选择一个提供者时,首先得调用Directory获取Invoker列表,再根据配置的路由规则过滤掉不符合的提供者
Directory 的用途是保存 Invoker,可简单类比为 List。其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker
官网的介绍中明确到,Directory中的Invoker列表会随着注册中心的内容的变化而变化,下面就从源码入手,分析Directory中的Invoker列表是如何变化的
Directory接口
先看下接口定义
public interface Directory<T> extends Node {
//返回接口Class
Class<T> getInterface();
//获取提供者对应的Invoker列表
List<Invoker<T>> list(Invocation invocation) throws RpcException;
}
有几种实现呢?
可以看到底层有RegistryDirectory
、StaticDirectory
两种实现
RegistryDirectory
实现了NotifyListener
接口,而NotifyListener接口的notify回调方法会在注册中心中提供者发生变化时被调用,从而达到通知RegistryDirectory更新Invoker列表的效果,它是能根据注册中心动态变化的核心所在StaticDirectory
没有实现NotifyListener
接口,因此其中Invoker列表不会改变,所以呢,要明白:Directory中的Invoker列表会随着注册中心的内容的变化而变化这句话在这里不适用。
既然我们分析的主要目标是Directory中Invoker的动态变化过程,自然需要分析RegistryDirectory的实现
AbstractDirectory
看Dubbo源码如果看到Abstract开头的抽象类,就应该联想到模板方法,什么意思?看下
AbstractDirectory的实现,其list方法做了一些公共的实现逻辑,再把具体的获取Invoker列表的方法通过调用doList交由子类实现,如果子类有很多实现,均只需按doList的抽象方法模板定制化自己的实现
public abstract class AbstractDirectory<T> implements Directory<T> {
...忽略其他代码
@Override
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed) {
throw new RpcException("Directory already destroyed .url: " + getUrl());
}
return doList(invocation);
}
//抽象方法 由子类实现
protected abstract List<Invoker<T>> doList(Invocation invocation) throws RpcException;
}
抽象类中做公共实现,子类做特色实现,这是抽象类、抽象方法的基本思路
RegistryDirectory#doList
再跟到子类RegistryDirectory#doList方法中看下Invoker列表是如何获取的
可以看到最终调用的是routerChain.route来获取符合规则的Invoker列表并返回的,RouterChain
就是文章开头提到的Route路由匹配规则,和架构图中调用Router过滤Invoker相吻合
看下其中route方法
public class RouterChain<T> {
//提供者列表
private List<Invoker<T>> invokers = Collections.emptyList();
//路由列表
private volatile List<Router> routers = Collections.emptyList();
//遍历每个route,针对每个路由规则,分别对Invoker列表进行过滤
public List<Invoker<T>> route(URL url, Invocation invocation) {
List<Invoker<T>> finalInvokers = invokers;
for (Router router : routers) {
finalInvokers = router.route(finalInvokers, url, invocation);
}
return finalInvokers;
}
}
看到这里,都是读和过滤Invoker的操作,从RegistryDirectory
中获取Invoker列表的过程想必已经清楚了,那么还有最重要的一步,怎么动态更新RegistryDirectory中的Invoker列表呢?
RegistryDirectory#notify
RegistryDirectory既然实现了notify接口,就能在注册中心中内容变化时,监听到对应变化
接收到Url变化通知时,首先按服务类型做了分组,在注册中心中,有如下几种节点信息
/dubbo/serviceInterface/providers
服务提供者注册信息,包含多个服务者 URL 元数据信息。eg: dubbo://192.168.139.101:20880/com.dubbo.DemoService1?key=value&…/dubbo/serviceInterface/consumers
服务消费者注册信息,包含多个消费者 URL 元数据信息。eg: dubbo://192.168.139.101:8888/com.dubbo.DemoService1?key=value&…/dubbo/serviceInterface/router
路由配置信息,包含消费者路由策略 URL 元数据信息。eg: condition://0.0.0.0/com.dubbo.DemoService1?category=routers&key=value&…/dubbo/serviceInterface/configurators
外部化配置信息,包含服务者动态配置 URL 元数据信息。eg: override://0.0.0.0/com.dubbo.DemoService1?category=configurators&key=value&…
notify方法的最后,针对提供者类型的Url,调用refreshInvoker
方法更新Invoker列表
RegistryDirectory#refreshInvoker
refreshInvoker代码如下
- 首先判断URL列表的大小是否为1,并且协议是否为
EMPTY_PROTOCOL
空协议,为什么这么判断?那肯定是哪里塞了一个空协议进去咯
ZookeeperRegistry#toUrlsWithEmpty中有如下代码实现
private List<URL> toUrlsWithEmpty(URL consumer, String path, List<String> providers) {
List<URL> urls = toUrlsWithoutEmpty(consumer, providers);
if (urls == null || urls.isEmpty()) {
int i = path.lastIndexOf(PATH_SEPARATOR);
String category = i < 0 ? path : path.substring(i + 1);
URL empty = URLBuilder.from(consumer)
.setProtocol(EMPTY_PROTOCOL)
.addParameter(CATEGORY_KEY, category)
.build();
urls.add(empty);
}
return urls;
}
toUrlsWithEmpty()会针对providers为空的情况生成empty://协议的URL对象来通知消费者
- 如果URL列表不为空,设置服务状态为可用,更新RouterChain中Invoker列表,这也就是Directory中Invoker列表是动态可变的原因